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).
158 lines (138 loc) • 4.65 kB
JavaScript
import isEqual from '../util/isEqual'
import Component from '../dom/Component'
import AbstractIsolatedNodeComponent from './AbstractIsolatedNodeComponent'
export default class IsolatedInlineNodeComponent extends AbstractIsolatedNodeComponent {
render ($$) {
const node = this.props.node
const ContentClass = this.ContentClass
const state = this.state
const el = $$('span')
el.addClass(this.getClassNames())
.addClass('sc-inline-node')
.addClass('sm-' + node.type)
.attr('data-id', node.id)
.attr('data-inline', '1')
const disabled = this.isDisabled()
if (state.mode) {
el.addClass('sm-' + state.mode)
} else {
el.addClass('sm-not-selected')
}
if (!ContentClass.noStyle) {
el.addClass('sm-default-style')
}
// shadowing handlers of the parent surface
// TODO: extract this into a helper so that we can reuse it anywhere where we want
// to prevent propagation to the parent surface
el.on('keydown', this.onKeydown)
el.append(
this.renderContent($$, node)
.ref('content')
.addClass('se-content')
)
el.on('click', this.onClick)
if (disabled) {
el.addClass('sm-disabled')
.attr('contenteditable', false)
}
// TODO: Chrome et al. does not display selections
// for `draggable=true`
// We should only enable draggable if the parent
// surface is actually focused
// However, there is some weird behavior:
// rerendering with `draggable=false` does
// not remove the attribute
if (node.shouldBeDraggable) {
el.attr('draggable', true)
}
return el
}
isDisabled () {
return !this.state.mode || ['co-selected', 'cursor'].indexOf(this.state.mode) > -1
}
getClassNames () {
return ''
}
onClick (event) {
if (this._shouldConsumeEvent(event)) {
event.stopPropagation()
event.preventDefault()
this.selectNode()
}
}
selectNode () {
// console.log('IsolatedNodeComponent: selecting node.');
const editorSession = this.getEditorSession()
const node = this.props.node
const selData = {
type: 'property',
path: node.start.path,
startOffset: node.start.offset,
endOffset: node.end.offset
}
const surface = this.getParentSurface()
if (surface) {
Object.assign(selData, {
containerPath: surface.getContainerPath(),
surfaceId: surface.id
})
}
editorSession.setSelection(selData)
}
_getContentClass () {
const node = this.props.node
let ComponentClass
// first try to get the component registered for this node
ComponentClass = this.getComponent(node.type, true)
// then try to find a generic a component registered
// for "inline-node"
if (!ComponentClass) {
ComponentClass = this.getComponent('unsupported-inline-node', true)
}
// TODO: this should not be in substance
// instead an application should register a custom implementation
// overriding _getContentClass()
if (!ComponentClass) {
console.error(`No component registered for inline node '${node.type}'.`)
ComponentClass = StubInlineNodeComponent
}
return ComponentClass
}
// TODO: this is almost the same as in IsolatedNodeComponent
// We should consolidate this
_deriveStateFromSelectionState (sel, selState) {
const surface = this._getSurfaceForSelection(sel, selState)
const parentSurface = this.getParentSurface()
if (!surface) return null
// detect cases where this node is selected or co-selected by inspecting the selection
if (surface === parentSurface) {
const node = this.props.node
if (sel.isPropertySelection() && !sel.isCollapsed() && isEqual(sel.start.path, node.start.path)) {
const nodeSel = node.getSelection()
if (nodeSel.equals(sel)) {
return { mode: 'selected' }
}
if (sel.contains(nodeSel)) {
return { mode: 'co-selected' }
}
}
}
const isolatedNodeComponent = surface.context.isolatedNodeComponent
if (!isolatedNodeComponent) return null
if (isolatedNodeComponent === this) {
return { mode: 'focused' }
}
const isolatedNodes = this._getIsolatedNodes(sel, selState)
if (isolatedNodes.indexOf(this) > -1) {
return { mode: 'co-focused' }
}
return null
}
get _isInlineNodeComponent () { return true }
}
class StubInlineNodeComponent extends Component {
render ($$) {
const node = this.props.node
return $$('span').text('???').attr('data-id', node.id).attr('data-type', node.type)
}
}