diff --git a/src/muya/lib/contentState/arrowCtrl.js b/src/muya/lib/contentState/arrowCtrl.js index ad1deef8..1c797919 100644 --- a/src/muya/lib/contentState/arrowCtrl.js +++ b/src/muya/lib/contentState/arrowCtrl.js @@ -64,10 +64,10 @@ const arrowCtrl = ContentState => { } } - // Just do nothing if the cursor is not collapsed + // Just do nothing if the cursor is not collapsed or `shiftKey` pressed if ( (start.key === end.key && start.offset !== end.offset) || - start.key !== end.key + start.key !== end.key || event.shiftKey ) { return } diff --git a/src/muya/lib/contentState/index.js b/src/muya/lib/contentState/index.js index 469d51f6..0df6aef6 100644 --- a/src/muya/lib/contentState/index.js +++ b/src/muya/lib/contentState/index.js @@ -25,6 +25,7 @@ import inputCtrl from './inputCtrl' import tocCtrl from './tocCtrl' import emojiCtrl from './emojiCtrl' import importMarkdown from '../utils/importMarkdown' +import Cursor from '../selection/cursor' const prototypes = [ tabCtrl, @@ -77,6 +78,9 @@ class ContentState { } set cursor (cursor) { + if (!(cursor instanceof Cursor)) { + cursor = new Cursor(cursor) + } const handler = () => { const { blocks, renderRange, currentCursor } = this this.history.push({ @@ -101,8 +105,6 @@ class ContentState { if (this.historyTimer) clearTimeout(this.historyTimer) this.historyTimer = setTimeout(handler, 2000) } - } else { - cursor.noHistory && delete cursor.noHistory } } @@ -160,7 +162,7 @@ class ContentState { if (isRenderCursor) this.setCursor() } - partialRender () { + partialRender (isRenderCursor = true) { const { blocks, cursor, searchMatches: { matches, index }, selectedBlock } = this const activeBlocks = this.getActiveBlocks() const [ startKey, endKey ] = this.renderRange @@ -174,7 +176,7 @@ class ContentState { this.setNextRenderRange() this.stateRender.collectLabels(blocks) this.stateRender.partialRender(needRenderBlocks, cursor, activeBlocks, matches, startKey, endKey, selectedBlock) - this.setCursor() + if (isRenderCursor) this.setCursor() } /** diff --git a/src/muya/lib/contentState/updateCtrl.js b/src/muya/lib/contentState/updateCtrl.js index 5790adf5..f3b69e9a 100644 --- a/src/muya/lib/contentState/updateCtrl.js +++ b/src/muya/lib/contentState/updateCtrl.js @@ -35,11 +35,11 @@ const updateCtrl = ContentState => { ContentState.prototype.checkNeedRender = function (cursor = this.cursor) { const { labels } = this.stateRender - const { start: cStart, end: cEnd } = cursor - const startBlock = this.getBlock(cStart.key) - const endBlock = this.getBlock(cEnd.key) - const startOffset = cStart.offset - const endOffset = cEnd.offset + const { start: cStart, end: cEnd, anchor, focus } = cursor + const startBlock = this.getBlock(cStart ? cStart.key : anchor.key) + const endBlock = this.getBlock(cEnd ? cEnd.key : focus.key) + const startOffset = cStart ? cStart.offset : anchor.offset + const endOffset = cEnd ? cEnd.offset : focus.offset for (const token of tokenizer(startBlock.text, undefined, undefined, labels)) { if (token.type === 'text') continue diff --git a/src/muya/lib/eventHandler/keyboard.js b/src/muya/lib/eventHandler/keyboard.js index 06bc4b0a..a1ab9b6c 100644 --- a/src/muya/lib/eventHandler/keyboard.js +++ b/src/muya/lib/eventHandler/keyboard.js @@ -198,23 +198,23 @@ class Keyboard { emojiNode }) } - // is show format float box? - const { start, end } = selection.getCursorRange() - if (!start || !end) { + + const { anchor, focus, start, end } = selection.getCursorRange() + if (!anchor || !focus) { return } if ( !this.isComposed ) { - const { start: oldStart, end: oldEnd } = contentState.cursor + const { anchor: oldAnchor, focus: oldFocus } = contentState.cursor if ( - start.key !== oldStart.key || - start.offset !== oldStart.offset || - end.key !== oldEnd.key || - end.offset !== oldEnd.offset + anchor.key !== oldAnchor.key || + anchor.offset !== oldAnchor.offset || + focus.key !== oldFocus.key || + focus.offset !== oldFocus.offset ) { const needRender = contentState.checkNeedRender(contentState.cursor) || contentState.checkNeedRender({ start, end }) - contentState.cursor = { start, end } + contentState.cursor = { anchor, focus } if (needRender) { return contentState.partialRender() } @@ -227,8 +227,8 @@ class Keyboard { eventCenter.dispatch('muya-image-picker', { list: [] }) } - const block = contentState.getBlock(start.key) - if (start.key === end.key && start.offset !== end.offset && block.functionType !== 'codeLine') { + const block = contentState.getBlock(anchor.key) + if (anchor.key === focus.key && anchor.offset !== focus.offset && block.functionType !== 'codeLine') { const reference = contentState.getPositionReference() const { formats } = contentState.selectionFormats() eventCenter.dispatch('muya-format-picker', { reference, formats }) diff --git a/src/muya/lib/selection/cursor.js b/src/muya/lib/selection/cursor.js new file mode 100644 index 00000000..589d2548 --- /dev/null +++ b/src/muya/lib/selection/cursor.js @@ -0,0 +1,43 @@ +import { compareParagraphsOrder } from './dom' + +class Cursor { + // You need to provide either `anchor`&&`focus` or `start`&&`end` or all. + constructor ({ anchor, focus, start, end, noHistory = false }) { + if (anchor && focus && start && end) { + this.anchor = anchor + this.focus = focus + this.start = start + this.end = end + } else if (anchor && focus) { + this.anchor = anchor + this.focus = focus + if (anchor.key === focus.key) { + if (anchor.offset <= focus.offset) { + this.start = this.anchor + this.end = this.focus + } else { + this.start = this.focus + this.end = this.anchor + } + } else { + const anchorParagraph = document.querySelector(`#${anchor.key}`) + const focusParagraph = document.querySelector(`#${focus.key}`) + const order = compareParagraphsOrder(anchorParagraph, focusParagraph) + + if (order) { + this.start = this.anchor + this.end = this.focus + } else { + this.start = this.focus + this.end = this.anchor + } + } + } else { + this.anchor = this.start = start + this.focus = this.end = end + } + this.noHistory = noHistory + } +} + +export default Cursor diff --git a/src/muya/lib/selection/index.js b/src/muya/lib/selection/index.js index 57082120..70aa3dfe 100644 --- a/src/muya/lib/selection/index.js +++ b/src/muya/lib/selection/index.js @@ -2,19 +2,18 @@ * This file is copy from [medium-editor](https://github.com/yabwe/medium-editor) * and customize for specialized use. */ +import Cursor from './cursor' +import { CLASS_OR_ID } from '../config' import { isBlockContainer, traverseUp, getFirstSelectableLeafNode, getClosestBlockContainer, getCursorPositionWithinMarkedText, - compareParagraphsOrder, findNearestParagraph, getTextContent } from './dom' -import { CLASS_OR_ID } from '../config' - const filterOnlyParentElements = node => { return isBlockContainer(node) ? NodeFilter.FILTER_ACCEPT : NodeFilter.FILTER_SKIP } @@ -353,6 +352,11 @@ class Selection { return range } + setFocus (focusNode, focusOffset) { + const selection = this.doc.getSelection() + selection.extend(focusNode, focusOffset) + } + /** * Clear the current highlighted selection and set the caret to the start or the end of that prior selection, defaults to end. * @@ -404,9 +408,9 @@ class Selection { } setCursorRange (cursorRange) { - const { start, end } = cursorRange - const startParagraph = document.querySelector(`#${start.key}`) - const endParagraph = document.querySelector(`#${end.key}`) + const { anchor, focus } = cursorRange + const anchorParagraph = document.querySelector(`#${anchor.key}`) + const focusParagraph = document.querySelector(`#${focus.key}`) const getNodeAndOffset = (node, offset) => { if (node.nodeType === 3) { return { @@ -430,11 +434,14 @@ class Selection { return { node, offset } } - let { node: startNode, offset: startOffset } = getNodeAndOffset(startParagraph, start.offset) - let { node: endNode, offset: endOffset } = getNodeAndOffset(endParagraph, end.offset) - startOffset = Math.min(startOffset, startNode.textContent.length) - endOffset = Math.min(endOffset, endNode.textContent.length) - this.select(startNode, startOffset, endNode, endOffset) + let { node: anchorNode, offset: anchorOffset } = getNodeAndOffset(anchorParagraph, anchor.offset) + let { node: focusNode, offset: focusOffset } = getNodeAndOffset(focusParagraph, focus.offset) + anchorOffset = Math.min(anchorOffset, anchorNode.textContent.length) + focusOffset = Math.min(focusOffset, focusNode.textContent.length) + // First set the anchor node and anchor offeet, make it collapsed + this.select(anchorNode, anchorOffset) + // Secondly, set the focus node and focus offset. + this.setFocus(focusNode, focusOffset) } isValidCursorNode (node) { @@ -465,14 +472,16 @@ class Selection { } else if (!isAnchorValid && !isFocusValid) { const editor = document.querySelector('#ag-editor-id').parentNode editor.blur() - return { + return new Cursor({ start: null, - end: null - } + end: null, + anchor: null, + focus: null + }) } - const startParagraph = findNearestParagraph(anchorNode) - const endParagraph = findNearestParagraph(focusNode) + const anchorParagraph = findNearestParagraph(anchorNode) + const focusParagraph = findNearestParagraph(focusNode) const getOffsetOfParagraph = (node, paragraph) => { let offset = 0 @@ -491,35 +500,11 @@ class Selection { : offset + getOffsetOfParagraph(node.parentNode, paragraph) } - let result = null - - if (startParagraph === endParagraph) { - const key = startParagraph.id - const offset1 = getOffsetOfParagraph(anchorNode, startParagraph) + anchorOffset - const offset2 = getOffsetOfParagraph(focusNode, endParagraph) + focusOffset - result = { - start: { key, offset: Math.min(offset1, offset2) }, - end: { key, offset: Math.max(offset1, offset2) } - } - } else { - const order = compareParagraphsOrder(startParagraph, endParagraph) - - const rawCursor = { - start: { - key: startParagraph.id, - offset: getOffsetOfParagraph(anchorNode, startParagraph) + anchorOffset - }, - end: { - key: endParagraph.id, - offset: getOffsetOfParagraph(focusNode, endParagraph) + focusOffset - } - } - if (order) { - result = rawCursor - } else { - result = { start: rawCursor.end, end: rawCursor.start } - } - } + const aOffset = getOffsetOfParagraph(anchorNode, anchorParagraph) + anchorOffset + const fOffset = getOffsetOfParagraph(focusNode, focusParagraph) + focusOffset + const anchor = { key: anchorParagraph.id, offset: aOffset } + const focus = { key: focusParagraph.id, offset: fOffset } + const result = new Cursor({ anchor, focus }) if (needFix) { this.setCursorRange(result)