diff --git a/.github/TODOLIST.md b/.github/TODOLIST.md index 7302d426..aa6ac527 100644 --- a/.github/TODOLIST.md +++ b/.github/TODOLIST.md @@ -68,6 +68,8 @@ - [ ] Refactor data structure of contentState, add copyBlock... +- [ ] Refactor the History data structure + ##### Documents - [ ] Manual diff --git a/src/editor/contentState/arrowCtrl.js b/src/editor/contentState/arrowCtrl.js index 5e18f933..6ea4cb88 100644 --- a/src/editor/contentState/arrowCtrl.js +++ b/src/editor/contentState/arrowCtrl.js @@ -3,60 +3,7 @@ import { isCursorAtFirstLine, isCursorAtLastLine, isCursorAtBegin, isCursorAtEnd import { findNearestParagraph } from '../utils/domManipulate' import selection from '../selection' -const HAS_TEXT_BLOCK_REG = /^(h\d|span|th|td|hr|pre)/ - const arrowCtrl = ContentState => { - ContentState.prototype.firstInDescendant = function (block) { - const children = block.children - if (block.children.length === 0 && HAS_TEXT_BLOCK_REG.test(block.type)) { - return block - } else if (children.length) { - if (children[0].type === 'input' || (children[0].type === 'div' && children[0].editable === false)) { // handle task item - return this.firstInDescendant(children[1]) - } else { - return this.firstInDescendant(children[0]) - } - } - } - - ContentState.prototype.lastInDescendant = function (block) { - if (block.children.length === 0 && HAS_TEXT_BLOCK_REG.test(block.type)) { - return block - } else if (block.children.length) { - const children = block.children - let lastChild = children[children.length - 1] - while (lastChild.editable === false) { - lastChild = this.getPreSibling(lastChild) - } - return this.lastInDescendant(lastChild) - } - } - - ContentState.prototype.findPreBlockInLocation = function (block) { - const parent = this.getParent(block) - const preBlock = this.getPreSibling(block) - if (block.preSibling && preBlock.type !== 'input' && preBlock.type !== 'div' && preBlock.editable !== false) { // handle task item and table - return this.lastInDescendant(preBlock) - } else if (parent) { - return this.findPreBlockInLocation(parent) - } else { - return null - } - } - - ContentState.prototype.findNextBlockInLocation = function (block) { - const parent = this.getParent(block) - const nextBlock = this.getNextSibling(block) - - if (block.nextSibling && nextBlock.editable !== false) { - return this.firstInDescendant(nextBlock) - } else if (parent) { - return this.findNextBlockInLocation(parent) - } else { - return null - } - } - ContentState.prototype.findNextRowCell = function (cell) { if (!/th|td/.test(cell.type)) throw new Error(`block with type ${cell && cell.type} is not a table cell`) const row = this.getParent(cell) @@ -126,6 +73,7 @@ const arrowCtrl = ContentState => { if (show && (event.key === EVENT_KEYS.ArrowUp || event.key === EVENT_KEYS.ArrowDown)) { event.preventDefault() + event.stopPropagation() switch (event.key) { case EVENT_KEYS.ArrowDown: if (index < list.length - 1) { @@ -147,6 +95,7 @@ const arrowCtrl = ContentState => { const anchorBlock = block.functionType === 'html' ? this.getParent(this.getParent(block)) : block let activeBlock event.preventDefault() + event.stopPropagation() switch (event.key) { case EVENT_KEYS.ArrowLeft: // fallthrough case EVENT_KEYS.ArrowUp: @@ -236,6 +185,7 @@ const arrowCtrl = ContentState => { if (activeBlock) { event.preventDefault() + event.stopPropagation() const offset = activeBlock.type === 'p' ? 0 : (event.key === EVENT_KEYS.ArrowUp ? activeBlock.text.length : 0) const key = activeBlock.type === 'p' ? activeBlock.children[0].key : activeBlock.key this.cursor = { @@ -258,6 +208,7 @@ const arrowCtrl = ContentState => { (preBlock && preBlock.type === 'pre' && event.key === EVENT_KEYS.ArrowLeft && left === 0) ) { event.preventDefault() + event.stopPropagation() const key = preBlock.key const offset = 0 this.cursor = { @@ -274,6 +225,7 @@ const arrowCtrl = ContentState => { (nextBlock && nextBlock.type === 'pre' && event.key === EVENT_KEYS.ArrowRight && right === 0) ) { event.preventDefault() + event.stopPropagation() const key = nextBlock.key const offset = 0 this.cursor = { @@ -289,6 +241,7 @@ const arrowCtrl = ContentState => { (event.key === EVENT_KEYS.ArrowLeft && start.offset === 0) ) { event.preventDefault() + event.stopPropagation() if (!preBlock) return const key = preBlock.key const offset = preBlock.text.length @@ -302,6 +255,7 @@ const arrowCtrl = ContentState => { (event.key === EVENT_KEYS.ArrowRight && start.offset === block.text.length) ) { event.preventDefault() + event.stopPropagation() let key if (nextBlock) { key = nextBlock.key diff --git a/src/editor/contentState/codeBlockCtrl.js b/src/editor/contentState/codeBlockCtrl.js index d7e3c1ed..ffe23e12 100644 --- a/src/editor/contentState/codeBlockCtrl.js +++ b/src/editor/contentState/codeBlockCtrl.js @@ -2,7 +2,7 @@ import createDOMPurify from 'dompurify' import codeMirror, { setMode, setCursorAtLastLine } from '../codeMirror' import { createInputInCodeBlock } from '../utils/domManipulate' import { escapeInBlockHtml } from '../utils' -import { codeMirrorConfig, CLASS_OR_ID, BLOCK_TYPE7, DOMPURIFY_CONFIG } from '../config' +import { codeMirrorConfig, BLOCK_TYPE7, DOMPURIFY_CONFIG } from '../config' const DOMPurify = createDOMPurify(window) @@ -72,9 +72,10 @@ const codeBlockCtrl = ContentState => { return !!match } - ContentState.prototype.pre2CodeMirror = function (isRenderCursor) { + ContentState.prototype.pre2CodeMirror = function (isRenderCursor, emptyPres) { + if (!emptyPres.length) return const { eventCenter } = this - const selector = `pre.${CLASS_OR_ID['AG_CODE_BLOCK']}, pre.${CLASS_OR_ID['AG_HTML_BLOCK']}` + const selector = emptyPres.map(key => `pre#${key}`).join(', ') const pres = document.querySelectorAll(selector) Array.from(pres).forEach(pre => { const id = pre.id diff --git a/src/editor/contentState/history.js b/src/editor/contentState/history.js index 3b0bcef5..9676a9c9 100644 --- a/src/editor/contentState/history.js +++ b/src/editor/contentState/history.js @@ -48,8 +48,13 @@ export class History { } } push (state) { + const UNDO_DEPTH = 20 this.stack.splice(this.index + 1) this.stack.push(deepCopy(state)) + if (this.stack.length > UNDO_DEPTH) { + this.stack.shift() + this.index = this.index - 1 + } this.index = this.index + 1 } } diff --git a/src/editor/contentState/index.js b/src/editor/contentState/index.js index 8e17fe0f..eadd7915 100644 --- a/src/editor/contentState/index.js +++ b/src/editor/contentState/index.js @@ -1,3 +1,4 @@ +import { setCursorAtLastLine } from '../codeMirror' import { getUniqueId } from '../utils' import selection from '../selection' import StateRender from '../parser/StateRender' @@ -42,17 +43,7 @@ const prototypes = [ importMarkdown ] -// deep first search -const convertBlocksToArray = blocks => { - const result = [] - blocks.forEach(block => { - result.push(block) - if (block.children.length) { - result.push(...convertBlocksToArray(block.children)) - } - }) - return result -} +const HAS_TEXT_BLOCK_REG = /^(h\d|span|th|td|hr|pre)/ // use to cache the keys which you don't want to remove. const exemption = new Set() @@ -91,7 +82,20 @@ class ContentState { } setCursor () { - selection.setCursorRange(this.cursor) + const { start: { key } } = this.cursor + const block = this.getBlock(key) + if (block.type !== 'pre') { + selection.setCursorRange(this.cursor) + } else { + const cm = this.codeBlocks.get(key) + const { pos } = block + if (pos) { + cm.focus() + cm.setCursor(pos) + } else { + setCursorAtLastLine(cm) + } + } } render (isRenderCursor = true) { @@ -100,12 +104,19 @@ class ContentState { matches.forEach((m, i) => { m.active = i === index }) - this.stateRender.render(blocks, cursor, activeBlocks, matches) + const emptyPres = this.stateRender.render(blocks, cursor, activeBlocks, matches) + this.pre2CodeMirror(isRenderCursor, emptyPres) if (isRenderCursor) this.setCursor() - this.pre2CodeMirror(isRenderCursor) this.renderMath() } + partialRender (block) { + const { cursor } = this + this.stateRender.partialRender(block, cursor) + this.setCursor() + this.renderMath(block) + } + /** * A block in Aganippe present a paragraph(block syntax in GFM) or a line in paragraph. * a line block must in a `p block` and `p block`'s children must be line blocks. @@ -137,11 +148,11 @@ class ContentState { // getBlocks getBlocks () { - for (const [ key, cm ] of this.codeBlocks.entries()) { - const value = cm.getValue() - const block = this.getBlock(key) - if (block) block.text = value - } + // for (const [ key, cm ] of this.codeBlocks.entries()) { + // const value = cm.getValue() + // const block = this.getBlock(key) + // if (block) block.text = value + // } return this.blocks } @@ -149,12 +160,22 @@ class ContentState { return this.cursor } - getArrayBlocks () { - return convertBlocksToArray(this.blocks) - } - getBlock (key) { - return this.getArrayBlocks().filter(block => block.key === key)[0] + let result = null + const travel = blocks => { + for (const block of blocks) { + if (block.key === key) { + result = block + return + } + const { children } = block + if (children.length) { + travel(children) + } + } + } + travel(this.blocks) + return result } getParent (block) { @@ -183,15 +204,6 @@ class ContentState { return block.nextSibling ? this.getBlock(block.nextSibling) : null } - getFirstBlock () { - const arrayBlocks = this.getArrayBlocks() - if (arrayBlocks.length) { - return arrayBlocks[0] - } else { - throw new Error('article need at least has one paragraph') - } - } - /** * if target is descendant of parent return true, else return false * @param {[type]} parent [description] @@ -459,39 +471,76 @@ class ContentState { return null } - getLastBlock () { - const arrayBlocks = this.getArrayBlocks() - const len = arrayBlocks.length - if (len) { - return arrayBlocks[len - 1] - } else { - throw new Error('article need at least has one paragraph') + firstInDescendant (block) { + const children = block.children + if (block.children.length === 0 && HAS_TEXT_BLOCK_REG.test(block.type)) { + return block + } else if (children.length) { + if (children[0].type === 'input' || (children[0].type === 'div' && children[0].editable === false)) { // handle task item + return this.firstInDescendant(children[1]) + } else { + return this.firstInDescendant(children[0]) + } } } - wordCount () { - const blocks = this.getBlocks() - let paragraph = blocks.length + lastInDescendant (block) { + if (block.children.length === 0 && HAS_TEXT_BLOCK_REG.test(block.type)) { + return block + } else if (block.children.length) { + const children = block.children + let lastChild = children[children.length - 1] + while (lastChild.editable === false) { + lastChild = this.getPreSibling(lastChild) + } + return this.lastInDescendant(lastChild) + } + } + + findPreBlockInLocation (block) { + const parent = this.getParent(block) + const preBlock = this.getPreSibling(block) + if (block.preSibling && preBlock.type !== 'input' && preBlock.type !== 'div' && preBlock.editable !== false) { // handle task item and table + return this.lastInDescendant(preBlock) + } else if (parent) { + return this.findPreBlockInLocation(parent) + } else { + return null + } + } + + findNextBlockInLocation (block) { + const parent = this.getParent(block) + const nextBlock = this.getNextSibling(block) + + if (block.nextSibling && nextBlock.editable !== false) { + return this.firstInDescendant(nextBlock) + } else if (parent) { + return this.findNextBlockInLocation(parent) + } else { + return null + } + } + + getLastBlock () { + const { blocks } = this + const len = blocks.length + return this.lastInDescendant(blocks[len - 1]) + } + + wordCount (markdown) { + let paragraph = this.blocks.length let word = 0 let character = 0 let all = 0 - const travel = block => { - if (block.text) { - const text = block.text - const removedChinese = text.replace(/[\u4e00-\u9fa5]/g, '') - const tokens = removedChinese.split(/[\s\n]+/).filter(t => t) - const chineseWordLength = text.length - removedChinese.length - word += chineseWordLength + tokens.length - character += tokens.reduce((acc, t) => acc + t.length, 0) + chineseWordLength - all += text.length - } - if (block.children.length) { - block.children.forEach(child => travel(child)) - } - } + const removedChinese = markdown.replace(/[\u4e00-\u9fa5]/g, '') + const tokens = removedChinese.split(/[\s\n]+/).filter(t => t) + const chineseWordLength = markdown.length - removedChinese.length + word += chineseWordLength + tokens.length + character += tokens.reduce((acc, t) => acc + t.length, 0) + chineseWordLength + all += markdown.length - blocks.forEach(block => travel(block)) return { word, paragraph, character, all } } diff --git a/src/editor/contentState/mathCtrl.js b/src/editor/contentState/mathCtrl.js index fde303b6..19681e41 100644 --- a/src/editor/contentState/mathCtrl.js +++ b/src/editor/contentState/mathCtrl.js @@ -4,8 +4,9 @@ import { CLASS_OR_ID } from '../config' import 'katex/dist/katex.min.css' const mathCtrl = ContentState => { - ContentState.prototype.renderMath = function () { - const mathEles = document.querySelectorAll(`.${CLASS_OR_ID['AG_MATH_RENDER']}`) + ContentState.prototype.renderMath = function (block) { + const selector = block ? `#${block.key} .${CLASS_OR_ID['AG_MATH_RENDER']}` : `.${CLASS_OR_ID['AG_MATH_RENDER']}` + const mathEles = document.querySelectorAll(selector) const { loadMathMap } = this for (const math of mathEles) { const content = math.getAttribute('data-math') diff --git a/src/editor/contentState/updateCtrl.js b/src/editor/contentState/updateCtrl.js index 4eda6105..9f52fbaa 100644 --- a/src/editor/contentState/updateCtrl.js +++ b/src/editor/contentState/updateCtrl.js @@ -1,8 +1,8 @@ import selection from '../selection' import { tokenizer } from '../parser/parse' -import { conflict } from '../utils' +import { conflict, isMetaKey } from '../utils' import { getTextContent } from '../utils/domManipulate' -import { CLASS_OR_ID } from '../config' +import { CLASS_OR_ID, EVENT_KEYS } from '../config' const INLINE_UPDATE_FREGMENTS = [ '^([*+-]\\s)', // Bullet list @@ -64,8 +64,7 @@ const updateCtrl = ContentState => { switch (true) { case (hr && new Set(hr.split('').filter(i => /\S/.test(i))).size === 1): - this.updateHr(block, hr) - return true + return this.updateHr(block, hr) case !!bullet: this.updateList(block, 'bullet', bullet) @@ -81,8 +80,7 @@ const updateCtrl = ContentState => { return true case !!header: - this.updateHeader(block, header, text) - return true + return this.updateHeader(block, header, text) case !!blockquote: this.updateBlockQuote(block) @@ -96,11 +94,15 @@ const updateCtrl = ContentState => { // thematic break ContentState.prototype.updateHr = function (block, marker) { - block.type = 'hr' - block.text = marker - block.children.length = 0 - const { key } = block - this.cursor.start.key = this.cursor.end.key = key + if (block.type !== 'hr') { + block.type = 'hr' + block.text = marker + block.children.length = 0 + const { key } = block + this.cursor.start.key = this.cursor.end.key = key + return true + } + return false } ContentState.prototype.updateList = function (block, type, marker = '') { @@ -230,8 +232,10 @@ const updateCtrl = ContentState => { block.type = newType block.text = text block.children.length = 0 + this.cursor.start.key = this.cursor.end.key = block.key + return true } - this.cursor.start.key = this.cursor.end.key = block.key + return false } ContentState.prototype.updateBlockQuote = function (block) { @@ -272,12 +276,16 @@ const updateCtrl = ContentState => { const { start, end } = selection.getCursorRange() const key = start.key const block = this.getBlock(key) + // bugfix: #67 problem 1 if (block && block.icon) return event.preventDefault() + if (isMetaKey(event)) { + return + } const { start: oldStart, end: oldEnd } = this.cursor - if (event.type === 'keyup' && (event.key === 'ArrowUp' || event.key === 'ArrowDown') && floatBox.show) { + if (event.type === 'keyup' && (event.key === EVENT_KEYS.ArrowUp || event.key === EVENT_KEYS.ArrowDown) && floatBox.show) { return } @@ -322,7 +330,6 @@ const updateCtrl = ContentState => { end.offset !== oldEnd.offset ) { this.cursor = { start, end } - // this.render() } return } @@ -331,6 +338,7 @@ const updateCtrl = ContentState => { const paragraph = document.querySelector(`#${key}`) let text = getTextContent(paragraph, [ CLASS_OR_ID['AG_MATH_RENDER'] ]) let needRender = false + let isPartialRender = false if (event.type === 'click' && block.type === 'figure' && block.functionType === 'table') { // first cell in thead const cursorBlock = block.children[1].children[0].children[0].children[0] @@ -342,6 +350,7 @@ const updateCtrl = ContentState => { } return this.render() } + // remove temp block which generated by operation on code block if (block && block.key !== oldKey) { let oldBlock = this.getBlock(oldKey) @@ -362,7 +371,7 @@ const updateCtrl = ContentState => { if (block && block.type === 'pre') { if (block.key !== oldKey) { this.cursor = lastCursor = { start, end } - this.render() + if (event.type === 'click') this.render() } return } @@ -405,12 +414,16 @@ const updateCtrl = ContentState => { needRender = true } + if (key === oldKey) { + isPartialRender = true + } + this.cursor = lastCursor = { start, end } const checkMarkedUpdate = this.checkNeedRender(block) const checkInlineUpdate = this.isCollapse() && this.checkInlineUpdate(block) if (checkMarkedUpdate || checkInlineUpdate || needRender) { - this.render() + isPartialRender && !checkInlineUpdate ? this.partialRender(block) : this.render() } } } diff --git a/src/editor/floatBox/index.css b/src/editor/floatBox/index.css index 0f7e0ba6..0b0d98f4 100644 --- a/src/editor/floatBox/index.css +++ b/src/editor/floatBox/index.css @@ -1,7 +1,7 @@ .ag-float-box { position: absolute; - left: -10000px; - top: -10000px; + left: -1000px; + top: -1000px; opacity: 0; max-height: 168px; min-width: 130px; @@ -12,7 +12,7 @@ border-radius: 4px; box-shadow: 0 2px 12px 0 rgba(0, 0, 0, .1); list-style: none; - transition: opacity .4s ease-in; + transition: opacity .15s ease-in; overflow: auto; background: #fff; z-index: 10000; diff --git a/src/editor/index.js b/src/editor/index.js index d5d0a203..cd922c4e 100644 --- a/src/editor/index.js +++ b/src/editor/index.js @@ -110,8 +110,8 @@ class Aganippe { dispatchChange () { const { eventCenter } = this - const markdown = this.getMarkdown() - const wordCount = this.getWordCount() + const markdown = this.markdown = this.getMarkdown() + const wordCount = this.getWordCount(markdown) const cursor = this.getCursor() eventCenter.dispatch('change', markdown, wordCount, cursor) } @@ -435,13 +435,12 @@ class Aganippe { } exportUnstylishHtml () { - const blocks = this.contentState.getBlocks() - const markdown = new ExportMarkdown(blocks).generate() + const { markdown } = this return exportHtml(markdown) } - getWordCount () { - return this.contentState.wordCount() + getWordCount (markdown) { + return this.contentState.wordCount(markdown) } getCursor () { diff --git a/src/editor/parser/StateRender.js b/src/editor/parser/StateRender.js index 8ed1f958..1903b60f 100644 --- a/src/editor/parser/StateRender.js +++ b/src/editor/parser/StateRender.js @@ -1,5 +1,5 @@ import virtualize from 'snabbdom-virtualize/strings' -import { LOWERCASE_TAGS, CLASS_OR_ID, IMAGE_EXT_REG } from '../config' +import { CLASS_OR_ID, IMAGE_EXT_REG } from '../config' import { conflict, isLengthEven, union, isEven, getIdWithoutSet, loadImage, getImageSrc } from '../utils' import { insertAfter, operateClassName } from '../utils/domManipulate' import { tokenizer } from './parse' @@ -19,6 +19,7 @@ class StateRender { constructor (eventCenter) { this.eventCenter = eventCenter this.loadImageMap = new Map() + this.tokenCache = new Map() this.container = null } @@ -52,11 +53,16 @@ class StateRender { } /** - * [render]: 2 steps: - * render vdom + * [render All blocks] + * @param {[Array]} blocks [description] + * @param {[Object]} cursor [description] + * @param {[Object]} activeBlocks [description] + * @param {[Array]} matches [description] + * @return {[undefined]} [description] */ render (blocks, cursor, activeBlocks, matches) { - const selector = `${LOWERCASE_TAGS.div}#${CLASS_OR_ID['AG_EDITOR_ID']}` + const selector = `div#${CLASS_OR_ID['AG_EDITOR_ID']}` + const emptyPres = [] const renderBlock = block => { const type = block.type === 'hr' ? 'p' : block.type @@ -145,7 +151,14 @@ class StateRender { const { text } = block let children = '' if (text) { - children = tokenizer(text, highlights).reduce((acc, token) => [...acc, ...this[token.type](h, cursor, block, token)], []) + let tokens = null + if (highlights.length === 0 && this.tokenCache.has(text)) { + tokens = this.tokenCache.get(text) + } else { + tokens = tokenizer(text, highlights) + this.tokenCache.set(text, tokens) + } + children = tokens.reduce((acc, token) => [...acc, ...this[token.type](h, cursor, block, token)], []) } if (/th|td/.test(block.type)) { @@ -179,12 +192,6 @@ class StateRender { Object.assign(data.dataset, { role: block.type }) } - if (block.type === 'pre') { - if (block.lang) Object.assign(data.dataset, { lang: block.lang }) - blockSelector += block.functionType === 'code' ? `.${CLASS_OR_ID['AG_CODE_BLOCK']}` : `.${CLASS_OR_ID['AG_HTML_BLOCK']}` - children = '' - } - if (block.type === 'input') { const { checked, type, key } = block Object.assign(data.attrs, { type: 'checkbox' }) @@ -200,6 +207,19 @@ class StateRender { blockSelector += `.${CLASS_OR_ID['AG_TEMP']}` } + if (block.type === 'pre') { + const { key, lang } = block + const pre = document.querySelector(`pre#${key}`) + if (pre) { + operateClassName(pre, isActive ? 'add' : 'remove', CLASS_OR_ID['AG_ACTIVE']) + return toVNode(pre) + } + if (lang) Object.assign(data.dataset, { lang }) + blockSelector += block.functionType === 'code' ? `.${CLASS_OR_ID['AG_CODE_BLOCK']}` : `.${CLASS_OR_ID['AG_HTML_BLOCK']}` + children = '' + emptyPres.push(key) + } + return h(blockSelector, data, children) } } @@ -213,6 +233,48 @@ class StateRender { const oldVdom = toVNode(rootDom) patch(oldVdom, newVdom) + return emptyPres + } + + partialRender (block, cursor) { + const { key, text } = block + const type = block.type === 'hr' ? 'p' : block.type + let selector = `${type}#${key}` + const oldDom = document.querySelector(selector) + const oldVdom = toVNode(oldDom) + + selector += `.${CLASS_OR_ID['AG_PARAGRAPH']}.${CLASS_OR_ID['AG_ACTIVE']}` + if (type === 'span') { + selector += `.${CLASS_OR_ID['AG_LINE']}` + } + + const data = { + attrs: {}, + dataset: {} + } + + let children = '' + if (text) { + children = tokenizer(text, []).reduce((acc, token) => [...acc, ...this[token.type](h, cursor, block, token)], []) + } + + if (/th|td/.test(type)) { + const { align } = block + if (align) { + Object.assign(data.attrs, { style: `text-align:${align}` }) + } + } + + if (/^h\d$/.test(block.type)) { + Object.assign(data.dataset, { head: block.type }) + } + + if (/^h/.test(block.type)) { // h\d or hr + Object.assign(data.dataset, { role: block.type }) + } + + const newVdom = h(selector, data, children) + patch(oldVdom, newVdom) } hr (h, cursor, block, token, outerClass) { diff --git a/src/editor/utils/importMarkdown.js b/src/editor/utils/importMarkdown.js index 293f66a2..ee7de32b 100644 --- a/src/editor/utils/importMarkdown.js +++ b/src/editor/utils/importMarkdown.js @@ -335,22 +335,27 @@ const importRegister = ContentState => { ContentState.prototype.importCursor = function (cursor) { // set cursor - if (cursor) { - const blocks = this.getArrayBlocks() + const travel = blocks => { for (const block of blocks) { - const { text, key } = block + const { key, text, children } = block if (text) { - const offset = block.text.indexOf(CURSOR_DNA) + const offset = text.indexOf(CURSOR_DNA) if (offset > -1) { - // remove the CURSOR_DNA in the block text block.text = text.substring(0, offset) + text.substring(offset + CURSOR_DNA.length) this.cursor = { start: { key, offset }, end: { key, offset } } + return } } + if (children.length) { + travel(children) + } } + } + if (cursor) { + travel(this.blocks) } else { const lastBlock = this.getLastBlock() const key = lastBlock.key diff --git a/src/renderer/components/sourceCode.vue b/src/renderer/components/sourceCode.vue index d680af39..f060a43f 100644 --- a/src/renderer/components/sourceCode.vue +++ b/src/renderer/components/sourceCode.vue @@ -43,7 +43,7 @@ created () { this.$nextTick(() => { - const { markdown = '', theme } = this + const { markdown = '', theme, cursor } = this const container = this.$refs.sourceCode const codeMirrorConfig = { value: markdown, @@ -60,11 +60,16 @@ } } if (theme === 'dark') codeMirrorConfig.theme = 'railscasts' - this.editor = codeMirror(container, codeMirrorConfig) + const editor = this.editor = codeMirror(container, codeMirrorConfig) bus.$on('file-loaded', this.setMarkdown) bus.$on('dotu-select', this.handleSelectDoutu) this.listenChange() + if (cursor) { + editor.setCursor(cursor) + } else { + setCursorAtLastLine(editor) + } }) }, beforeDestroy () {