substance
Version:
Substance is a JavaScript library for web-based content editing. It provides building blocks for realizing custom text editors and web-based publishing system. It is developed to power our online editing platform [Substance](http://substance.io).
137 lines (125 loc) • 4.39 kB
JavaScript
import cloneDeep from '../util/cloneDeep'
import forEach from '../util/forEach'
import {
copyNode, deleteTextRange, deleteListRange, TEXT_SNIPPET_ID, getContainerRoot, append
} from './documentHelpers'
import {
isFirst, isLast, getNodeIdsCoveredByContainerSelection
} from './selectionHelpers'
/**
Creates a new document instance containing only the selected content
@param {Object} args object with `selection`
@return {Object} with a `doc` property that has a fresh doc with the copied content
*/
export default function copySelection (doc, selection) {
if (!selection) throw new Error("'selection' is mandatory.")
let copy = null
if (!selection.isNull() && !selection.isCollapsed()) {
// return a simplified version if only a piece of text is selected
if (selection.isPropertySelection()) {
copy = _copyPropertySelection(doc, selection)
} else if (selection.isContainerSelection()) {
copy = _copyContainerSelection(doc, selection)
} else if (selection.isNodeSelection()) {
copy = _copyNodeSelection(doc, selection)
} else {
console.error('Copy is not yet supported for selection type.')
}
}
return copy
}
function _copyPropertySelection (doc, selection) {
const path = selection.start.path
const offset = selection.start.offset
const endOffset = selection.end.offset
const text = doc.get(path)
const snippet = doc.createSnippet()
const containerNode = snippet.getContainer()
snippet.create({
type: doc.schema.getDefaultTextType(),
id: TEXT_SNIPPET_ID,
content: text.substring(offset, endOffset)
})
containerNode.append(TEXT_SNIPPET_ID)
const annotations = doc.getIndex('annotations').get(path, offset, endOffset)
forEach(annotations, function (anno) {
const data = cloneDeep(anno.toJSON())
const path = [TEXT_SNIPPET_ID, 'content']
data.start = {
path: path,
offset: Math.max(offset, anno.start.offset) - offset
}
data.end = {
path: path,
offset: Math.min(endOffset, anno.end.offset) - offset
}
snippet.create(data)
})
return snippet
}
function _copyContainerSelection (tx, sel) {
const containerPath = sel.containerPath
const snippet = tx.createSnippet()
const targetContainer = snippet.getContainer()
const targetContainerPath = targetContainer.getContentPath()
const nodeIds = getNodeIdsCoveredByContainerSelection(tx, sel)
const L = nodeIds.length
if (L === 0) return snippet
const start = sel.start
const end = sel.end
let skippedFirst = false
let skippedLast = false
// First copy the whole covered nodes
const created = {}
for (let i = 0; i < L; i++) {
const id = nodeIds[i]
const node = tx.get(id)
// skip NIL selections, such as cursor at the end of first node or cursor at the start of last node.
if (i === 0 && isLast(tx, containerPath, start)) {
skippedFirst = true
continue
}
if (i === L - 1 && isFirst(tx, containerPath, end)) {
skippedLast = true
continue
}
if (!created[id]) {
copyNode(node).forEach((nodeData) => {
const copy = snippet.create(nodeData)
created[copy.id] = true
})
append(snippet, targetContainerPath, id)
}
}
if (!skippedFirst) {
// ATTENTION: we need the root node here, e.g. the list, not the list items
const startNode = getContainerRoot(snippet, targetContainerPath, start.getNodeId())
if (startNode.isText()) {
deleteTextRange(snippet, null, start)
} else if (startNode.isList()) {
deleteListRange(snippet, startNode, null, start, { deleteEmptyFirstItem: true })
}
}
if (!skippedLast) {
// ATTENTION: we need the root node here, e.g. the list, not the list items
const endNode = getContainerRoot(snippet, targetContainerPath, end.getNodeId())
if (endNode.isText()) {
deleteTextRange(snippet, end, null)
} else if (endNode.isList()) {
deleteListRange(snippet, endNode, end, null)
}
}
return snippet
}
function _copyNodeSelection (doc, selection) {
const snippet = doc.createSnippet()
const targetNode = snippet.getContainer()
const targetPath = targetNode.getContentPath()
const nodeId = selection.getNodeId()
const node = doc.get(nodeId)
copyNode(node).forEach((nodeData) => {
snippet.create(nodeData)
})
append(snippet, targetPath, node.id)
return snippet
}