mirror of
https://github.com/marktext/marktext.git
synced 2025-05-03 22:11:42 +08:00
optimization of cursor, and fix some cursor related issues (#963)
* optimization of cursor, and fix some cursor related issues * remove duplicate codes
This commit is contained in:
parent
c4664546bd
commit
a900b7f2ef
@ -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
|
||||
}
|
||||
|
@ -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()
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -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
|
||||
|
@ -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 })
|
||||
|
43
src/muya/lib/selection/cursor.js
Normal file
43
src/muya/lib/selection/cursor.js
Normal file
@ -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
|
@ -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)
|
||||
|
Loading…
Reference in New Issue
Block a user