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 systems.
228 lines (193 loc) • 5.96 kB
JavaScript
import { platform, substanceGlobals } from '../util'
import { copySelection, documentHelpers } from '../model'
import ClipboardImporter from './ClipboardImporter'
import ClipboardExporter from './ClipboardExporter'
/**
The Clipboard is a Component which should be rendered as a sibling component
of one or multiple Surfaces.
It uses the JSONImporter and JSONExporter for internal copy'n'pasting,
i.e., within one window or between two instances with the same DocumentSchema.
For inter-application copy'n'paste, the ClipboardImporter and ClipboardExporter is used.
@internal
*/
class Clipboard {
constructor(editorSession, config) {
this.editorSession = editorSession
let doc = editorSession.getDocument()
let schema = doc.getSchema()
let htmlConverters = []
if (config.converterRegistry && config.converterRegistry.contains('html')) {
htmlConverters = config.converterRegistry.get('html').values() || []
}
let _config = {
schema: schema,
DocumentClass: doc.constructor,
converters: htmlConverters,
editorOptions: config.editorOptions
}
this.htmlImporter = new ClipboardImporter(_config)
this.htmlExporter = new ClipboardExporter(_config)
}
dispose() {
this.htmlImporter.dispose()
// does not need to be disposed
// this.htmlExporter.dispose()
}
getEditorSession() {
return this.editorSession
}
/*
Called by to enable clipboard handling on a given root element.
*/
attach(el) {
el.on('copy', this.onCopy, this)
el.on('cut', this.onCut, this)
el.on('paste', this.onPaste, this)
}
/*
Called by to disable clipboard handling.
*/
detach(el) {
el.off(this)
}
/*
Called when copy event fired.
@param {Event} event
*/
onCopy(event) {
// console.log("Clipboard.onCopy", arguments);
let clipboardData = this._copy()
substanceGlobals._clipboardData = event.clipboardData
if (event.clipboardData && clipboardData.doc) {
event.preventDefault()
// store as plain text and html
event.clipboardData.setData('text/plain', clipboardData.text)
// WORKAROUND: under IE and Edge it is not permitted to set 'text/html'
if (!platform.isIE && !platform.isEdge) {
event.clipboardData.setData('text/html', clipboardData.html)
}
}
}
/*
Called when cut event fired.
@param {Event} event
*/
onCut(event) {
// preventing default behavior to avoid that contenteditable
// destroys our DOM
event.preventDefault()
// console.log("Clipboard.onCut", arguments);
this.onCopy(event)
let editorSession = this.getEditorSession()
editorSession.transaction((tx)=>{
tx.deleteSelection()
})
}
/*
Called when paste event fired.
@param {Event} event
*/
// Works on Safari/Chrome/FF
onPaste(event) {
let clipboardData = event.clipboardData
let types = {}
for (let i = 0; i < clipboardData.types.length; i++) {
types[clipboardData.types[i]] = true
}
// console.log('onPaste(): received content types', types);
event.preventDefault()
event.stopPropagation()
let plainText
let html
if (types['text/plain']) {
plainText = clipboardData.getData('text/plain')
}
if (types['text/html']) {
html = clipboardData.getData('text/html')
}
// HACK: to allow at least in app copy and paste under Edge (which blocks HTML)
// we guess by comparing the old and new plain text
if (platform.isEdge &&
substanceGlobals.clipboardData &&
substanceGlobals.clipboardData.text === plainText) {
html = substanceGlobals.clipboardData.html
} else {
substanceGlobals.clipboardData = {
text: plainText,
html: html
}
}
// console.log('onPaste(): html = ', html);
// WORKAROUND: FF does not provide HTML coming in from other applications
// so fall back to pasting plain text
if (platform.isFF && !html) {
this._pastePlainText(plainText)
return
}
// if we have content given as HTML we let the importer assess the quality first
// and fallback to plain text import if it's bad
if (html) {
if (!this._pasteHtml(html, plainText)) {
this._pastePlainText(plainText)
}
} else {
this._pastePlainText(plainText)
}
}
/*
Pastes a given plain text into the surface.
@param {String} plainText plain text
*/
_pastePlainText(plainText) {
let editorSession = this.getEditorSession()
editorSession.transaction(function(tx) {
tx.paste(plainText)
}, { action: 'paste' })
}
/*
Copies selected content from document to clipboard.
*/
_copy() {
let editorSession = this.getEditorSession()
let sel = editorSession.getSelection()
let doc = editorSession.getDocument()
let clipboardDoc = null
let clipboardText = ""
let clipboardHtml = ""
if (!sel.isCollapsed()) {
clipboardText = documentHelpers.getTextForSelection(doc, sel) || ""
clipboardDoc = copySelection(doc, sel)
clipboardHtml = this.htmlExporter.exportDocument(clipboardDoc)
}
return {
doc: clipboardDoc,
html: clipboardHtml,
text: clipboardText
}
}
/*
Pastes a given parsed html document into the surface.
@param {ui/DOMElement} docElement
@param {String} text plain text representation used as a fallback
*/
_pasteHtml(html, text) {
let content = this.htmlImporter.importDocument(html)
this.paste(content, text)
return true
}
/*
Takes a clipboard document and pastes it at the current
cursor position.
Used by PasteCommand
*/
paste(doc, text) {
let content = doc || text
let editorSession = this.getEditorSession()
if (content) {
editorSession.transaction((tx) => {
tx.paste(content)
}, { action: 'paste' })
}
}
}
export default Clipboard