mirror of
https://github.com/ianstormtaylor/slate.git
synced 2025-08-16 20:24:01 +02:00
Reconcile dom node (#2801)
* Add reconcileDOMNode command fixes set-text-from-dom-node * Fix linting and cleanup unused files * Removed unnecessary console log
This commit is contained in:
@@ -1,4 +1,3 @@
|
|||||||
import getSelectionFromDom from '../../utils/get-selection-from-dom'
|
|
||||||
import ElementSnapshot from './element-snapshot'
|
import ElementSnapshot from './element-snapshot'
|
||||||
import SELECTORS from '../../constants/selectors'
|
import SELECTORS from '../../constants/selectors'
|
||||||
|
|
||||||
@@ -51,7 +50,7 @@ export default class DomSnapshot {
|
|||||||
}
|
}
|
||||||
|
|
||||||
this.snapshot = new ElementSnapshot(elements)
|
this.snapshot = new ElementSnapshot(elements)
|
||||||
this.selection = getSelectionFromDom(window, editor, domSelection)
|
this.selection = editor.findSelection(domSelection)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@@ -5,7 +5,6 @@ import pick from 'lodash/pick'
|
|||||||
import { ANDROID_API_VERSION } from 'slate-dev-environment'
|
import { ANDROID_API_VERSION } from 'slate-dev-environment'
|
||||||
import fixSelectionInZeroWidthBlock from './fix-selection-in-zero-width-block'
|
import fixSelectionInZeroWidthBlock from './fix-selection-in-zero-width-block'
|
||||||
import getSelectionFromDom from '../../utils/get-selection-from-dom'
|
import getSelectionFromDom from '../../utils/get-selection-from-dom'
|
||||||
import setTextFromDomNode from '../../utils/set-text-from-dom-node'
|
|
||||||
import isInputDataEnter from './is-input-data-enter'
|
import isInputDataEnter from './is-input-data-enter'
|
||||||
import isInputDataLastChar from './is-input-data-last-char'
|
import isInputDataLastChar from './is-input-data-last-char'
|
||||||
import DomSnapshot from './dom-snapshot'
|
import DomSnapshot from './dom-snapshot'
|
||||||
@@ -133,10 +132,10 @@ function AndroidPlugin() {
|
|||||||
function reconcile(window, editor, { from }) {
|
function reconcile(window, editor, { from }) {
|
||||||
debug.reconcile({ from })
|
debug.reconcile({ from })
|
||||||
const domSelection = window.getSelection()
|
const domSelection = window.getSelection()
|
||||||
const selection = getSelectionFromDom(window, editor, domSelection)
|
const selection = editor.findSelection(domSelection)
|
||||||
|
|
||||||
nodes.forEach(node => {
|
nodes.forEach(node => {
|
||||||
setTextFromDomNode(window, editor, node)
|
editor.reconcileDOMNode(node)
|
||||||
})
|
})
|
||||||
|
|
||||||
editor.select(selection)
|
editor.select(selection)
|
||||||
|
@@ -8,7 +8,6 @@ import { IS_IOS, IS_IE, IS_EDGE } from 'slate-dev-environment'
|
|||||||
import cloneFragment from '../../utils/clone-fragment'
|
import cloneFragment from '../../utils/clone-fragment'
|
||||||
import getEventTransfer from '../../utils/get-event-transfer'
|
import getEventTransfer from '../../utils/get-event-transfer'
|
||||||
import setEventTransfer from '../../utils/set-event-transfer'
|
import setEventTransfer from '../../utils/set-event-transfer'
|
||||||
import setTextFromDomNode from '../../utils/set-text-from-dom-node'
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Debug.
|
* Debug.
|
||||||
@@ -432,7 +431,7 @@ function AfterPlugin(options = {}) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const { anchorNode } = domSelection
|
const { anchorNode } = domSelection
|
||||||
setTextFromDomNode(window, editor, anchorNode)
|
editor.reconcileDOMNode(anchorNode)
|
||||||
|
|
||||||
next()
|
next()
|
||||||
}
|
}
|
||||||
|
60
packages/slate-react/src/plugins/react/commands.js
Normal file
60
packages/slate-react/src/plugins/react/commands.js
Normal file
@@ -0,0 +1,60 @@
|
|||||||
|
/**
|
||||||
|
* A set of commands for the React plugin.
|
||||||
|
*
|
||||||
|
* @return {Object}
|
||||||
|
*/
|
||||||
|
|
||||||
|
function CommandsPlugin() {
|
||||||
|
/**
|
||||||
|
* reconcileDOMNode takes text from inside the `domNode` and uses it to set
|
||||||
|
* the text inside the matching `node` in Slate.
|
||||||
|
*
|
||||||
|
* @param {Window} window
|
||||||
|
* @param {Editor} editor
|
||||||
|
* @param {Node} domNode
|
||||||
|
*/
|
||||||
|
|
||||||
|
function reconcileDOMNode(editor, domNode) {
|
||||||
|
const { value } = editor
|
||||||
|
const { document, selection } = value
|
||||||
|
const domElement = domNode.parentElement.closest('[data-key]')
|
||||||
|
const point = editor.findPoint(domElement, 0)
|
||||||
|
const node = document.getDescendant(point.path)
|
||||||
|
const block = document.getClosestBlock(point.path)
|
||||||
|
|
||||||
|
// Get text information
|
||||||
|
const { text } = node
|
||||||
|
let { textContent: domText } = domElement
|
||||||
|
|
||||||
|
const isLastNode = block.nodes.last() === node
|
||||||
|
const lastChar = domText.charAt(domText.length - 1)
|
||||||
|
|
||||||
|
// COMPAT: If this is the last leaf, and the DOM text ends in a new line,
|
||||||
|
// we will have added another new line in <Leaf>'s render method to account
|
||||||
|
// for browsers collapsing a single trailing new lines, so remove it.
|
||||||
|
if (isLastNode && lastChar === '\n') {
|
||||||
|
domText = domText.slice(0, -1)
|
||||||
|
}
|
||||||
|
|
||||||
|
// If the text is no different, abort.
|
||||||
|
if (text === domText) return
|
||||||
|
|
||||||
|
let entire = selection
|
||||||
|
.moveAnchorTo(point.path, 0)
|
||||||
|
.moveFocusTo(point.path, text.length)
|
||||||
|
|
||||||
|
entire = document.resolveRange(entire)
|
||||||
|
|
||||||
|
// Change the current value to have the leaf's text replaced.
|
||||||
|
editor.insertTextAtRange(entire, domText, node.marks)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
commands: {
|
||||||
|
reconcileDOMNode,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default CommandsPlugin
|
@@ -2,6 +2,7 @@ import PlaceholderPlugin from 'slate-react-placeholder'
|
|||||||
|
|
||||||
import EditorPropsPlugin from './editor-props'
|
import EditorPropsPlugin from './editor-props'
|
||||||
import RenderingPlugin from './rendering'
|
import RenderingPlugin from './rendering'
|
||||||
|
import CommandsPlugin from './commands'
|
||||||
import QueriesPlugin from './queries'
|
import QueriesPlugin from './queries'
|
||||||
import DOMPlugin from '../dom'
|
import DOMPlugin from '../dom'
|
||||||
import RestoreDOMPlugin from './restore-dom'
|
import RestoreDOMPlugin from './restore-dom'
|
||||||
@@ -16,6 +17,7 @@ import RestoreDOMPlugin from './restore-dom'
|
|||||||
function ReactPlugin(options = {}) {
|
function ReactPlugin(options = {}) {
|
||||||
const { placeholder = '', plugins = [] } = options
|
const { placeholder = '', plugins = [] } = options
|
||||||
const renderingPlugin = RenderingPlugin(options)
|
const renderingPlugin = RenderingPlugin(options)
|
||||||
|
const commandsPlugin = CommandsPlugin(options)
|
||||||
const queriesPlugin = QueriesPlugin(options)
|
const queriesPlugin = QueriesPlugin(options)
|
||||||
const editorPropsPlugin = EditorPropsPlugin(options)
|
const editorPropsPlugin = EditorPropsPlugin(options)
|
||||||
const domPlugin = DOMPlugin({
|
const domPlugin = DOMPlugin({
|
||||||
@@ -36,6 +38,7 @@ function ReactPlugin(options = {}) {
|
|||||||
restoreDomPlugin,
|
restoreDomPlugin,
|
||||||
placeholderPlugin,
|
placeholderPlugin,
|
||||||
renderingPlugin,
|
renderingPlugin,
|
||||||
|
commandsPlugin,
|
||||||
queriesPlugin,
|
queriesPlugin,
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
@@ -1,67 +0,0 @@
|
|||||||
import findPoint from './find-point'
|
|
||||||
|
|
||||||
/**
|
|
||||||
* setTextFromDomNode lets us take a domNode and reconcile the text in the
|
|
||||||
* editor's Document such that it reflects the text in the DOM. This is the
|
|
||||||
* opposite of what the Editor usually does which takes the Editor's Document
|
|
||||||
* and React modifies the DOM to match. The purpose of this method is for
|
|
||||||
* composition changes where we don't know what changes the user made by
|
|
||||||
* looking at events. Instead we wait until the DOM is in a safe state, we
|
|
||||||
* read from it, and update the Editor's Document.
|
|
||||||
*
|
|
||||||
* @param {Window} window
|
|
||||||
* @param {Editor} editor
|
|
||||||
* @param {Node} domNode
|
|
||||||
*/
|
|
||||||
|
|
||||||
export default function setTextFromDomNode(window, editor, domNode) {
|
|
||||||
const point = findPoint(domNode, 0, editor)
|
|
||||||
if (!point) return
|
|
||||||
|
|
||||||
// Get the text node and leaf in question.
|
|
||||||
const { value } = editor
|
|
||||||
const { document, selection } = value
|
|
||||||
const node = document.getDescendant(point.path)
|
|
||||||
const block = document.getClosestBlock(point.path)
|
|
||||||
const leaves = node.getLeaves()
|
|
||||||
const lastText = block.getLastText()
|
|
||||||
const lastLeaf = leaves.last()
|
|
||||||
let start = 0
|
|
||||||
let end = 0
|
|
||||||
|
|
||||||
const leaf =
|
|
||||||
leaves.find(r => {
|
|
||||||
start = end
|
|
||||||
end += r.text.length
|
|
||||||
if (end > point.offset) return true
|
|
||||||
}) || lastLeaf
|
|
||||||
|
|
||||||
// Get the text information.
|
|
||||||
const { text } = leaf
|
|
||||||
let { textContent } = domNode
|
|
||||||
const isLastText = node === lastText
|
|
||||||
const isLastLeaf = leaf === lastLeaf
|
|
||||||
const lastChar = textContent.charAt(textContent.length - 1)
|
|
||||||
|
|
||||||
// COMPAT: If this is the last leaf, and the DOM text ends in a new line,
|
|
||||||
// we will have added another new line in <Leaf>'s render method to account
|
|
||||||
// for browsers collapsing a single trailing new lines, so remove it.
|
|
||||||
if (isLastText && isLastLeaf && lastChar === '\n') {
|
|
||||||
textContent = textContent.slice(0, -1)
|
|
||||||
}
|
|
||||||
|
|
||||||
// If the text is no different, abort.
|
|
||||||
if (textContent === text) return
|
|
||||||
|
|
||||||
// Determine what the selection should be after changing the text.
|
|
||||||
// const delta = textContent.length - text.length
|
|
||||||
// const corrected = selection.moveToEnd().moveForward(delta)
|
|
||||||
let entire = selection
|
|
||||||
.moveAnchorTo(point.path, start)
|
|
||||||
.moveFocusTo(point.path, end)
|
|
||||||
|
|
||||||
entire = document.resolveRange(entire)
|
|
||||||
|
|
||||||
// Change the current value to have the leaf's text replaced.
|
|
||||||
editor.insertTextAtRange(entire, textContent, leaf.marks)
|
|
||||||
}
|
|
Reference in New Issue
Block a user