UNPKG

suneditor

Version:

Vanilla JavaScript based WYSIWYG web editor

183 lines (156 loc) 5.62 kB
import { PluginDropdown } from '../../interfaces'; import { dom } from '../../helper'; /** * @typedef {{tag: string, command: "line"|"br-line"|"block", name?: string, class?: string}} BlockStyleItem */ /** * @typedef {Object} BlockStylePluginOptions * @property {Array<"p"|"div"|"blockquote"|"pre"|"h1"|"h2"|"h3"|"h4"|"h5"|"h6"|string|BlockStyleItem>} [items] - Format list. * Use string shortcuts for built-in tags, or `BlockStyleItem` objects for custom block styles. * - `command` — `"line"`: single line block, `"br-line"`: br-separated block, `"block"`: container block. * ```js * // string shortcuts + custom item * ['p', 'h1', 'h2', 'blockquote', { tag: 'div', command: 'block', name: 'Custom Block', class: 'my-block' }] * ``` */ /** * @class * @description BlockStyle Plugin (`P`, `BLOCKQUOTE`, `PRE`, `H1`, `H2`...) */ class BlockStyle extends PluginDropdown { static key = 'blockStyle'; static className = 'se-btn-select se-btn-tool-format'; /** * @constructor * @param {SunEditor.Kernel} kernel - The Kernel instance * @param {BlockStylePluginOptions} pluginOptions - Plugin options */ constructor(kernel, pluginOptions) { super(kernel); // plugin basic properties this.title = this.$.lang.formats; this.inner = '<span class="se-txt">' + this.$.lang.formats + '</span>' + this.$.icons.arrow_down; // create HTML const menu = CreateHTML(this.$, pluginOptions.items); // members this.formatList = menu.querySelectorAll('li button'); this.currentFormat = ''; // init this.$.menu.initDropdownTarget(BlockStyle, menu); } /** * @hook Editor.EventManager * @type {SunEditor.Hook.Event.Active} */ active(element, target) { let formatTitle = this.$.lang.formats; const targetText = target.querySelector('.se-txt'); if (!element) { dom.utils.changeTxt(targetText, formatTitle); } else if (this.$.format.isLine(element)) { const formatList = this.formatList; const nodeName = element.nodeName.toLowerCase(); const className = (element.className.match(/(\s|^)__se__format__[^\s]+/) || [''])[0].trim(); for (let i = 0, len = formatList.length, f; i < len; i++) { f = /** @type {HTMLButtonElement} */ (formatList[i]); if (nodeName === f.getAttribute('data-value') && className === f.getAttribute('data-class')) { formatTitle = f.title; break; } } dom.utils.changeTxt(targetText, formatTitle); targetText.setAttribute('data-value', nodeName); targetText.setAttribute('data-class', className); return true; } return false; } /** * @override * @type {PluginDropdown['on']} */ on(target) { const formatList = this.formatList; const targetText = target.querySelector('.se-txt'); const currentFormat = (targetText.getAttribute('data-value') || '') + (targetText.getAttribute('data-class') || ''); if (currentFormat !== this.currentFormat) { for (let i = 0, len = formatList.length, f; i < len; i++) { f = formatList[i]; if (currentFormat === f.getAttribute('data-value') + f.getAttribute('data-class')) { dom.utils.addClass(f, 'active'); } else { dom.utils.removeClass(f, 'active'); } } this.currentFormat = currentFormat; } } /** * @override * @type {PluginDropdown['action']} */ action(target) { // "line"|"br-line"|"block" const command = target.getAttribute('data-command'); const tag = target.firstElementChild; if (command === 'block') { this.$.format.applyBlock(tag); } else if (command === 'br-line') { this.$.format.setBrLine(tag); } else { this.$.format.setLine(tag); } this.$.menu.dropdownOff(); } /** * @description Create a header tag, call by `shortcut` class * - (e.g. shortcuts._h1: ['c+s+49+$~blockStyle.applyHeaderByShortcut', '']) * @param {SunEditor.HookParams.Shortcut} params - Information of the `shortcut` plugin */ applyHeaderByShortcut({ keyCode }) { const headerNum = keyCode.match(/\d+$/)?.[0]; const tag = dom.utils.createElement(`H${headerNum}`); this.$.format.setLine(tag); } } /** * @param {SunEditor.Deps} $ - Kernel dependencies * @param {Array<string|BlockStyleItem>} [items] - Block style items * @returns {HTMLElement} */ function CreateHTML({ lang }, items) { const defaultFormats = ['p', 'blockquote', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6']; const formatList = !items || items.length === 0 ? defaultFormats : items; let list = /*html*/ ` <div class="se-list-inner"> <ul class="se-list-basic">`; for (let i = 0, len = formatList.length, format, tagName, command, name, h, attrs, className; i < len; i++) { format = formatList[i]; if (typeof format === 'string') { if (!defaultFormats.includes(format)) continue; tagName = format.toLowerCase(); command = tagName === 'blockquote' ? 'block' : tagName === 'pre' ? 'br-line' : 'line'; h = /^h/.test(tagName) ? tagName.match(/\d+/)[0] : ''; name = lang['tag_' + (h ? 'h' : tagName)] + h; className = ''; attrs = ''; } else { tagName = format.tag.toLowerCase(); command = format.command; name = format.name || tagName; className = format.class; attrs = className ? ' class="' + className + '"' : ''; } list += /*html*/ ` <li> <button type="button" class="se-btn se-btn-list" data-command="${command}" data-value="${tagName}" data-class="${className}" title="${name}" aria-label="${name}"> <${tagName}${attrs}>${name}</${tagName}> </button> </li>`; } list += /*html*/ ` </ul> </div>`; return dom.utils.createElement('DIV', { class: 'se-dropdown se-list-layer se-list-format' }, list); } export default BlockStyle;