UNPKG

grapesjs_codeapps

Version:

Free and Open Source Web Builder Framework/SC Modification

303 lines (269 loc) 7.95 kB
/** * This module allows to customize the built-in toolbar of the Rich Text Editor and use commands from the [HTML Editing APIs](https://developer.mozilla.org/en-US/docs/Web/API/Document/execCommand). * It's highly recommended to keep this toolbar as small as possible, especially from styling commands (eg. 'fontSize') and leave this task to the Style Manager * * You can customize the initial state of the module from the editor initialization, by passing the following [Configuration Object](https://github.com/artf/grapesjs/blob/master/src/rich_text_editor/config/config.js) * ```js * const editor = grapesjs.init({ * rte: { * // options * } * }) * ``` * * Once the editor is instantiated you can use its API. Before using these methods you should get the module from the instance * * ```js * const rte = editor.RichTextEditor; * ``` * * * [add](#add) * * [get](#get) * * [getAll](#getall) * * [remove](#remove) * * [getToolbarEl](#gettoolbarel) * * @module RichTextEditor */ import RichTextEditor from './model/RichTextEditor'; import { on, off } from 'utils/mixins'; module.exports = () => { let config = {}; const defaults = require('./config/config'); let toolbar, actions, lastEl, globalRte; const hideToolbar = () => { const style = toolbar.style; const size = '-100px'; style.top = size; style.left = size; style.display = 'none'; }; return { customRte: null, /** * Name of the module * @type {String} * @private */ name: 'RichTextEditor', /** * Initialize module. Automatically called with a new instance of the editor * @param {Object} opts Options * @private */ init(opts = {}) { config = opts; for (let name in defaults) { if (!(name in config)) { config[name] = defaults[name]; } } const ppfx = config.pStylePrefix; if (ppfx) { config.stylePrefix = ppfx + config.stylePrefix; } this.pfx = config.stylePrefix; actions = config.actions || []; toolbar = document.createElement('div'); toolbar.className = `${ppfx}rte-toolbar ${ppfx}one-bg`; globalRte = this.initRte(document.createElement('div')); //Avoid closing on toolbar clicking on(toolbar, 'mousedown', e => e.stopPropagation()); return this; }, /** * Post render callback * @param {View} ev * @private */ postRender(ev) { const canvas = ev.model.get('Canvas'); toolbar.style.pointerEvents = 'all'; hideToolbar(); canvas.getToolsEl().appendChild(toolbar); }, /** * Init the built-in RTE * @param {HTMLElement} el * @return {RichTextEditor} * @private */ initRte(el) { const pfx = this.pfx; const actionbarContainer = toolbar; const actionbar = this.actionbar; const actions = this.actions || config.actions; const classes = { actionbar: `${pfx}actionbar`, button: `${pfx}action`, active: `${pfx}active` }; const rte = new RichTextEditor({ el, classes, actions, actionbar, actionbarContainer }); globalRte && globalRte.setEl(el); if (rte.actionbar) { this.actionbar = rte.actionbar; } if (rte.actions) { this.actions = rte.actions; } return rte; }, /** * Add a new action to the built-in RTE toolbar * @param {string} name Action name * @param {Object} action Action options * @example * rte.add('bold', { * icon: '<b>B</b>', * attributes: {title: 'Bold',} * result: rte => rte.exec('bold') * }); * rte.add('link', { * icon: document.getElementById('t'), * attributes: {title: 'Link',} * // Example on it's easy to wrap a selected content * result: rte => rte.insertHTML(`<a href="#">${rte.selection()}</a>`) * }); * // An example with fontSize * rte.add('fontSize', { * icon: `<select class="gjs-field"> * <option>1</option> * <option>4</option> * <option>7</option> * </select>`, * // Bind the 'result' on 'change' listener * event: 'change', * result: (rte, action) => rte.exec('fontSize', action.btn.firstChild.value), * // Callback on any input change (mousedown, keydown, etc..) * update: (rte, action) => { * const value = rte.doc.queryCommandValue(action.name); * if (value != 'false') { // value is a string * action.btn.firstChild.value = value; * } * } * }) */ add(name, action = {}) { action.name = name; globalRte.addAction(action, { sync: 1 }); }, /** * Get the action by its name * @param {string} name Action name * @return {Object} * @example * const action = rte.get('bold'); * // {name: 'bold', ...} */ get(name) { let result; globalRte.getActions().forEach(action => { if (action.name == name) { result = action; } }); return result; }, /** * Get all actions * @return {Array} */ getAll() { return globalRte.getActions(); }, /** * Remove the action from the toolbar * @param {string} name * @return {Object} Removed action * @example * const action = rte.remove('bold'); * // {name: 'bold', ...} */ remove(name) { const actions = this.getAll(); const action = this.get(name); if (action) { const btn = action.btn; const index = actions.indexOf(action); btn.parentNode.removeChild(btn); actions.splice(index, 1); } return action; }, /** * Get the toolbar element * @return {HTMLElement} */ getToolbarEl() { return toolbar; }, /** * Triggered when the offset of the editor is changed * @private */ udpatePosition() { const un = 'px'; const canvas = config.em.get('Canvas'); const pos = canvas.getTargetToElementDim(toolbar, lastEl, { event: 'rteToolbarPosUpdate' }); if (pos) { if (config.adjustToolbar) { // Move the toolbar down when the top canvas edge is reached if (pos.top <= pos.canvasTop) { pos.top = pos.elementTop + pos.elementHeight; } } const toolbarStyle = toolbar.style; toolbarStyle.top = pos.top + un; toolbarStyle.left = pos.left + un; } }, /** * Enable rich text editor on the element * @param {View} view Component view * @param {Object} rte The instance of already defined RTE * @private * */ enable(view, rte) { lastEl = view.el; const em = config.em; const el = view.getChildrenContainer(); const customRte = this.customRte; toolbar.style.display = ''; rte = customRte ? customRte.enable(el, rte) : this.initRte(el).enable(); if (em) { setTimeout(this.udpatePosition.bind(this), 0); const event = 'change:canvasOffset canvasScroll'; em.off(event, this.udpatePosition, this); em.on(event, this.udpatePosition, this); em.trigger('rte:enable', view, rte); } return rte; }, /** * Unbind rich text editor from the element * @param {View} view * @param {Object} rte The instance of already defined RTE * @private * */ disable(view, rte) { const em = config.em; const customRte = this.customRte; var el = view.getChildrenContainer(); if (customRte) { customRte.disable(el, rte); } else { rte && rte.disable(); } hideToolbar(); em && em.trigger('rte:disable', view, rte); } }; };