| 12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001 | <template lang='pug'>  .editor-markdown    v-toolbar.editor-markdown-toolbar(dense, color='primary', dark, flat, style='overflow-x: hidden;')      template(v-if='isModalShown')        v-spacer        v-btn.animated.fadeInRight(text, @click='closeAllModal')          v-icon(left) mdi-arrow-left-circle          span {{$t('editor:backToEditor')}}      template(v-else)        v-tooltip(bottom, color='primary')          template(v-slot:activator='{ on }')            v-btn.animated.fadeIn(icon, tile, v-on='on', @click='toggleMarkup({ start: `**` })').mx-0              v-icon mdi-format-bold          span {{$t('editor:markup.bold')}}        v-tooltip(bottom, color='primary')          template(v-slot:activator='{ on }')            v-btn.animated.fadeIn.wait-p1s(icon, tile, v-on='on', @click='toggleMarkup({ start: `*` })').mx-0              v-icon mdi-format-italic          span {{$t('editor:markup.italic')}}        v-tooltip(bottom, color='primary')          template(v-slot:activator='{ on }')            v-btn.animated.fadeIn.wait-p2s(icon, tile, v-on='on', @click='toggleMarkup({ start: `~~` })').mx-0              v-icon mdi-format-strikethrough          span {{$t('editor:markup.strikethrough')}}        v-menu(offset-y, open-on-hover)          template(v-slot:activator='{ on }')            v-btn.animated.fadeIn.wait-p3s(icon, tile, v-on='on').mx-0              v-icon mdi-format-header-pound          v-list.py-0            template(v-for='(n, idx) in 6')              v-list-item(@click='setHeaderLine(n)', :key='idx')                v-list-item-action                  v-icon(:size='24 - (idx - 1) * 2') mdi-format-header-{{n}}                v-list-item-title {{$t('editor:markup.heading', { level: n })}}              v-divider(v-if='idx < 5')        v-tooltip(bottom, color='primary')          template(v-slot:activator='{ on }')            v-btn.animated.fadeIn.wait-p4s(icon, tile, v-on='on', @click='toggleMarkup({ start: `~` })').mx-0              v-icon mdi-format-subscript          span {{$t('editor:markup.subscript')}}        v-tooltip(bottom, color='primary')          template(v-slot:activator='{ on }')            v-btn.animated.fadeIn.wait-p5s(icon, tile, v-on='on', @click='toggleMarkup({ start: `^` })').mx-0              v-icon mdi-format-superscript          span {{$t('editor:markup.superscript')}}        v-menu(offset-y, open-on-hover)          template(v-slot:activator='{ on }')            v-btn.animated.fadeIn.wait-p6s(icon, tile, v-on='on').mx-0              v-icon mdi-alpha-t-box-outline          v-list.py-0            v-list-item(@click='insertBeforeEachLine({ content: `> `})')              v-list-item-action                v-icon mdi-alpha-t-box-outline              v-list-item-title {{$t('editor:markup.blockquote')}}            v-divider            v-list-item(@click='insertBeforeEachLine({ content: `> `, after: `{.is-info}`})')              v-list-item-action                v-icon(color='blue') mdi-alpha-i-box-outline              v-list-item-title {{$t('editor:markup.blockquoteInfo')}}            v-divider            v-list-item(@click='insertBeforeEachLine({ content: `> `, after: `{.is-success}`})')              v-list-item-action                v-icon(color='success') mdi-alpha-s-box-outline              v-list-item-title {{$t('editor:markup.blockquoteSuccess')}}            v-divider            v-list-item(@click='insertBeforeEachLine({ content: `> `, after: `{.is-warning}`})')              v-list-item-action                v-icon(color='warning') mdi-alpha-w-box-outline              v-list-item-title {{$t('editor:markup.blockquoteWarning')}}            v-divider            v-list-item(@click='insertBeforeEachLine({ content: `> `, after: `{.is-danger}`})')              v-list-item-action                v-icon(color='error') mdi-alpha-e-box-outline              v-list-item-title {{$t('editor:markup.blockquoteError')}}            v-divider        v-tooltip(bottom, color='primary')          template(v-slot:activator='{ on }')            v-btn.animated.fadeIn.wait-p7s(icon, tile, v-on='on', @click='insertBeforeEachLine({ content: `- `})').mx-0              v-icon mdi-format-list-bulleted          span {{$t('editor:markup.unorderedList')}}        v-tooltip(bottom, color='primary')          template(v-slot:activator='{ on }')            v-btn.animated.fadeIn.wait-p8s(icon, tile, v-on='on', @click='insertBeforeEachLine({ content: `1. `})').mx-0              v-icon mdi-format-list-numbered          span {{$t('editor:markup.orderedList')}}        v-tooltip(bottom, color='primary')          template(v-slot:activator='{ on }')            v-btn.animated.fadeIn.wait-p9s(icon, tile, v-on='on', @click='toggleMarkup({ start: "`" })').mx-0              v-icon mdi-code-tags          span {{$t('editor:markup.inlineCode')}}        v-tooltip(bottom, color='primary')          template(v-slot:activator='{ on }')            v-btn.animated.fadeIn.wait-p10s(icon, tile, v-on='on', @click='toggleMarkup({ start: `<kbd>`, end: `</kbd>` })').mx-0              v-icon mdi-keyboard-variant          span {{$t('editor:markup.keyboardKey')}}        v-tooltip(bottom, color='primary')          template(v-slot:activator='{ on }')            v-btn.animated.fadeIn.wait-p11s(icon, tile, v-on='on', @click='insertAfter({ content: `---`, newLine: true })').mx-0              v-icon mdi-minus          span {{$t('editor:markup.horizontalBar')}}        template(v-if='$vuetify.breakpoint.mdAndUp')          v-spacer          v-tooltip(bottom, color='primary', v-if='previewShown')            template(v-slot:activator='{ on }')              v-btn.animated.fadeIn.wait-p1s(icon, tile, v-on='on', @click='spellModeActive = !spellModeActive').mx-0                v-icon(:color='spellModeActive ? `amber` : `white`') mdi-spellcheck            span {{$t('editor:markup.toggleSpellcheck')}}          v-tooltip(bottom, color='primary')            template(v-slot:activator='{ on }')              v-btn.animated.fadeIn.wait-p2s(icon, tile, v-on='on', @click='previewShown = !previewShown').mx-0                v-icon mdi-book-open-outline            span {{$t('editor:markup.togglePreviewPane')}}    .editor-markdown-main      .editor-markdown-sidebar        v-tooltip(right, color='teal')          template(v-slot:activator='{ on }')            v-btn.animated.fadeInLeft(icon, tile, v-on='on', dark, @click='insertLink').mx-0              v-icon mdi-link-plus          span {{$t('editor:markup.insertLink')}}        v-tooltip(right, color='teal')          template(v-slot:activator='{ on }')            v-btn.mt-3.animated.fadeInLeft.wait-p1s(icon, tile, v-on='on', dark, @click='toggleModal(`editorModalMedia`)').mx-0              v-icon(:color='activeModal === `editorModalMedia` ? `teal` : ``') mdi-folder-multiple-image          span {{$t('editor:markup.insertAssets')}}        v-tooltip(right, color='teal')          template(v-slot:activator='{ on }')            v-btn.mt-3.animated.fadeInLeft.wait-p2s(icon, tile, v-on='on', dark, disabled, @click='toggleModal(`editorModalBlocks`)').mx-0              v-icon(:color='activeModal === `editorModalBlocks` ? `teal` : ``') mdi-view-dashboard-outline          span {{$t('editor:markup.insertBlock')}}        v-tooltip(right, color='teal')          template(v-slot:activator='{ on }')            v-btn.mt-3.animated.fadeInLeft.wait-p3s(icon, tile, v-on='on', dark, disabled).mx-0              v-icon mdi-code-braces          span {{$t('editor:markup.insertCodeBlock')}}        v-tooltip(right, color='teal')          template(v-slot:activator='{ on }')            v-btn.mt-3.animated.fadeInLeft.wait-p4s(icon, tile, v-on='on', dark, disabled).mx-0              v-icon mdi-movie          span {{$t('editor:markup.insertVideoAudio')}}        v-tooltip(right, color='teal')          template(v-slot:activator='{ on }')            v-btn.mt-3.animated.fadeInLeft.wait-p5s(icon, tile, v-on='on', dark, disabled).mx-0              v-icon mdi-chart-multiline          span {{$t('editor:markup.insertDiagram')}}        v-tooltip(right, color='teal')          template(v-slot:activator='{ on }')            v-btn.mt-3.animated.fadeInLeft.wait-p6s(icon, tile, v-on='on', dark, disabled).mx-0              v-icon mdi-function-variant          span {{$t('editor:markup.insertMathExpression')}}        v-tooltip(right, color='teal')          template(v-slot:activator='{ on }')            v-btn.mt-3.animated.fadeInLeft.wait-p7s(icon, tile, v-on='on', dark, disabled).mx-0              v-icon mdi-table-plus          span {{$t('editor:markup.tableHelper')}}        template(v-if='$vuetify.breakpoint.mdAndUp')          v-spacer          v-tooltip(right, color='teal')            template(v-slot:activator='{ on }')              v-btn.mt-3.animated.fadeInLeft.wait-p8s(icon, tile, v-on='on', dark, @click='toggleFullscreen').mx-0                v-icon mdi-arrow-expand-all            span {{$t('editor:markup.distractionFreeMode')}}          v-tooltip(right, color='teal')            template(v-slot:activator='{ on }')              v-btn.mt-3.animated.fadeInLeft.wait-p9s(icon, tile, v-on='on', dark, @click='toggleHelp').mx-0                v-icon(:color='helpShown ? `teal` : ``') mdi-help-circle            span {{$t('editor:markup.markdownFormattingHelp')}}      .editor-markdown-editor        textarea(ref='cm')      transition(name='editor-markdown-preview')        .editor-markdown-preview(v-if='previewShown')          .editor-markdown-preview-content.contents(ref='editorPreviewContainer')            div(              ref='editorPreview'              v-html='previewHTML'              :spellcheck='spellModeActive'              :contenteditable='spellModeActive'              @blur='spellModeActive = false'              )    v-system-bar.editor-markdown-sysbar(dark, status, color='grey darken-3')      .caption.editor-markdown-sysbar-locale {{locale.toUpperCase()}}      .caption.px-3 /{{path}}      template(v-if='$vuetify.breakpoint.mdAndUp')        v-spacer        .caption Markdown        v-spacer        .caption Ln {{cursorPos.line + 1}}, Col {{cursorPos.ch + 1}}    markdown-help(v-if='helpShown')    page-selector(mode='select', v-model='insertLinkDialog', :open-handler='insertLinkHandler', :path='path', :locale='locale')</template><script>import _ from 'lodash'import { get, sync } from 'vuex-pathify'import markdownHelp from './markdown/help.vue'import gql from 'graphql-tag'import DOMPurify from 'dompurify'/* global siteConfig, siteLangs */// ========================================// IMPORTS// ========================================// Code Mirrorimport CodeMirror from 'codemirror'import 'codemirror/lib/codemirror.css'// Languageimport 'codemirror/mode/markdown/markdown.js'// Addonsimport 'codemirror/addon/selection/active-line.js'import 'codemirror/addon/display/fullscreen.js'import 'codemirror/addon/display/fullscreen.css'import 'codemirror/addon/selection/mark-selection.js'import 'codemirror/addon/search/searchcursor.js'import 'codemirror/addon/hint/show-hint.js'// Markdown-itimport MarkdownIt from 'markdown-it'import mdAttrs from 'markdown-it-attrs'import mdEmoji from 'markdown-it-emoji'import mdTaskLists from 'markdown-it-task-lists'import mdExpandTabs from 'markdown-it-expand-tabs'import mdAbbr from 'markdown-it-abbr'import mdSup from 'markdown-it-sup'import mdSub from 'markdown-it-sub'import mdMark from 'markdown-it-mark'import mdFootnote from 'markdown-it-footnote'import mdImsize from 'markdown-it-imsize'import katex from 'katex'import underline from '../../libs/markdown-it-underline'import 'katex/dist/contrib/mhchem'import twemoji from 'twemoji'import plantuml from './markdown/plantuml'// Prism (Syntax Highlighting)import Prism from 'prismjs'// Mermaidimport mermaid from 'mermaid'// Helpersimport katexHelper from './common/katex'// ========================================// INIT// ========================================// Platform detectionconst CtrlKey = /Mac/.test(navigator.platform) ? 'Cmd' : 'Ctrl'// Prism ConfigPrism.plugins.autoloader.languages_path = '/_assets/js/prism/'Prism.plugins.NormalizeWhitespace.setDefaults({  'remove-trailing': true,  'remove-indent': true,  'left-trim': true,  'right-trim': true,  'remove-initial-line-feed': true,  'tabs-to-spaces': 2})// Markdown Instanceconst md = new MarkdownIt({  html: true,  breaks: true,  linkify: true,  typography: true,  highlight(str, lang) {    if (['mermaid', 'plantuml'].includes(lang)) {      return `<pre class="codeblock-${lang}"><code>${_.escape(str)}</code></pre>`    } else {      return `<pre class="line-numbers"><code class="language-${lang}">${_.escape(str)}</code></pre>`    }  }})  .use(mdAttrs, {    allowedAttributes: ['id', 'class', 'target']  })  .use(underline)  .use(mdEmoji)  .use(mdTaskLists, {label: true, labelAfter: true})  .use(mdExpandTabs)  .use(mdAbbr)  .use(mdSup)  .use(mdSub)  .use(mdMark)  .use(mdFootnote)  .use(mdImsize)// ========================================// HELPER FUNCTIONS// ========================================// Inject line numbers for preview scroll synclet linesMap = []function injectLineNumbers (tokens, idx, options, env, slf) {  let line  if (tokens[idx].map && tokens[idx].level === 0) {    line = tokens[idx].map[0]    tokens[idx].attrJoin('class', 'line')    tokens[idx].attrSet('data-line', String(line))    linesMap.push(line)  }  return slf.renderToken(tokens, idx, options, env, slf)}md.renderer.rules.paragraph_open = injectLineNumbersmd.renderer.rules.heading_open = injectLineNumbersmd.renderer.rules.blockquote_open = injectLineNumbers// ========================================// PLANTUML// ========================================// TODO: Use same options as defined in backendplantuml.init(md, {})// ========================================// KATEX// ========================================md.inline.ruler.after('escape', 'katex_inline', katexHelper.katexInline)md.renderer.rules.katex_inline = (tokens, idx) => {  try {    return katex.renderToString(tokens[idx].content, {      displayMode: false    })  } catch (err) {    console.warn(err)    return tokens[idx].content  }}md.block.ruler.after('blockquote', 'katex_block', katexHelper.katexBlock, {  alt: [ 'paragraph', 'reference', 'blockquote', 'list' ]})md.renderer.rules.katex_block = (tokens, idx) => {  try {    return `<p>` + katex.renderToString(tokens[idx].content, {      displayMode: true    }) + `</p>`  } catch (err) {    console.warn(err)    return tokens[idx].content  }}// ========================================// TWEMOJI// ========================================md.renderer.rules.emoji = (token, idx) => {  return twemoji.parse(token[idx].content, {    callback (icon, opts) {      return `/_assets/svg/twemoji/${icon}.svg`    }  })}// ========================================// Vue Component// ========================================let mermaidId = 0export default {  components: {    markdownHelp  },  props: {    save: {      type: Function,      default: () => {}    }  },  data() {    return {      fabInsertMenu: false,      cm: null,      cursorPos: { ch: 0, line: 1 },      previewShown: true,      previewHTML: '',      helpShown: false,      spellModeActive: false,      insertLinkDialog: false    }  },  computed: {    isMobile() {      return this.$vuetify.breakpoint.smAndDown    },    isModalShown() {      return this.helpShown || this.activeModal !== ''    },    locale: get('page/locale'),    path: get('page/path'),    mode: get('editor/mode'),    activeModal: sync('editor/activeModal')  },  watch: {    previewShown (newValue, oldValue) {      if (newValue && !oldValue) {        this.$nextTick(() => {          this.renderMermaidDiagrams()          Prism.highlightAllUnder(this.$refs.editorPreview)          Array.from(this.$refs.editorPreview.querySelectorAll('pre.line-numbers')).forEach(pre => pre.classList.add('prismjs'))        })      }    },    spellModeActive (newValue, oldValue) {      if (newValue) {        this.$nextTick(() => {          this.$refs.editorPreview.focus()        })      }    }  },  methods: {    toggleModal(key) {      this.activeModal = (this.activeModal === key) ? '' : key      this.helpShown = false    },    closeAllModal() {      this.activeModal = ''      this.helpShown = false    },    onCmInput: _.debounce(function (newContent) {      linesMap = []      this.$store.set('editor/content', newContent)      this.previewHTML = DOMPurify.sanitize(md.render(newContent))      this.$nextTick(() => {        this.renderMermaidDiagrams()        Prism.highlightAllUnder(this.$refs.editorPreview)        Array.from(this.$refs.editorPreview.querySelectorAll('pre.line-numbers')).forEach(pre => pre.classList.add('prismjs'))        this.scrollSync(this.cm)      })    }, 500),    onCmPaste (cm, ev) {      // const clipItems = (ev.clipboardData || ev.originalEvent.clipboardData).items      // for (let clipItem of clipItems) {      //   if (_.startsWith(clipItem.type, 'image/')) {      //     const file = clipItem.getAsFile()      //     const reader = new FileReader()      //     reader.onload = evt => {      //       this.$store.commit(`loadingStart`, 'editor-paste-image')      //       this.insertAfter({      //         content: ``,      //         newLine: true      //       })      //     }      //     reader.readAsDataURL(file)      //   }      // }    },    /**     * Update cursor state     */    positionSync(cm) {      this.cursorPos = cm.getCursor('head')    },    /**     * Wrap selection with start / end tags     */    toggleMarkup({ start, end }) {      if (!end) { end = start }      if (!this.cm.doc.somethingSelected()) {        return this.$store.commit('showNotification', {          message: this.$t('editor:markup.noSelectionError'),          style: 'warning',          icon: 'warning'        })      }      this.cm.doc.replaceSelections(this.cm.doc.getSelections().map(s => start + s + end))    },    /**     * Set current line as header     */    setHeaderLine(lvl) {      const curLine = this.cm.doc.getCursor('head').line      let lineContent = this.cm.doc.getLine(curLine)      const lineLength = lineContent.length      if (_.startsWith(lineContent, '#')) {        lineContent = lineContent.replace(/^(#+ )/, '')      }      lineContent = _.times(lvl, n => '#').join('') + ` ` + lineContent      this.cm.doc.replaceRange(lineContent, { line: curLine, ch: 0 }, { line: curLine, ch: lineLength })    },    /**     * Get the header lever of the current line     */    getHeaderLevel(cm) {      const curLine = this.cm.doc.getCursor('head').line      let lineContent = this.cm.doc.getLine(curLine)      let lvl = 0      const result = lineContent.match(/^(#+) /)      if (result) {        lvl = _.get(result, '[1]', '').length      }      return lvl    },    /**     * Insert content at cursor     */    insertAtCursor({ content }) {      const cursor = this.cm.doc.getCursor('head')      this.cm.doc.replaceRange(content, cursor)    },    /**     * Insert content after current line     */    insertAfter({ content, newLine }) {      const curLine = this.cm.doc.getCursor('to').line      const lineLength = this.cm.doc.getLine(curLine).length      this.cm.doc.replaceRange(newLine ? `\n${content}\n` : content, { line: curLine, ch: lineLength + 1 })    },    /**     * Insert content before current line     */    insertBeforeEachLine({ content, after }) {      let lines = []      if (!this.cm.doc.somethingSelected()) {        lines.push(this.cm.doc.getCursor('head').line)      } else {        lines = _.flatten(this.cm.doc.listSelections().map(sl => {          const range = Math.abs(sl.anchor.line - sl.head.line) + 1          const lowestLine = (sl.anchor.line > sl.head.line) ? sl.head.line : sl.anchor.line          return _.times(range, l => l + lowestLine)        }))      }      lines.forEach(ln => {        let lineContent = this.cm.doc.getLine(ln)        const lineLength = lineContent.length        if (_.startsWith(lineContent, content)) {          lineContent = lineContent.substring(content.length)        }        this.cm.doc.replaceRange(content + lineContent, { line: ln, ch: 0 }, { line: ln, ch: lineLength })      })      if (after) {        const lastLine = _.last(lines)        this.cm.doc.replaceRange(`\n${after}\n`, { line: lastLine, ch: this.cm.doc.getLine(lastLine).length + 1 })      }    },    /**     * Update scroll sync     */    scrollSync: _.debounce(function (cm) {      if (!this.previewShown || cm.somethingSelected()) { return }      let currentLine = cm.getCursor().line      if (currentLine < 3) {        this.Velocity(this.$refs.editorPreview, 'stop', true)        this.Velocity(this.$refs.editorPreview.firstChild, 'scroll', { offset: '-50', duration: 1000, container: this.$refs.editorPreviewContainer })      } else {        let closestLine = _.findLast(linesMap, n => n <= currentLine)        let destElm = this.$refs.editorPreview.querySelector(`[data-line='${closestLine}']`)        if (destElm) {          this.Velocity(this.$refs.editorPreview, 'stop', true)          this.Velocity(destElm, 'scroll', { offset: '-100', duration: 1000, container: this.$refs.editorPreviewContainer })        }      }    }, 500),    toggleHelp () {      this.helpShown = !this.helpShown      this.activeModal = ''    },    toggleFullscreen () {      this.cm.setOption('fullScreen', true)    },    refresh() {      this.$nextTick(() => {        this.cm.refresh()      })    },    renderMermaidDiagrams () {      document.querySelectorAll('.editor-markdown-preview pre.codeblock-mermaid > code').forEach(elm => {        mermaidId++        const mermaidDef = elm.innerText        const mmElm = document.createElement('div')        mmElm.innerHTML = `<div id="mermaid-id-${mermaidId}">${mermaid.render(`mermaid-id-${mermaidId}`, mermaidDef)}</div>`        elm.parentElement.replaceWith(mmElm)      })    },    autocomplete (cm, change) {      if (cm.getModeAt(cm.getCursor()).name !== 'markdown') {        return      }      // Links      if (change.text[0] === '(') {        const curLine = cm.getLine(change.from.line).substring(0, change.from.ch)        if (curLine[curLine.length - 1] === ']') {          cm.showHint({            hint: async (cm, options) => {              const cur = cm.getCursor()              const token = cm.getTokenAt(cur)              try {                const respRaw = await this.$apollo.query({                  query: gql`                    query ($query: String!, $locale: String) {                      pages {                        search(query:$query, locale:$locale) {                          results {                            title                            path                            locale                          }                          totalHits                        }                      }                    }                  `,                  variables: {                    query: token.string,                    locale: this.locale                  },                  fetchPolicy: 'cache-first'                })                const resp = _.get(respRaw, 'data.pages.search', {})                if (resp && resp.totalHits > 0) {                  return {                    list: resp.results.map(r => ({                      text: (siteLangs.length > 0 ? `/${r.locale}/${r.path}` : `/${r.path}`) + ')',                      displayText: siteLangs.length > 0 ? `/${r.locale}/${r.path} - ${r.title}` : `/${r.path} - ${r.title}`                    })),                    from: CodeMirror.Pos(cur.line, token.start),                    to: CodeMirror.Pos(cur.line, token.end)                  }                }              } catch (err) {}              return {                list: [],                from: CodeMirror.Pos(cur.line, token.start),                to: CodeMirror.Pos(cur.line, token.end)              }            }          })        }      }    },    insertLink () {      this.insertLinkDialog = true    },    insertLinkHandler ({ locale, path }) {      const lastPart = _.last(path.split('/'))      this.insertAtCursor({        content: siteLangs.length > 0 ? `[${lastPart}](/${locale}/${path})` : `[${lastPart}](/${path})`      })    }  },  mounted() {    this.$store.set('editor/editorKey', 'markdown')    if (this.mode === 'create' && !this.$store.get('editor/content')) {      this.$store.set('editor/content', '# Header\nYour content here')    }    // Initialize Mermaid API    mermaid.initialize({      startOnLoad: false,      theme: this.$vuetify.theme.dark ? `dark` : `default`    })    // Initialize CodeMirror    this.cm = CodeMirror.fromTextArea(this.$refs.cm, {      tabSize: 2,      mode: 'text/markdown',      theme: 'wikijs-dark',      lineNumbers: true,      lineWrapping: true,      line: true,      styleActiveLine: true,      highlightSelectionMatches: {        annotateScrollbar: true      },      viewportMargin: 50,      inputStyle: 'contenteditable',      allowDropFileTypes: ['image/jpg', 'image/png', 'image/svg', 'image/jpeg', 'image/gif'],      direction: siteConfig.rtl ? 'rtl' : 'ltr'    })    this.cm.setValue(this.$store.get('editor/content'))    this.cm.on('change', c => {      this.$store.set('editor/content', c.getValue())      this.onCmInput(this.$store.get('editor/content'))    })    if (this.$vuetify.breakpoint.mdAndUp) {      this.cm.setSize(null, 'calc(100vh - 112px - 24px)')    } else {      this.cm.setSize(null, 'calc(100vh - 112px - 16px)')    }    // Set Keybindings    const keyBindings = {      'F11' (c) {        c.setOption('fullScreen', !c.getOption('fullScreen'))      },      'Esc' (c) {        if (c.getOption('fullScreen')) c.setOption('fullScreen', false)      }    }    _.set(keyBindings, `${CtrlKey}-S`, c => {      this.save()      return false    })    _.set(keyBindings, `${CtrlKey}-B`, c => {      this.toggleMarkup({ start: `**` })      return false    })    _.set(keyBindings, `${CtrlKey}-I`, c => {      this.toggleMarkup({ start: `*` })      return false    })    _.set(keyBindings, `${CtrlKey}-Alt-Right`, c => {      let lvl = this.getHeaderLevel(c)      if (lvl >= 6) { lvl = 5 }      this.setHeaderLine(lvl + 1)      return false    })    _.set(keyBindings, `${CtrlKey}-Alt-Left`, c => {      let lvl = this.getHeaderLevel(c)      if (lvl <= 1) { lvl = 2 }      this.setHeaderLine(lvl - 1)      return false    })    this.cm.setOption('extraKeys', keyBindings)    this.cm.on('inputRead', this.autocomplete)    // Handle cursor movement    this.cm.on('cursorActivity', c => {      this.positionSync(c)      this.scrollSync(c)    })    // Handle special paste    this.cm.on('paste', this.onCmPaste)    // Render initial preview    this.onCmInput(this.$store.get('editor/content'))    this.refresh()    this.$root.$on('editorInsert', opts => {      switch (opts.kind) {        case 'IMAGE':          let img = ``          if (opts.align && opts.align !== '') {            img += `{.align-${opts.align}}`          }          this.insertAtCursor({            content: img          })          break        case 'BINARY':          this.insertAtCursor({            content: `[${opts.text}](${opts.path})`          })          break      }    })    // Handle save conflict    this.$root.$on('saveConflict', () => {      this.toggleModal(`editorModalConflict`)    })    this.$root.$on('overwriteEditorContent', () => {      this.cm.setValue(this.$store.get('editor/content'))    })  },  beforeDestroy() {    this.$root.$off('editorInsert')  }}</script><style lang='scss'>$editor-height: calc(100vh - 112px - 24px);$editor-height-mobile: calc(100vh - 112px - 16px);.editor-markdown {  &-main {    display: flex;    width: 100%;  }  &-editor {    background-color: darken(mc('grey', '900'), 4.5%);    flex: 1 1 50%;    display: block;    height: $editor-height;    position: relative;    @include until($tablet) {      height: $editor-height-mobile;    }  }  &-preview {    flex: 1 1 50%;    background-color: mc('grey', '100');    position: relative;    height: $editor-height;    overflow: hidden;    padding: 1rem;    @at-root .theme--dark & {      background-color: mc('grey', '900');    }    @include until($tablet) {      display: none;    }    &-enter-active, &-leave-active {      transition: max-width .5s ease;      max-width: 50vw;      .editor-code-preview-content {        width: 50vw;        overflow:hidden;      }    }    &-enter, &-leave-to {      max-width: 0;    }    &-content {      height: $editor-height;      overflow-y: scroll;      padding: 0;      width: calc(100% + 17px);      // -ms-overflow-style: none;      // &::-webkit-scrollbar {      //   width: 0px;      //   background: transparent;      // }      @include until($tablet) {        height: $editor-height-mobile;      }      > div {        outline: none;      }      p.line {        overflow-wrap: break-word;      }    }  }  &-toolbar {    background-color: mc('blue', '700');    background-image: linear-gradient(to bottom, mc('blue', '700') 0%, mc('blue','800') 100%);    color: #FFF;    .v-toolbar__content {      padding-left: 64px;      @include until($tablet) {        padding-left: 8px;      }    }  }  &-insert:not(.v-speed-dial--right) {    @include from($tablet) {      left: 50%;      margin-left: -28px;    }  }  &-sidebar {    background-color: mc('grey', '900');    width: 64px;    display: flex;    flex-direction: column;    justify-content: flex-start;    align-items: center;    padding: 24px 0;    @include until($tablet) {      padding: 12px 0;      width: 40px;    }  }  &-sysbar {    padding-left: 0;    &-locale {      background-color: rgba(255,255,255,.25);      display:inline-flex;      padding: 0 12px;      height: 24px;      width: 63px;      justify-content: center;      align-items: center;    }  }  // ==========================================  // Fix FAB revealing under codemirror  // ==========================================  .speed-dial--fixed {    z-index: 8;  }  // ==========================================  // CODE MIRROR  // ==========================================  .CodeMirror {    height: auto;    font-family: 'Roboto Mono', monospace;    font-size: .9rem;    .cm-header-1 {      font-size: 1.5rem;    }    .cm-header-2 {      font-size: 1.25rem;    }    .cm-header-3 {      font-size: 1.15rem;    }    .cm-header-4 {      font-size: 1.1rem;    }    .cm-header-5 {      font-size: 1.05rem;    }    .cm-header-6 {      font-size: 1.025rem;    }  }  .CodeMirror-wrap pre.CodeMirror-line, .CodeMirror-wrap pre.CodeMirror-line-like {    word-break: break-word;  }  .CodeMirror-focused .cm-matchhighlight {    background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAIAAAACCAYAAABytg0kAAAAFklEQVQI12NgYGBgkKzc8x9CMDAwAAAmhwSbidEoSQAAAABJRU5ErkJggg==);    background-position: bottom;    background-repeat: repeat-x;  }  .cm-matchhighlight {    background-color: mc('grey', '800');  }  .CodeMirror-selection-highlight-scrollbar {    background-color: mc('green', '600');  }}// HINT DROPDOWN.CodeMirror-hints {  position: absolute;  z-index: 10;  overflow: hidden;  list-style: none;  margin: 0;  padding: 1px;  box-shadow: 2px 3px 5px rgba(0,0,0,.2);  border: 1px solid mc('grey', '700');  background: mc('grey', '900');  font-family: 'Roboto Mono', monospace;  font-size: .9rem;  max-height: 150px;  overflow-y: auto;  min-width: 250px;  max-width: 80vw;}.CodeMirror-hint {  margin: 0;  padding: 0 4px;  white-space: pre;  color: #FFF;  cursor: pointer;}li.CodeMirror-hint-active {  background: mc('blue', '500');  color: #FFF;}</style>
 |