diff --git a/packages/core/src/Editor.ts b/packages/core/src/Editor.ts index 264d359d..6cf2523d 100644 --- a/packages/core/src/Editor.ts +++ b/packages/core/src/Editor.ts @@ -1,7 +1,6 @@ -import { EditorState, Plugin } from 'prosemirror-state' +import { EditorState, Plugin, Transaction } from 'prosemirror-state' import { EditorView} from 'prosemirror-view' import { Schema, DOMParser, DOMSerializer } from 'prosemirror-model' - import magicMethods from './utils/magicMethods' import elementFromString from './utils/elementFromString' import getAllMethodNames from './utils/getAllMethodNames' @@ -64,6 +63,9 @@ export class Editor extends EventEmitter { this.options = { ...this.options, ...options } } + /** + * This method is called after the proxy is initialized. + */ private init() { this.createExtensionManager() this.createSchema() @@ -77,6 +79,11 @@ export class Editor extends EventEmitter { this.proxy.focus(this.options.autoFocus) } + /** + * A magic method to call commands. + * + * @param name The name of the command + */ private __get(name: string) { const command = this.commands[name] @@ -89,6 +96,11 @@ export class Editor extends EventEmitter { return (...args: any) => command(...args) } + /** + * Update editor options. + * + * @param options A list of options + */ public setOptions(options: Partial = {}) { this.options = { ...this.options, ...options } @@ -96,21 +108,38 @@ export class Editor extends EventEmitter { this.view.updateState(this.state) } } - + + /** + * Returns whether the editor is editable. + */ public get isEditable() { return this.view && this.view.editable } + /** + * Returns the editor state. + */ public get state() { return this.view.state } + /** + * Register a list of commands. + * + * @param commands A list of commands + */ public registerCommands(commands: CommandSpec) { Object .entries(commands) .forEach(([name, command]) => this.registerCommand(name, command)) } + /** + * Register a command. + * + * @param name The name of your command + * @param callback The method of your command + */ public registerCommand(name: string, callback: Command): Editor { if (this.commands[name]) { throw new Error(`tiptap: command '${name}' is already defined.`) @@ -127,6 +156,12 @@ export class Editor extends EventEmitter { return this.proxy } + /** + * Register a ProseMirror plugin. + * + * @param plugin A ProseMirror plugin + * @param handlePlugins Control how to merge the plugin into the existing plugins. + */ public registerPlugin(plugin: Plugin, handlePlugins?: (plugin: Plugin, plugins: Plugin[]) => Plugin[]) { const plugins = typeof handlePlugins === 'function' ? handlePlugins(plugin, this.state.plugins) @@ -137,6 +172,11 @@ export class Editor extends EventEmitter { this.view.updateState(state) } + /** + * Unregister a ProseMirror plugin. + * + * @param name The plugins name + */ public unregisterPlugin(name: string) { const state = this.state.reconfigure({ // @ts-ignore @@ -146,14 +186,39 @@ export class Editor extends EventEmitter { this.view.updateState(state) } - public command(name: string, ...args: any) { - return this.commands[name](...args) + /** + * Call a command. + * + * @param name The name of the command you want to call. + * @param options The options of the command. + */ + public command(name: string, ...options: any) { + return this.commands[name](...options) } + /** + * Wraps a command to make it chainable. + * + * @param method + */ + private chainCommand = (method: Function) => (...args: any) => { + this.lastCommand = this.lastCommand + .then(() => method.apply(this, args)) + .catch(console.error) + + return this.proxy + } + + /** + * Creates an extension manager. + */ private createExtensionManager() { this.extensionManager = new ExtensionManager(this.options.extensions, this.proxy) } + /** + * Creates a ProseMirror schema. + */ private createSchema() { this.schema = new Schema({ topNode: this.extensionManager.topNode, @@ -163,32 +228,26 @@ export class Editor extends EventEmitter { this.emit('schemaCreated') } - private get plugins() { - return [ - ...this.extensionManager.plugins, - ...defaultPlugins.map(plugin => plugin(this.proxy)), - ] - } - + /** + * Creates a ProseMirror view. + */ private createView() { this.view = new EditorView(this.options.element, { state: EditorState.create({ doc: this.createDocument(this.options.content), - plugins: this.plugins, + plugins: [ + ...this.extensionManager.plugins, + ...defaultPlugins.map(plugin => plugin(this.proxy)), + ], }), dispatchTransaction: this.dispatchTransaction.bind(this), nodeViews: this.extensionManager.nodeViews, }) } - private chainCommand = (method: Function) => (...args: any) => { - this.lastCommand = this.lastCommand - .then(() => method.apply(this, args)) - .catch(console.error) - - return this.proxy - } - + /** + * Creates a ProseMirror document. + */ public createDocument = (content: EditorContent, parseOptions: any = {}): any => { if (content && typeof content === 'object') { try { @@ -208,12 +267,20 @@ export class Editor extends EventEmitter { return this.createDocument('') } + /** + * Store the current selection. + */ private storeSelection() { const { from, to } = this.state.selection this.selection = { from, to } } - private dispatchTransaction(transaction: any): void { + /** + * The callback over which to send transactions (state updates) produced by the view. + * + * @param transaction An editor state transaction + */ + private dispatchTransaction(transaction: Transaction) { const state = this.state.apply(transaction) this.view.updateState(state) this.storeSelection() @@ -226,14 +293,30 @@ export class Editor extends EventEmitter { this.emit('update', { transaction }) } + /** + * Get attributes of the currently selected node. + * + * @param name Name of the node + */ public getNodeAttrs(name: string) { return getNodeAttrs(this.state, this.schema.nodes[name]) } + /** + * Get attributes of the currently selected mark. + * + * @param name Name of the mark + */ public getMarkAttrs(name: string) { return getMarkAttrs(this.state, this.schema.marks[name]) } + /** + * Returns if the currently selected node or mark is active. + * + * @param name Name of the node or mark + * @param attrs Attributes of the node or mark + */ public isActive(name: string, attrs = {}) { const schemaType = getSchemaTypeByName(name, this.schema) @@ -262,10 +345,16 @@ export class Editor extends EventEmitter { // }) // } + /** + * Get the document as JSON. + */ public json() { return this.state.doc.toJSON() } + /** + * Get the document as HTML. + */ public html() { const div = document.createElement('div') const fragment = DOMSerializer @@ -277,6 +366,9 @@ export class Editor extends EventEmitter { return div.innerHTML } + /** + * Destroy the editor. + */ public destroy() { if (this.view) { this.view.destroy()