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).
243 lines (198 loc) • 5.54 kB
JavaScript
import { isPlainObject } from '../util'
import copySelection from './copySelection'
import Selection from './Selection'
import { augmentSelection } from './selectionHelpers'
import { deepDeleteNode } from './documentHelpers'
import Editing from './Editing'
/*
Abstract base class for document editor APIs such as Transaction.
It implements a turtle-graphics way of editing by maintaining a selection state
and providing an interface for low- and high-level manipulation.
Low-level manipulations are dispatched to the edited document instance.
Higher-level manipulations involve complex manipulations keeping the selection in a correct state.
*/
export default class EditingInterface {
constructor (doc, options = {}) {
this._document = doc
this._selection = null
this._impl = options.editing || new Editing()
this._direction = null
}
dispose () {}
getDocument () {
return this._document
}
/* low-level API */
get (...args) {
return this._document.get(...args)
}
getProperty (...args) {
return this._document.getProperty(...args)
}
contains (id) {
return this._document.contains(id)
}
find (cssSelector) {
return this._document.find(cssSelector)
}
findAll (cssSelector) {
return this._document.findAll(cssSelector)
}
create (nodeData) {
return this._document.create(nodeData)
}
delete (nodeId) {
return this._document.delete(nodeId)
}
deepDeleteNode (nodeId) {
return deepDeleteNode(this._document.get(nodeId))
}
set (path, value) {
return this._document.set(path, value)
}
update (path, diffOp) {
return this._document.update(path, diffOp)
}
updateNode (id, newProps) {
return this._document.updateNode(id, newProps)
}
/* Selection API */
createSelection (selData) {
// TODO: we need to rethink this
// I'd like to make it convenient for the 99% use cases
// which means reusing containerPath and surfaceId
// However, it does not work well for cases
// where the surface changes
// Even better would be just to have surfaceId, and derive
// containerPath dynamically
selData = augmentSelection(selData, this._selection)
return this._document.createSelection(selData)
}
setSelection (sel) {
if (!sel) {
sel = Selection.nullSelection
} else if (isPlainObject(sel)) {
sel = this.createSelection(sel)
} else if (!sel.isNull()) {
sel = augmentSelection(sel, this._selection)
}
this._selection = sel
return sel
}
getSelection () {
return this._selection
}
get selection () {
return this._selection
}
set selection (sel) {
this.setSelection(sel)
}
/*
ATTENTION/TODO: text direction could be different on different paragraphs
I.e. it should probably be a TextNode property
*/
get textDirection () {
return this._direction
}
set textDirection (dir) {
this._direction = dir
}
/* High-level editing */
annotate (annotationData) {
const sel = this._selection
if (sel && (sel.isPropertySelection() || sel.isContainerSelection())) {
return this._impl.annotate(this, annotationData)
}
}
break () {
if (this._selection && !this._selection.isNull()) {
this._impl.break(this)
}
}
copySelection () {
const doc = this.getDocument()
const sel = this._selection
if (sel && !sel.isNull() && !sel.isCollapsed()) {
return copySelection(doc, this._selection)
}
}
deleteSelection (options) {
const sel = this._selection
if (sel && !sel.isNull() && !sel.isCollapsed()) {
this._impl.delete(this, 'right', options)
}
}
deleteCharacter (direction) {
const sel = this._selection
if (!sel || sel.isNull()) {
// nothing
} else if (!sel.isCollapsed()) {
this.deleteSelection()
} else {
this._impl.delete(this, direction)
}
}
insertText (text) {
const sel = this._selection
if (sel && !sel.isNull()) {
this._impl.insertText(this, text)
}
}
// insert an inline node with given data at the current selection
insertInlineNode (inlineNode) {
const sel = this._selection
if (sel && !sel.isNull() && sel.isPropertySelection()) {
return this._impl.insertInlineNode(this, inlineNode)
}
}
insertBlockNode (blockNode) {
const sel = this._selection
if (sel && !sel.isNull()) {
return this._impl.insertBlockNode(this, blockNode)
}
}
paste (content) {
const sel = this._selection
if (sel && !sel.isNull() && !sel.isCustomSelection()) {
return this._impl.paste(this, content)
}
}
switchTextType (nodeData) {
const sel = this._selection
if (sel && !sel.isNull()) {
return this._impl.switchTextType(this, nodeData)
}
}
toggleList (params) {
const sel = this._selection
if (sel && !sel.isNull()) {
return this._impl.toggleList(this, params)
}
}
indent () {
const sel = this._selection
if (sel && !sel.isNull()) {
return this._impl.indent(this)
}
}
dedent () {
const sel = this._selection
if (sel && !sel.isNull()) {
return this._impl.dedent(this)
}
}
/* Legacy low-level API */
getIndex (...args) {
return this._document.getIndex(...args)
}
getAnnotations (...args) {
return this._document.getAnnotations(...args)
}
getSchema () {
return this._document.getSchema()
}
createSnippet () {
return this._document.createSnippet()
}
}