suneditor
Version:
Vanilla JavaScript based WYSIWYG web editor
183 lines (156 loc) • 5.62 kB
JavaScript
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;