199 lines
5.6 KiB
JavaScript
199 lines
5.6 KiB
JavaScript
import {EditorState, Plugin} from 'prosemirror-state'
|
|
import {EditorView} from 'prosemirror-view'
|
|
import {history} from 'prosemirror-history'
|
|
import {keymap} from 'prosemirror-keymap'
|
|
import {baseKeymap} from 'prosemirror-commands'
|
|
import {DOMParser, DOMSerializer} from 'prosemirror-model'
|
|
|
|
import {Menu, blocks, marks} from './godo-menus.mjs'
|
|
import {dialog} from './godo-dialog.mjs'
|
|
import {loadSchema, buildKeymap} from './utils.mjs'
|
|
import base from './components/base.mjs'
|
|
import fontMarks from './components/font-marks.mjs'
|
|
import heading from './components/heading.mjs'
|
|
import link from './components/link.mjs'
|
|
import list from './components/list.mjs'
|
|
|
|
function menuPlugin (menu) {
|
|
return new Plugin({
|
|
view (editorView) {
|
|
let menuView = new Menu(menu, editorView)
|
|
editorView.dom.parentNode.insertBefore(menuView.el, editorView.dom)
|
|
return menuView
|
|
},
|
|
})
|
|
}
|
|
|
|
function changeEventPlugin (editor) {
|
|
return new Plugin({
|
|
state: {
|
|
init () {},
|
|
apply (tr, callback) {
|
|
if (tr.docChanged) {
|
|
clearTimeout(callback)
|
|
callback = setTimeout(() => {
|
|
editor.sourceContent = editor.getHTML(tr.doc.content)
|
|
editor.linkedSource.dispatchEvent(new Event('change', { 'bubbles': true }))
|
|
}, 300)
|
|
}
|
|
return callback
|
|
},
|
|
},
|
|
})
|
|
}
|
|
|
|
export default class Godo extends window.HTMLElement {
|
|
connectedCallback () {
|
|
const linkedSourceId = this.getAttribute('linked-source')
|
|
this.linkedSource = document.getElementById(linkedSourceId)
|
|
this.on()
|
|
|
|
if (this.linkedSource.form) {
|
|
this.editorWrapper.classList.add('is-form-widget')
|
|
this.editable = !this.linkedSource.readOnly
|
|
} else {
|
|
this.initHTMLEditor()
|
|
}
|
|
}
|
|
|
|
on () {
|
|
this.options = {
|
|
schema: this.getAttribute('schema') || 'full',
|
|
instantUpdate: this.getAttribute('instant-update') !== null,
|
|
updateEvent: this.getAttribute('update-event'),
|
|
headingLevels: this.getAttribute('heading-levels'),
|
|
editable: false,
|
|
activePlugins: false,
|
|
}
|
|
|
|
const componentsDefinitions = [base, fontMarks, link]
|
|
if (this.options.schema === 'full') {
|
|
componentsDefinitions.push(heading, list)
|
|
}
|
|
|
|
const components = componentsDefinitions.map(c => c(this.options))
|
|
this.schema = loadSchema(components)
|
|
|
|
this.editorWrapper = document.createElement('div')
|
|
this.editorWrapper.className = 'godo'
|
|
this.appendChild(this.editorWrapper)
|
|
|
|
this.marksMenu = menuPlugin(marks(components, this.schema))
|
|
this.blocksMenu = menuPlugin(blocks(components, this.schema))
|
|
this.menuDialog = document.body.appendChild(dialog)
|
|
|
|
this.pluginsList = [
|
|
history(),
|
|
keymap(buildKeymap(components, this.schema, null)),
|
|
keymap(baseKeymap),
|
|
this.marksMenu,
|
|
]
|
|
|
|
if (this.options.schema !== 'basic') this.pluginsList.push(this.blocksMenu)
|
|
if (this.options.instantUpdate) this.pluginsList.push(changeEventPlugin(this))
|
|
|
|
this.stateConfig = () => {
|
|
return {
|
|
schema: this.schema,
|
|
doc: DOMParser.fromSchema(this.schema).parse(this.sourceContent),
|
|
plugins: this.activePlugins ? this.pluginsList : [],
|
|
}
|
|
}
|
|
|
|
this.state = EditorState.create(this.stateConfig())
|
|
|
|
const labelSelector = `label[for=${this.linkedSource.id}]`
|
|
const label = this.linkedSource.id ? document.querySelector(labelSelector) : undefined
|
|
this.view = new EditorView(this.editorWrapper, {
|
|
state: this.state,
|
|
editable: () => this.options.editable,
|
|
attributes: {
|
|
class: 'godo--editor',
|
|
role: 'textbox',
|
|
'aria-multiline': 'true',
|
|
'aria-labelledby': label ? label.id : undefined,
|
|
},
|
|
})
|
|
|
|
if (this.linkedSource.form) {
|
|
this.linkedSource.form.addEventListener('submit', () => {
|
|
this.sourceContent = this.getHTML()
|
|
})
|
|
}
|
|
|
|
const updateEvent = this.options.updateEvent
|
|
if (updateEvent) {
|
|
this.linkedSource.addEventListener(updateEvent, () => {
|
|
this.resetFromSource()
|
|
})
|
|
}
|
|
}
|
|
|
|
initHTMLEditor () {
|
|
this.linkedSource.hidden = true
|
|
this.editorWrapper.classList.add('html-edition')
|
|
|
|
this.cancelEdition = () => {
|
|
this.resetFromSource()
|
|
this.editable = false
|
|
}
|
|
}
|
|
|
|
get sourceContent () {
|
|
const w = document.createElement('div')
|
|
w.innerHTML = this.linkedSource.form ? this.linkedSource.value : this.linkedSource.innerHTML
|
|
return w
|
|
}
|
|
|
|
set sourceContent (newContent) {
|
|
if (this.linkedSource.form) {
|
|
this.linkedSource.value = newContent
|
|
} else {
|
|
this.linkedSource.innerHTML = newContent
|
|
}
|
|
}
|
|
|
|
getHTML (from) {
|
|
from = from || this.view.state.doc.content
|
|
const div = document.createElement('div')
|
|
const fragment = DOMSerializer
|
|
.fromSchema(this.schema)
|
|
.serializeFragment(from)
|
|
div.appendChild(fragment)
|
|
return div.innerHTML
|
|
}
|
|
|
|
get activePlugins () {
|
|
return this.options.activePlugins
|
|
}
|
|
|
|
set activePlugins (bool) {
|
|
const newState = this.view.state.reconfigure({
|
|
plugins: (bool) ? this.pluginsList : [],
|
|
})
|
|
this.view.updateState(newState)
|
|
this.view.updatePluginViews()
|
|
this.options.activePlugins = bool
|
|
}
|
|
|
|
get editable () {
|
|
return this.options.editable
|
|
}
|
|
|
|
set editable (bool) {
|
|
this.activePlugins = bool
|
|
this.view.setProps({
|
|
editable: () => bool,
|
|
})
|
|
this.editorWrapper.classList.toggle('is-editable', bool)
|
|
this.options.editable = bool
|
|
}
|
|
|
|
resetFromSource () {
|
|
this.state = EditorState.create(this.stateConfig())
|
|
this.view.updateState(this.state)
|
|
}
|
|
}
|
|
|
|
window.customElements.define('godo-editor', Godo)
|