suneditor
Version:
Vanilla JavaScript based WYSIWYG web editor
1,146 lines (1,030 loc) • 60.6 kB
JavaScript
import _icons from '../../assets/icons/defaultIcons';
import _defaultLang from '../../langs/en';
import { CreateContext } from '../schema/context';
import { CreateFrameContext } from '../schema/frameContext';
import { dom, numbers, converter, env } from '../../helper';
import { DEFAULTS } from '../schema/options';
const _d = env._d;
/**
* @typedef {import('../schema/options').AllBaseOptions} AllBaseOptions_constructor
*/
/**
* @typedef {Object} ConstructorReturnType
* @property {SunEditor.Context} context - Editor context object
* @property {HTMLElement} carrierWrapper - Carrier wrapper element
* @property {Map<string, *>} options - Processed editor options (`Map`)
* @property {Object<string, *>} plugins - Loaded plugins
* @property {Object<string, string>} icons - Icon set
* @property {Object<string, string>} lang - Language pack
* @property {?string} value - Initial editor value
* @property {?string} rootId - Root frame ID
* @property {Array<string|null>} rootKeys - Array of frame keys
* @property {Map<string|null, ReturnType<import('../schema/frameContext').CreateFrameContext>>} frameRoots - Map of frame contexts
* @property {Object<string, Array<HTMLElement>>} pluginCallButtons - Plugin toolbar buttons
* @property {Array<HTMLElement>} responsiveButtons - Responsive toolbar buttons
* @property {Object<string, Array<HTMLElement>>|[]} pluginCallButtons_sub - Sub-toolbar plugin buttons
* @property {Array<HTMLElement>} responsiveButtons_sub - Sub-toolbar responsive buttons
*/
/**
* @description Creates a new SunEditor instance with specified options.
* @param {Array<{target: Element, key: *, options: SunEditor.InitFrameOptions}>} editorTargets - Target element or multi-root object.
* @param {SunEditor.InitOptions} options - Configuration options for the editor.
* @returns {ConstructorReturnType} - SunEditor instance with context, options, and DOM elements.
*/
function Constructor(editorTargets, options) {
if (typeof options !== 'object') options = /** @type {SunEditor.InitOptions} */ ({});
/** --- Plugins ------------------------------------------------------------------------------------------ */
const plugins = {};
if (options.plugins) {
const excludedPlugins = options.excludedPlugins || [];
const originPlugins = options.plugins;
const pluginsValues = (Array.isArray(originPlugins) ? originPlugins : Object.keys(originPlugins)).filter((name) => !excludedPlugins.includes(name)).map((name) => originPlugins[name]);
for (let i = 0, len = pluginsValues.length, p; i < len; i++) {
p = pluginsValues[i].default || pluginsValues[i];
plugins[p.key] = p;
}
}
/** --- options --------------------------------------------------------------- */
const optionMap = InitOptions(options, editorTargets, plugins);
const o = optionMap.o;
const icons = optionMap.i;
const lang = optionMap.l;
const loadingBox = dom.utils.createElement('DIV', { class: 'se-loading-box sun-editor-common' }, '<div class="se-loading-effect"></div>');
/** --- carrier wrapper --------------------------------------------------------------- */
const editor_carrier_wrapper = dom.utils.createElement('DIV', { class: 'sun-editor sun-editor-carrier-wrapper sun-editor-common' + o.get('_themeClass') + (o.get('_rtl') ? ' se-rtl' : '') });
// menuTray
const menuTray = dom.utils.createElement('DIV', { class: 'se-menu-tray' });
editor_carrier_wrapper.appendChild(menuTray);
// focus temp element
const focusTemp = /** @type {HTMLInputElement} */ (
dom.utils.createElement('INPUT', {
class: '__se__focus__temp__',
style: 'position: fixed !important; top: -10000px !important; left: -10000px !important; display: block !important; width: 0 !important; height: 0 !important; margin: 0 !important; padding: 0 !important;',
})
);
focusTemp.tabIndex = 0;
editor_carrier_wrapper.appendChild(focusTemp);
// modal
const modal = dom.utils.createElement('DIV', { class: 'se-modal se-modal-area sun-editor-common' });
const modal_back = dom.utils.createElement('DIV', { class: 'se-modal-back' });
const modal_inner = dom.utils.createElement('DIV', { class: 'se-modal-inner' });
modal.appendChild(modal_back);
modal.appendChild(modal_inner);
editor_carrier_wrapper.appendChild(modal);
// alert
const alert = dom.utils.createElement('DIV', { class: 'se-alert se-modal-area sun-editor-common', style: 'display: none;' });
const alert_back = dom.utils.createElement('DIV', { class: 'se-modal-back' });
const alert_inner = dom.utils.createElement('DIV', { class: 'se-modal-inner' });
alert.appendChild(alert_back);
alert.appendChild(alert_inner);
editor_carrier_wrapper.appendChild(alert);
// loding box, resizing back
editor_carrier_wrapper.appendChild(dom.utils.createElement('DIV', { class: 'se-back-wrapper' }));
editor_carrier_wrapper.appendChild(loadingBox.cloneNode(true));
// drag cursor
const dragCursor = dom.utils.createElement('DIV', { class: 'se-drag-cursor' });
editor_carrier_wrapper.appendChild(dragCursor);
// set carrier wrapper
_d.body.appendChild(editor_carrier_wrapper);
/** --- toolbar --------------------------------------------------------------- */
let subbar = null,
sub_main = null;
const tool_bar_main = CreateToolBar(optionMap.buttons, plugins, o, icons, lang, false);
const toolbar = tool_bar_main.element;
toolbar.style.visibility = 'hidden';
// toolbar mode
if (/inline/i.test(o.get('mode'))) {
toolbar.className += ' se-toolbar-inline';
toolbar.style.width = o.get('toolbar_width');
} else if (/balloon/i.test(o.get('mode'))) {
toolbar.className += ' se-toolbar-balloon';
toolbar.style.width = o.get('toolbar_width');
toolbar.appendChild(dom.utils.createElement('DIV', { class: 'se-arrow' }));
}
if (o.get('_toolbar_bottom')) {
toolbar.className += ' se-toolbar-bottom';
}
/** --- subToolbar --------------------------------------------------------------- */
if (optionMap.subButtons) {
sub_main = CreateToolBar(optionMap.subButtons, plugins, o, icons, lang, false);
subbar = sub_main.element;
subbar.style.visibility = 'hidden';
// subbar mode must be balloon-*
subbar.className += ' se-toolbar-balloon se-toolbar-sub';
subbar.style.width = o.get('toolbar_sub_width');
subbar.appendChild(dom.utils.createElement('DIV', { class: 'se-arrow' }));
}
/** frame - root set - start -------------------------------------------------------------- */
const rootId = editorTargets[0].key || null;
const rootKeys = [];
const frameRoots = new Map();
const statusbarContainer = optionMap.statusbarContainer;
let default_status_bar = null;
for (let i = 0, len = editorTargets.length; i < len; i++) {
const editTarget = editorTargets[i];
const to = optionMap.frameMap.get(editTarget.key);
const top_div = dom.utils.createElement('DIV', { class: 'sun-editor' + o.get('_themeClass') + (o.get('_rtl') ? ' se-rtl' : '') });
const container = dom.utils.createElement('DIV', { class: 'se-container' });
const editor_div = dom.utils.createElement('DIV', { class: 'se-wrapper' + (o.get('type') === 'document' ? ' se-type-document' : '') + (o.get('_type_options').includes('header') ? ' se-type-document-header' : '') });
container.appendChild(dom.utils.createElement('DIV', { class: 'se-toolbar-shadow' }));
// init element
const initElements = _initTargetElements(editTarget.key, o, top_div, to);
const bottomBar = initElements.bottomBar;
const statusbar = bottomBar.statusbar;
const wysiwyg_div = initElements.wysiwygFrame;
const placeholder_span = initElements.placeholder;
let textarea = initElements.codeView;
// line breaker
const line_breaker_t = dom.utils.createElement('DIV', { class: 'se-line-breaker-component se-line-breaker-component-t', title: lang.insertLine }, icons.line_break);
const line_breaker_b = dom.utils.createElement('DIV', { class: 'se-line-breaker-component se-line-breaker-component-b', title: lang.insertLine }, icons.line_break);
editor_div.appendChild(line_breaker_t);
editor_div.appendChild(line_breaker_b);
// append container
if (placeholder_span) editor_div.appendChild(placeholder_span);
container.appendChild(dom.utils.createElement('DIV', { class: 'se-toolbar-sticky-dummy' }));
container.appendChild(editor_div);
// statusbar
if (statusbar) {
if (statusbarContainer) {
if (!default_status_bar) {
statusbarContainer.appendChild(dom.utils.createElement('DIV', { class: 'sun-editor' + o.get('_themeClass') }, statusbar));
default_status_bar = statusbar;
}
} else {
container.appendChild(statusbar);
}
}
// loading bar
container.appendChild(loadingBox.cloneNode(true));
// root key
const key = editTarget.key || null;
// code view - wrapper (only created when codeView button exists)
let codeWrapper = null;
if (o.get('buttons').has('codeView')) {
codeWrapper = dom.utils.createElement('DIV', { class: 'se-code-wrapper' }, textarea);
codeWrapper.style.setProperty('display', 'none', 'important');
editor_div.appendChild(codeWrapper);
// check code mirror
const codeMirrorEl = _checkCodeMirror(o, to, textarea);
// not used code mirror
if (textarea === codeMirrorEl) {
// add line nubers
const codeNumbers = dom.utils.createElement('TEXTAREA', { class: 'se-code-view-line', readonly: 'true' }, null);
codeWrapper.insertBefore(codeNumbers, textarea);
} else {
textarea = codeMirrorEl;
}
}
// markdown view - wrapper (only created when markdownView button exists)
let markdownWrapper = null;
let markdownTextarea = null;
if (o.get('buttons').has('markdownView')) {
markdownTextarea = initElements.markdownView;
const markdownNumbers = dom.utils.createElement('TEXTAREA', { class: 'se-markdown-view-line', readonly: 'true' }, null);
markdownWrapper = dom.utils.createElement('DIV', { class: 'se-markdown-wrapper' });
markdownWrapper.appendChild(markdownNumbers);
markdownWrapper.appendChild(markdownTextarea);
markdownWrapper.style.setProperty('display', 'none', 'important');
editor_div.appendChild(markdownWrapper);
}
// document type
const documentTypeInner = { inner: null, page: null, pageMirror: null };
if (o.get('_type_options').includes('header')) {
documentTypeInner.inner = dom.utils.createElement('DIV', { class: 'se-document-lines', style: `height: ${to.get('height')};` }, '<div class="se-document-lines-inner"></div>');
}
if (o.get('_type_options').includes('page')) {
documentTypeInner.page = dom.utils.createElement('DIV', { class: 'se-document-page' }, null);
documentTypeInner.pageMirror = dom.utils.createElement(
'DIV',
{
class: 'sun-editor-editable se-document-page-mirror-a4',
style: `position: absolute; width: 21cm; columns: 21cm; border: 0; overflow: hidden; height: auto; top: -10000px; left: -10000px;`,
},
null,
);
}
// set container
top_div.appendChild(container);
rootKeys.push(key);
frameRoots.set(
key,
CreateFrameContext({ target: editTarget.target, key: editTarget.key, options: to }, top_div, wysiwyg_div, codeWrapper, textarea, markdownWrapper, markdownTextarea, default_status_bar || statusbar, documentTypeInner, key),
);
}
/** frame - root set - end -------------------------------------------------------------- */
// toolbar container
const toolbar_container = o.get('toolbar_container');
if (toolbar_container) {
const top_div = dom.utils.createElement('DIV', { class: 'sun-editor' + o.get('_themeClass') + (o.get('_rtl') ? ' se-rtl' : '') });
const container = dom.utils.createElement('DIV', { class: 'se-container' });
container.appendChild(toolbar);
if (subbar) container.appendChild(subbar);
top_div.appendChild(container);
toolbar_container.appendChild(top_div);
toolbar_container.appendChild(dom.utils.createElement('DIV', { class: 'se-toolbar-sticky-dummy' }));
} else {
const rootContainer = frameRoots.get(rootId).get('container');
if (o.get('_toolbar_bottom')) {
const statusbar = rootContainer.querySelector('.se-statusbar');
rootContainer.insertBefore(toolbar, statusbar);
if (subbar) rootContainer.insertBefore(subbar, toolbar);
} else {
rootContainer.insertBefore(toolbar, rootContainer.firstElementChild);
if (subbar) rootContainer.insertBefore(subbar, rootContainer.firstElementChild);
}
}
return {
context: CreateContext(toolbar, toolbar_container, menuTray, subbar, statusbarContainer),
carrierWrapper: editor_carrier_wrapper,
options: o,
plugins: plugins,
icons: icons,
lang: lang,
value: optionMap.v,
rootId: rootId,
rootKeys: rootKeys,
frameRoots: frameRoots,
pluginCallButtons: tool_bar_main.pluginCallButtons,
responsiveButtons: tool_bar_main.responsiveButtons,
pluginCallButtons_sub: sub_main ? sub_main.pluginCallButtons : [],
responsiveButtons_sub: sub_main ? sub_main.responsiveButtons : [],
};
}
/**
* @description Create shortcuts desc span.
* @param {string} command Command string
* @param {Array<string>} values `options.shortcuts[command]`
* @param {?Element} button Command button element
* @param {Map<string, *>} keyMap Map to store shortcut key info
* @param {Array} rc `_reverseCommandArray` option
* @param {Set} reverseKeys Reverse key array
*/
export function CreateShortcuts(command, button, values, keyMap, rc, reverseKeys) {
if (!values || values.length < 2) return;
const tooptip = button?.querySelector('.se-tooltip-text');
for (let i = 0, a, v, c, s, edge, space, enter, textTrigger, plugin, method, t, k, r, _i; i < values.length; i += 2 + _i) {
_i = 0;
a = values[i].split('+');
plugin = null;
method = a.at(-1).trim?.();
if (method.startsWith('~')) {
// plugin key, method
plugin = command;
method = a.pop().trim().substring(1);
} else if (method.startsWith('$~')) {
// custom key, plugin method
const a_ = a.pop().trim().substring(2).split('.');
plugin = a_[0];
method = a_[1];
} else if (method.startsWith('$')) {
// directly method
_i = 1;
method = values[i + 2];
} else {
method = '';
}
c = s = edge = space = enter = textTrigger = v = null;
for (const a_ of a) {
switch (a_.trim()) {
case 'c':
c = true;
break;
case '!':
edge = true;
break;
case 's':
s = true;
break;
case '_':
space = true;
break;
case '=':
textTrigger = true;
break;
case '/':
enter = true;
break;
default:
v = a_;
}
}
v = v.split('|');
for (let j = 0, len = v.length; j < len; j++) {
k = c ? v[j] + (s ? '1000' : '') : v[j];
if (!keyMap.has(k)) {
r = rc.indexOf(command);
r = r === -1 ? '' : numbers.isOdd(r) ? rc[r + 1] : rc[r - 1];
if (r) reverseKeys.add(k);
keyMap.set(k, { c, s, edge, space, enter, textTrigger, plugin, command, method, r, type: button?.getAttribute('data-type'), button, key: k });
}
}
if (!(t = values[i + 1])) continue;
if (tooptip) _addTooltip(tooptip, s, t);
}
}
function _addTooltip(tooptipBtn, shift, shortcut) {
tooptipBtn.appendChild(dom.utils.createElement('SPAN', { class: 'se-shortcut' }, env.cmdIcon + (shift ? env.shiftIcon : '') + '+<span class="se-shortcut-key">' + shortcut + '</span>'));
}
/**
* @description Returns a new object with merge `a` and `b`
* @param {Object<*, *>} a object
* @param {Object<*, *>} b object
* @returns {Object<*, *>} new object
*/
function _mergeObject(a, b) {
return [a, b].reduce((_default, _new) => {
for (const key in _new) {
_default[key] = (_new[key] || '').toLowerCase();
}
return _default;
}, {});
}
/**
* @typedef {Object} InitOptionsReturnType
* @property {Map<string, *>} o - Processed base options (`Map` containing {@link AllBaseOptions_constructor} keys)
* @property {Object<string, string>} i - Icon set
* @property {Object<string, string>} l - Language pack
* @property {?string} v - Initial editor value
* @property {SunEditor.UI.ButtonList} buttons - Toolbar button list (arrays for groups, strings for single buttons)
* @property {?SunEditor.UI.ButtonList} subButtons - Sub-toolbar button list
* @property {?Element} statusbarContainer - Container element for status bar (if specified)
* @property {Map<string|null, SunEditor.FrameOptions>} frameMap - Map of frame-specific options (frame key => `SunEditor.FrameOptions`)
*/
/**
* @description Initialize options
* @param {SunEditor.InitOptions} options Configuration options for the editor.
* @param {Array<{target: Element, key: *, options: SunEditor.InitFrameOptions}>} editorTargets Target textarea
* @param {Object<string, *>} plugins Plugins object
* @returns {InitOptionsReturnType} Initialized options and configuration
*/
export function InitOptions(options, editorTargets, plugins) {
const buttonList = options.buttonList || DEFAULTS.BUTTON_LIST;
const o = new Map();
/** Multi root */
if (editorTargets.length > 1) {
if (!options.toolbar_container && !/inline|balloon/i.test(options.mode)) throw Error('[SUNEDITOR.create.fail] In multi root, The "mode" option cannot be "classic" without using the "toolbar_container" option.');
}
// migration data-.+
o.set('v2Migration', !!options.v2Migration);
/** Base */
o.set('buttons', new Set(buttonList.toString().split(',')));
o.set('strictMode', {
tagFilter: true,
formatFilter: true,
classFilter: true,
textStyleTagFilter: true,
attrFilter: true,
styleFilter: true,
...(typeof options.strictMode === 'boolean' ? {} : options.strictMode),
});
o.set('freeCodeViewMode', !!options.freeCodeViewMode);
o.set('finder_panel', options.finder_panel !== false);
o.set('finder_liveSearch', options.finder_liveSearch !== false);
o.set('__lineFormatFilter', options.__lineFormatFilter ?? true);
o.set('__pluginRetainFilter', options.__pluginRetainFilter ?? true);
const [modeBase, modePart] = (options.mode || 'classic').split(':');
o.set('mode', modeBase); // classic, inline, balloon, balloon-always
o.set('_toolbar_bottom', modePart === 'bottom' && /^(classic|inline)$/i.test(modeBase));
o.set('type', options.type?.split(':')[0] || ''); // document:header,page
o.set('theme', options.theme || '');
o.set('_themeClass', options.theme ? ` se-theme-${options.theme}` : '');
o.set('_type_options', options.type?.split(':')[1] || '');
o.set('externalLibs', options.externalLibs || {});
o.set('fontSizeUnits', Array.isArray(options.fontSizeUnits) && options.fontSizeUnits.length > 0 ? options.fontSizeUnits.map((v) => v.toLowerCase()) : DEFAULTS.SIZE_UNITS);
o.set('allowedClassName', new RegExp(`${options.allowedClassName && typeof options.allowedClassName === 'string' ? options.allowedClassName + '|' : ''}${DEFAULTS.CLASS_NAME}`));
o.set('closeModalOutsideClick', !!options.closeModalOutsideClick);
// format
o.set('copyFormatKeepOn', !!options.copyFormatKeepOn);
o.set('syncTabIndent', options.syncTabIndent ?? true);
// auto convert on paste
o.set('autoLinkify', options.autoLinkify ?? !!plugins.link);
o.set('autoStyleify', Array.isArray(options.autoStyleify) ? options.autoStyleify : ['bold', 'underline', 'italic', 'strike']);
let retainStyleMode = options.retainStyleMode || 'repeat';
if (typeof retainStyleMode === 'string' && !DEFAULTS.RETAIN_STYLE_MODE.includes(retainStyleMode)) {
console.error(`Invalid retainStyleMode: ${retainStyleMode}. Valid options are ${DEFAULTS.RETAIN_STYLE_MODE.join(', ')}. Using default 'repeat'.`);
retainStyleMode = 'repeat';
}
o.set('retainStyleMode', retainStyleMode);
const allowedExtraTags = { ...DEFAULTS.EXTRA_TAG_MAP, ...options.allowedExtraTags, '-': true };
const extraKeys = Object.keys(allowedExtraTags);
const allowedKeys = extraKeys.filter((k) => allowedExtraTags[k]).join('|');
const disallowedKeys = extraKeys.filter((k) => !allowedExtraTags[k]).join('|');
o.set('_allowedExtraTag', allowedKeys);
o.set('_disallowedExtraTag', disallowedKeys);
o.set('events', options.events || {});
// text style tags
o.set('textStyleTags', (typeof options.__textStyleTags === 'string' ? options.__textStyleTags : DEFAULTS.TEXT_STYLE_TAGS) + (options.textStyleTags ? '|' + options.textStyleTags : ''));
const textTags = _mergeObject(
{
bold: 'strong',
underline: 'u',
italic: 'em',
strike: 'del',
subscript: 'sub',
superscript: 'sup',
},
options.convertTextTags || {},
);
o.set('convertTextTags', textTags);
o.set('_textStyleTags', Object.values(textTags).concat(['span', 'li']));
o.set(
'tagStyles',
[{ ...DEFAULTS.TAG_STYLES, ...(options.__tagStyles || {}) }, options.tagStyles || {}].reduce((_default, _new) => {
for (const key in _new) {
_default[key] = _new[key];
}
return _default;
}, {}),
);
o.set('_textStylesRegExp', new RegExp(`\\s*[^-a-zA-Z](${DEFAULTS.SPAN_STYLES}${options.spanStyles ? '|' + options.spanStyles : ''})\\s*:[^;]+(?!;)*`, 'gi'));
o.set('_lineStylesRegExp', new RegExp(`\\s*[^-a-zA-Z](${DEFAULTS.LINE_STYLES}${options.lineStyles ? '|' + options.lineStyles : ''})\\s*:[^;]+(?!;)*`, 'gi'));
o.set('_defaultStyleTagMap', {
strong: textTags.bold,
b: textTags.bold,
u: textTags.underline,
ins: textTags.underline,
em: textTags.italic,
i: textTags.italic,
del: textTags.strike,
strike: textTags.strike,
s: textTags.strike,
sub: textTags.subscript,
sup: textTags.superscript,
});
o.set(
'_styleCommandMap',
_mergeObject(converter.swapKeyValue(textTags), {
strong: 'bold',
b: 'bold',
u: 'underline',
ins: 'underline',
em: 'italic',
i: 'italic',
del: 'strike',
strike: 'strike',
s: 'strike',
sub: 'subscript',
sup: 'superscript',
}),
);
o.set('_defaultTagCommand', {
bold: textTags.bold,
underline: textTags.underline,
italic: textTags.italic,
strike: textTags.strike,
subscript: textTags.sub,
superscript: textTags.sup,
});
// text direction
o.set('textDirection', options.textDirection ?? 'ltr');
o.set('_rtl', o.get('textDirection') === 'rtl');
// An array of key codes generated with the reverseButtons option, used to reverse the action for a specific key combination.
o.set('reverseCommands', ['indent-outdent'].concat(options.reverseButtons || []));
o.set('_reverseCommandArray', ('-' + o.get('reverseCommands').join('-')).split('-'));
if (numbers.isEven(o.get('_reverseCommandArray').length)) {
console.warn('[SUNEDITOR.create.warning] The "reverseCommands" option is invalid, Shortcuts key may not work properly.');
}
// etc
o.set('historyStackDelayTime', typeof options.historyStackDelayTime === 'number' ? options.historyStackDelayTime : 400);
o.set('historyStackSize', typeof options.historyStackSize === 'number' && options.historyStackSize > 0 ? options.historyStackSize : 100);
o.set('_editableClass', 'sun-editor-editable' + o.get('_themeClass') + (o.get('_rtl') ? ' se-rtl' : '') + (o.get('type') === 'document' ? ' se-type-document-editable-a4' : ''));
o.set('lineAttrReset', ['id'].concat(options.lineAttrReset && typeof options.lineAttrReset === 'string' ? options.lineAttrReset.toLowerCase().split('|') : []));
o.set('printClass', typeof options.printClass === 'string' ? options.printClass + ' ' + o.get('_editableClass') : null);
/** whitelist, blacklist */
// default line
o.set('defaultLine', typeof options.defaultLine === 'string' && options.defaultLine.length > 0 ? options.defaultLine : 'p');
o.set('defaultLineBreakFormat', options.defaultLineBreakFormat || 'line');
o.set('scopeSelectionTags', options.scopeSelectionTags || DEFAULTS.SCOPE_SELECTION_TAGS);
// element
const elw = (typeof options.elementWhitelist === 'string' ? options.elementWhitelist : '').toLowerCase();
const mjxEls = o.get('externalLibs').mathjax ? DEFAULTS.CLASS_MJX + '|' : '';
o.set('elementWhitelist', elw + (elw ? '|' : '') + mjxEls + o.get('_allowedExtraTag'));
const elb = _createBlacklist((typeof options.elementBlacklist === 'string' ? options.elementBlacklist : '').toLowerCase(), o.get('defaultLine'));
o.set('elementBlacklist', elb + (elb ? '|' : '') + o.get('_disallowedExtraTag'));
// attribute
o.set('attributeWhitelist', !options.attributeWhitelist || typeof options.attributeWhitelist !== 'object' ? null : options.attributeWhitelist);
o.set('attributeBlacklist', !options.attributeBlacklist || typeof options.attributeBlacklist !== 'object' ? null : options.attributeBlacklist);
// format tag
o.set(
'formatClosureBrLine',
_createFormatInfo(
options.formatClosureBrLine,
(options.__defaultFormatClosureBrLine = typeof options.__defaultFormatClosureBrLine === 'string' ? options.__defaultFormatClosureBrLine : DEFAULTS.FORMAT_CLOSURE_BR_LINE).toLowerCase(),
o.get('elementBlacklist'),
),
);
o.set(
'formatBrLine',
_createFormatInfo(
(options.formatBrLine || '') + '|' + o.get('formatClosureBrLine').str,
(options.__defaultFormatBrLine = typeof options.__defaultFormatBrLine === 'string' ? options.__defaultFormatBrLine : DEFAULTS.FORMAT_BR_LINE).toLowerCase(),
o.get('elementBlacklist'),
),
);
o.set(
'formatLine',
_createFormatInfo(
DEFAULTS.REQUIRED_FORMAT_LINE + '|' + (options.formatLine || '') + '|' + o.get('formatBrLine').str,
(options.__defaultFormatLine = typeof options.__defaultFormatLine === 'string' ? options.__defaultFormatLine : DEFAULTS.FORMAT_LINE).toLowerCase(),
o.get('elementBlacklist'),
),
);
// Error - default line
if (!o.get('formatLine').reg.test(o.get('defaultLine'))) {
throw Error(`[SUNEDITOR.create.fail] The "defaultLine(${o.get('defaultLine')})" option must be included in the "formatLine(${o.get('formatLine').str})" option.`);
}
o.set(
'formatClosureBlock',
_createFormatInfo(
options.formatClosureBlock,
(options.__defaultFormatClosureBlock = typeof options.__defaultFormatClosureBlock === 'string' ? options.__defaultFormatClosureBlock : DEFAULTS.FORMAT_CLOSURE_BLOCK).toLowerCase(),
o.get('elementBlacklist'),
),
);
o.set(
'formatBlock',
_createFormatInfo(
(options.formatBlock || '') + '|' + o.get('formatClosureBlock').str,
(options.__defaultFormatBlock = typeof options.__defaultFormatBlock === 'string' ? options.__defaultFormatBlock : DEFAULTS.FORMAT_BLOCK).toLowerCase(),
o.get('elementBlacklist'),
),
);
o.set('allowedEmptyTags', DEFAULTS.ALLOWED_EMPTY_NODE_LIST + (options.allowedEmptyTags ? ', ' + options.allowedEmptyTags : ''));
/** __defaults */
o.set('__defaultElementWhitelist', DEFAULTS.REQUIRED_ELEMENT_WHITELIST + '|' + (typeof options.__defaultElementWhitelist === 'string' ? options.__defaultElementWhitelist : DEFAULTS.ELEMENT_WHITELIST).toLowerCase());
o.set('__defaultAttributeWhitelist', (typeof options.__defaultAttributeWhitelist === 'string' ? options.__defaultAttributeWhitelist : DEFAULTS.ATTRIBUTE_WHITELIST).toLowerCase());
// --- create element whitelist (__defaultElementWhiteList + elementWhitelist + format[line, BrLine, Block, Closureblock, ClosureBrLine] - elementBlacklist)
o.set('_editorElementWhitelist', o.get('elementWhitelist') === '*' ? '*' : _createWhitelist(o));
/** Toolbar */
o.set('toolbar_width', options.toolbar_width ? (numbers.is(options.toolbar_width) ? options.toolbar_width + 'px' : options.toolbar_width) : 'auto');
o.set('toolbar_container', options.toolbar_container && !/inline/i.test(o.get('mode')) ? (typeof options.toolbar_container === 'string' ? _d.querySelector(options.toolbar_container) : options.toolbar_container) : null);
const _stickyOpt = options.toolbar_sticky;
const _isBalloon = /balloon/i.test(o.get('mode'));
if (_isBalloon) {
o.set('_toolbar_sticky', -1);
o.set('_toolbar_sticky_offset', 0);
} else if (_stickyOpt !== null && typeof _stickyOpt === 'object') {
o.set('_toolbar_sticky', numbers.get(_stickyOpt.top, 0));
o.set('_toolbar_sticky_offset', numbers.get(_stickyOpt.offset, 0));
} else {
o.set('_toolbar_sticky', _stickyOpt === undefined ? 0 : numbers.is(_stickyOpt) ? _stickyOpt : -1);
o.set('_toolbar_sticky_offset', 0);
}
o.set('toolbar_hide', !!options.toolbar_hide);
/** subToolbar */
let subButtons = null;
const subbar = options.subToolbar;
if (subbar?.buttonList?.length > 0) {
if (/balloon/.test(o.get('mode'))) {
console.warn('[SUNEDITOR.create.subToolbar.fail] When the "mode" option is "balloon-*", the "subToolbar" option is omitted.');
} else {
o.set('_subMode', subbar.mode || 'balloon');
o.set('toolbar_sub_width', subbar.width ? (numbers.is(subbar.width) ? subbar.width + 'px' : subbar.width) : 'auto');
subButtons = o.get('_rtl') ? subbar.buttonList.reverse() : subbar.buttonList;
o.set('buttons_sub', new Set(subButtons.toString().split(',')));
}
}
/** root options */
const frameMap = new Map();
for (let i = 0, len = editorTargets.length; i < len; i++) {
frameMap.set(editorTargets[i].key, InitFrameOptions(editorTargets[i].options || /** @type {SunEditor.InitFrameOptions} */ ({}), options));
}
/** Key actions */
o.set('tabDisable', !!options.tabDisable);
o.set('shortcutsHint', options.shortcutsHint === undefined ? true : !!options.shortcutsHint);
const shortcuts = (options.shortcutsDisable === undefined ? false : !!options.shortcutsDisable)
? {}
: [
{
// default command
selectAll: ['c+KeyA', 'A'],
bold: ['c+KeyB', 'B'],
strike: ['c+s+KeyS', 'S'],
underline: ['c+KeyU', 'U'],
italic: ['c+KeyI', 'I'],
redo: ['c+KeyY', 'Y', 'c+s+KeyZ', 'Z'],
undo: ['c+KeyZ', 'Z'],
indent: ['c+BracketRight', ']'],
outdent: ['c+BracketLeft', '['],
save: ['c+KeyS', 'S'],
...(o.get('finder_panel') ? { finder: ['c+KeyF', 'F'] } : {}),
// plugins
link: ['c+KeyK', 'K'],
hr: ['!+---+=+~shortcut', ''],
list_numbered: ['!+1.+_+~shortcut', ''],
list_bulleted: ['!+*.+_+~shortcut', ''],
// custom
_h1: ['c+s+Digit1|Numpad1+$~blockStyle.applyHeaderByShortcut', ''],
_h2: ['c+s+Digit2|Numpad2+$~blockStyle.applyHeaderByShortcut', ''],
_h3: ['c+s+Digit3|Numpad3+$~blockStyle.applyHeaderByShortcut', ''],
},
options.shortcuts || {},
].reduce((_default, _new) => {
for (const key in _new) {
_default[key] = _new[key];
}
return _default;
}, {});
o.set('shortcuts', shortcuts);
/** View */
o.set('fullScreenOffset', options.fullScreenOffset === undefined ? 0 : numbers.is(options.fullScreenOffset) ? numbers.get(options.fullScreenOffset, 0) : 0);
o.set('previewTemplate', typeof options.previewTemplate === 'string' ? options.previewTemplate : null);
o.set('printTemplate', typeof options.printTemplate === 'string' ? options.printTemplate : null);
/** --- Media select */
o.set('componentInsertBehavior', ['auto', 'select', 'line', 'none'].includes(options.componentInsertBehavior) ? options.componentInsertBehavior : 'auto');
/** --- Url input protocol */
o.set('defaultUrlProtocol', typeof options.defaultUrlProtocol === 'string' ? options.defaultUrlProtocol : null);
/** External library */
// CodeMirror
const cm = o.get('externalLibs').codeMirror;
if (cm) {
o.set('codeMirror', cm);
if (cm.src) {
o.set('codeMirrorEditor', true);
} else {
console.warn('[SUNEDITOR.options.externalLibs.codeMirror.fail] The codeMirror option is set incorrectly. See: https://github.com/ARA-developer/suneditor/blob/develop/guide/external-libraries.md');
o.set('codeMirror', null);
}
}
/** Private options */
o.set('__listCommonStyle', options.__listCommonStyle || ['fontSize', 'color', 'fontFamily', 'fontWeight', 'fontStyle']);
/** --- Icons ------------------------------------------------------------------------------------------ */
const icons =
!options.icons || typeof options.icons !== 'object'
? _icons
: [_icons, options.icons].reduce((_default, _new) => {
for (const key in _new) {
_default[key] = _new[key];
}
return _default;
}, {});
o.set('icons', icons);
/** Create all used styles */
const allUsedStyles = new Set(DEFAULTS.CONTENT_STYLES.split('|'));
const _ss = options.spanStyles?.split('|') || [];
const _ls = o.get('__listCommonStyle');
const _dts = DEFAULTS.SPAN_STYLES.split('|');
for (let i = 0, len = _dts.length; i < len; i++) {
allUsedStyles.add(_dts[i]);
}
for (const _ts of Object.values(o.get('tagStyles'))) {
const _tss = _ts.split('|');
for (let i = 0, len = _tss.length; i < len; i++) {
allUsedStyles.add(_tss[i]);
}
}
for (let i = 0, len = _ss.length; i < len; i++) {
allUsedStyles.add(_ss[i]);
}
for (let i = 0, len = _ls.length; i < len; i++) {
allUsedStyles.add(_ls[i]);
}
const _aus = (typeof options.allUsedStyles === 'string' ? options.allUsedStyles.split('|') : options.allUsedStyles) || [];
for (let i = 0, len = _aus.length; i < len; i++) {
allUsedStyles.add(_aus[i]);
}
o.set('allUsedStyles', allUsedStyles);
o.set('toastMessageTime', { copy: 1500, ...options.toastMessageTime });
return {
o: o,
i: icons,
l: /** @type {Object<string, string>} */ (options.lang || _defaultLang),
v: (options.value = typeof options.value === 'string' ? options.value : null),
buttons: o.get('_rtl') ? buttonList.reverse() : buttonList,
subButtons: subButtons,
statusbarContainer: typeof options.statusbar_container === 'string' ? _d.querySelector(options.statusbar_container) : options.statusbar_container,
frameMap: frameMap,
};
}
/**
* @description Create a context object for the editor frame.
* @param {SunEditor.FrameOptions} targetOptions - `editor.frameOptions`
* @param {HTMLElement} statusbar - statusbar element
* @returns {{statusbar: HTMLElement, navigation: HTMLElement, charWrapper: HTMLElement, charCounter: HTMLElement, wordWrapper: HTMLElement, wordCounter: HTMLElement}}
*/
export function CreateStatusbar(targetOptions, statusbar) {
let navigation = null;
let charWrapper = null;
let charCounter = null;
let wordWrapper = null;
let wordCounter = null;
if (targetOptions.get('statusbar')) {
statusbar ||= dom.utils.createElement('DIV', { class: 'se-status-bar sun-editor-common' });
/** navigation */
navigation = statusbar.querySelector('.se-navigation') || dom.utils.createElement('DIV', { class: 'se-navigation sun-editor-common' });
statusbar.appendChild(navigation);
/** word counter (left) */
if (targetOptions.get('wordCounter')) {
wordWrapper = statusbar.querySelector('.se-word-counter-wrapper') || dom.utils.createElement('DIV', { class: 'se-word-counter-wrapper' });
if (targetOptions.get('wordCounter_label')) {
const wordLabel = wordWrapper.querySelector('.se-word-label') || dom.utils.createElement('SPAN', { class: 'se-word-label' });
wordLabel.textContent = targetOptions.get('wordCounter_label');
wordWrapper.appendChild(wordLabel);
}
wordCounter = wordWrapper.querySelector('.se-word-counter') || dom.utils.createElement('SPAN', { class: 'se-word-counter' });
wordCounter.textContent = '0';
wordWrapper.appendChild(wordCounter);
statusbar.appendChild(wordWrapper);
}
/** char counter (right) */
if (targetOptions.get('charCounter')) {
charWrapper = statusbar.querySelector('.se-char-counter-wrapper') || dom.utils.createElement('DIV', { class: 'se-char-counter-wrapper' });
if (targetOptions.get('wordCounter') && charWrapper.className.indexOf('se-with-word-counter') === -1) {
charWrapper.className += ' se-with-word-counter';
}
if (targetOptions.get('charCounter_label')) {
const charLabel = charWrapper.querySelector('.se-char-label') || dom.utils.createElement('SPAN', { class: 'se-char-label' });
charLabel.textContent = targetOptions.get('charCounter_label');
charWrapper.appendChild(charLabel);
}
charCounter = charWrapper.querySelector('.se-char-counter') || dom.utils.createElement('SPAN', { class: 'se-char-counter' });
charCounter.textContent = '0';
charWrapper.appendChild(charCounter);
if (targetOptions.get('charCounter_max') > 0) {
const char_max = charWrapper.querySelector('.se-char-max') || dom.utils.createElement('SPAN', { class: 'se-char-max' });
char_max.textContent = ' / ' + targetOptions.get('charCounter_max');
charWrapper.appendChild(char_max);
}
statusbar.appendChild(charWrapper);
}
}
return {
statusbar: statusbar,
navigation: /** @type {HTMLElement} */ (navigation),
charWrapper: /** @type {HTMLElement} */ (charWrapper),
charCounter: /** @type {HTMLElement} */ (charCounter),
wordWrapper: /** @type {HTMLElement} */ (wordWrapper),
wordCounter: /** @type {HTMLElement} */ (wordCounter),
};
}
/**
* @description Initialize options.
* @param {SunEditor.InitFrameOptions} o - Target options
* @param {SunEditor.InitOptions} origin - Full options
* @returns {SunEditor.FrameOptions} Processed frame options `Map`
*/
function InitFrameOptions(o, origin) {
const fo = /** @type {SunEditor.FrameOptions} */ (/** @type {unknown} */ (new Map()));
fo.set('_origin', o);
const barContainer = origin.statusbar_container;
// members
const value = o.value === undefined ? origin.value : o.value;
const placeholder = o.placeholder === undefined ? origin.placeholder : o.placeholder;
const editableFrameAttributes = o.editableFrameAttributes === undefined ? origin.editableFrameAttributes : o.editableFrameAttributes;
const width = o.width === undefined ? origin.width : o.width;
const minWidth = o.minWidth === undefined ? origin.minWidth : o.minWidth;
const maxWidth = o.maxWidth === undefined ? origin.maxWidth : o.maxWidth;
const height = o.height === undefined ? origin.height : o.height;
const minHeight = o.minHeight === undefined ? origin.minHeight : o.minHeight;
const maxHeight = o.maxHeight === undefined ? origin.maxHeight : o.maxHeight;
const editorStyle = o.editorStyle === undefined ? origin.editorStyle : o.editorStyle;
const iframe = o.iframe === undefined ? origin.iframe : o.iframe;
const iframe_fullPage = o.iframe_fullPage === undefined ? origin.iframe_fullPage : o.iframe_fullPage;
const iframe_attributes = o.iframe_attributes === undefined ? origin.iframe_attributes : o.iframe_attributes;
const iframe_cssFileName = o.iframe_cssFileName === undefined ? origin.iframe_cssFileName : o.iframe_cssFileName;
const statusbar = barContainer || o.statusbar === undefined ? origin.statusbar : o.statusbar;
const statusbar_showPathLabel = barContainer || o.statusbar_showPathLabel === undefined ? origin.statusbar_showPathLabel : o.statusbar_showPathLabel;
const statusbar_resizeEnable = barContainer ? false : o.statusbar_resizeEnable === undefined ? origin.statusbar_resizeEnable : o.statusbar_resizeEnable;
const charCounter = barContainer || o.charCounter === undefined ? origin.charCounter : o.charCounter;
const charCounter_max = barContainer || o.charCounter_max === undefined ? origin.charCounter_max : o.charCounter_max;
const charCounter_label = barContainer || o.charCounter_label === undefined ? origin.charCounter_label : o.charCounter_label;
const charCounter_type = barContainer || o.charCounter_type === undefined ? origin.charCounter_type : o.charCounter_type;
const wordCounter = barContainer || o.wordCounter === undefined ? origin.wordCounter : o.wordCounter;
const wordCounter_label = barContainer || o.wordCounter_label === undefined ? origin.wordCounter_label : o.wordCounter_label;
// value
fo.set('value', value);
fo.set('placeholder', placeholder);
fo.set('editableFrameAttributes', { spellcheck: 'false', ...editableFrameAttributes });
// styles
fo.set('width', width ? String(numbers.is(width) ? width + 'px' : width) : '100%');
fo.set('minWidth', minWidth ? String(numbers.is(minWidth) ? minWidth + 'px' : minWidth) : '');
fo.set('maxWidth', maxWidth ? String(numbers.is(maxWidth) ? maxWidth + 'px' : maxWidth) : '');
fo.set('height', height ? String(numbers.is(height) ? height + 'px' : height) : 'auto');
fo.set('minHeight', minHeight ? String(numbers.is(minHeight) ? minHeight + 'px' : minHeight) : '');
fo.set('maxHeight', maxHeight ? String(numbers.is(maxHeight) ? maxHeight + 'px' : maxHeight) : '');
fo.set('editorStyle', editorStyle);
fo.set('_defaultStyles', converter._setDefaultOptionStyle(fo, typeof editorStyle === 'string' ? editorStyle : ''));
// iframe
fo.set('iframe', !!(iframe_fullPage || iframe));
fo.set('iframe_fullPage', !!iframe_fullPage);
fo.set('iframe_attributes', iframe_attributes || {});
fo.set('iframe_cssFileName', iframe ? (typeof iframe_cssFileName === 'string' ? [iframe_cssFileName] : iframe_cssFileName) || ['suneditor'] : null);
// status bar
const hasStatusbar = statusbar === undefined ? true : !!statusbar;
fo.set('statusbar', hasStatusbar);
fo.set('statusbar_showPathLabel', !hasStatusbar ? false : typeof statusbar_showPathLabel === 'boolean' ? statusbar_showPathLabel : true);
fo.set('statusbar_resizeEnable', !hasStatusbar ? false : statusbar_resizeEnable === undefined ? true : !!statusbar_resizeEnable);
// status bar - character count
fo.set('charCounter', charCounter_max > 0 ? true : typeof charCounter === 'boolean' ? charCounter : false);
fo.set('charCounter_max', numbers.is(charCounter_max) && charCounter_max > -1 ? charCounter_max * 1 : null);
fo.set('charCounter_label', typeof charCounter_label === 'string' ? charCounter_label.trim() : null);
fo.set('charCounter_type', typeof charCounter_type === 'string' ? charCounter_type : 'char');
// status bar - word count
fo.set('wordCounter', typeof wordCounter === 'boolean' ? wordCounter : false);
fo.set('wordCounter_label', typeof wordCounter_label === 'string' ? wordCounter_label.trim() : null);
return fo;
}
/**
* @description Initialize property of `suneditor` elements
* @param {string} key - The key of the editor frame
* @param {Map<string, *>} options - Options
* @param {HTMLElement} topDiv - Top div
* @param {SunEditor.FrameOptions} targetOptions - `editor.frameOptions`
* @returns {{bottomBar: ReturnType<CreateStatusbar>, wysiwygFrame: HTMLElement, codeView: HTMLElement, markdownView: HTMLElement, placeholder: HTMLElement}}
*/
function _initTargetElements(key, options, topDiv, targetOptions) {
const editorStyles = targetOptions.get('_defaultStyles');
/** top div */
topDiv.style.cssText = editorStyles.top;
/** editor */
// wysiwyg div or iframe
const wysiwygDiv = dom.utils.createElement(!targetOptions.get('iframe') ? 'DIV' : 'IFRAME', {
class: 'se-wrapper-inner se-wrapper-wysiwyg' + (options.get('type') === 'document' ? ' se-type-document-iframe-a4' : ''),
'data-root-key': key,
});
if (!targetOptions.get('iframe')) {
wysiwygDiv.setAttribute('contenteditable', 'true');
wysiwygDiv.className += ' ' + options.get('_editableClass');
wysiwygDiv.style.cssText = editorStyles.frame + editorStyles.editor;
} else {
const iframeWW = /** @type {HTMLIFrameElement} */ (wysiwygDiv);
const frameAttrs = targetOptions.get('iframe_attributes');
// [sandbox] prop
let sandboxValue = frameAttrs.sandbox;
if (sandboxValue) {
const requiredSandbox = ['allow-same-origin'];
const userSandbox = sandboxValue.split(/\s+/);
const missingSandbox = requiredSandbox.filter((req) => !userSandbox.includes(req));
if (missingSandbox.length > 0) {
// Add missing required value
sandboxValue = userSandbox.concat(missingSandbox).join(' ');
}
} else {
sandboxValue = 'allow-same-origin';
}
// iframe [sandbox] attr
iframeWW.setAttribute('sandbox', sandboxValue);
// iframe [default border]
iframeWW.setAttribute('frameBorder', '0');
// iframe attr
for (const frameKey in frameAttrs) {
if (frameKey === 'sandbox') continue;
iframeWW.setAttribute(frameKey, frameAttrs[frameKey]);
}
iframeWW.allowFullscreen = true;
iframeWW.setAttribute('scrolling', targetOptions.get('height') === 'auto' ? 'no' : 'auto');
iframeWW.style.cssText = editorStyles.frame;
}
// textarea for code view
const textarea = dom.utils.createElement('TEXTAREA', { class: 'se-wrapper-inner se-code-viewer', style: editorStyles.frame });
// textarea for markdown view
const markdownTextarea = dom.utils.createElement('TEXTAREA', { class: 'se-wrapper-inner se-markdown-viewer', style: editorStyles.frame });
const placeholder = dom.utils.createElement('SPAN', { class: 'se-placeholder' });
if (targetOptions.get('placeholder')) {
placeholder.textContent = targetOptions.get('placeholder');
}
return {
bottomBar: CreateStatusbar(targetOptions, null),
wysiwygFrame: wysiwygDiv,
codeView: textarea,
markdownView: markdownTextarea,
placeholder: placeholder,
};
}
/**
* @description Check the `CodeMirror` option to apply the `CodeMirror` and return the `CodeMirror` element.
* @param {Map<string, *>} options Options
* @param {HTMLElement} textarea Textarea element
*/
function _checkCodeMirror(options, targetOptions, textarea) {
let cmeditor = null;
let hasCodeMirror = false;
if (options.get('codeMirrorEditor')) {
const codeMirror = options.get('codeMirror');
const cmOptions = [
{
mode: 'htmlmixed',
htmlMode: true,
lineNumbers: true,
lineWrapping: false,
},
codeMirror.options || {},
].reduce((init, option) => {
for (const key in option) {
init[key] = option[key];
}
return init;
}, {});
if (targetOptions.get('height') === 'auto') {
cmOptions.viewportMargin = Infinity;
cmOptions.height = 'auto';
}
const codeStyles = textarea.style.cssText;
const cm = codeMirror.src.fromTextArea(textarea, cmOptions);
targetOptions.set('codeMirrorEditor', cm);
cmeditor = cm.display.wrapper;
cmeditor.style.cssText = codeStyles;
hasCodeMirror = true;
}
options.set('hasCodeMirror', hasCodeMirror);
if (cmeditor) {
dom.utils.removeItem(textarea);
cmeditor.className += ' se-code-viewer-mirror';
return cmeditor;
}
return textarea;
}
/**
* @description Create blacklist
* @param {string} blacklist Blacklist
* @param {string} defaultLine `options.get('defaultLine')`
* @returns {string}
*/
function _createBlacklist(blacklist, defaultLine) {
defaultLine = defaultLine.toLowerCase();
return blacklist
.split('|')
.filter(function (v) {
if (v !== defaultLine) {
return true;
} else {
console.warn(`[SUNEDITOR.constructor.createBlacklist.warn] defaultLine("<${defaultLine}>") cannot be included in the blacklist and will be removed.`);
return false;
}
})
.join('|');
}
/**
* @description Create formats regexp object.
* @param {string} value value
* @param {string} defaultValue default value
* @param {string} blacklist blacklist
* @returns {{reg: RegExp, str: string}}
*/
function _createFormatInfo(value, defaultValue, blacklist) {
const blist = blacklist.split('|');
const str = (defaultValue + '|' + (typeof value === 'string' ? value.toLowerCase() : ''))
.replace(/^\||\|$/g, '')
.split('|')
.filter((v) => v && !blist.includes(v))
.join('|');
return {
reg: new RegExp(`^(${str})$`, 'i'),
str: str,
};
}
/**
* @description Create whitelist or blacklist.
* @param {Map<string, *>} o options
* @returns {string} whitelist
*/
function _createWhitelist(o) {
const blacklist = o.get('elementBlacklist').split('|');
const whitelist = (o.get('__defaultElementWhitelist') + '|' + o.get('elementWhitelist') + '|' + o.get('formatLine').str + '|' + o.get('formatBrLine').str + '|' + o.get('formatClosureBlock').str + '|' + o.get('formatClosureBrLine').str)
.replace(/(^\||\|$)/g, '')
.split('|')
.filter((v, i, a) => v && a.indexOf(v) === i && !blacklist.includes(v));
return whitelist.join('|');
}
/**
* @description SunEditor's default button list
* @param {boolean} isRTL `rtl`
*/
function _defaultButtons(isRTL, icons, lang) {
return {
bold: ['', lang.bold, 'bold', '', icons.bold],
underline: ['', lang.underline, 'underline', '', icons.underline],
italic: ['', lang.italic, 'italic', '', icons.italic],
strike: ['', lang.strike, 'strike', '', icons.strike],
subscript: ['', lang.subscript, 'subscript', '', icons.subscript],
superscript: ['', lang.superscript, 'superscript', '', icons.superscript],
removeFormat: ['', lang.removeFormat, 'removeFormat', '', icons.remove_format],
copyFormat: ['', lang.copyFormat, 'copyFormat', '', icons.format_paint],
indent: ['se-icon-flip-rtl', lang.indent, 'indent', '', isRTL ? icons.outdent : icons.indent],
outdent: ['se-icon-flip-rtl', lang.outdent, 'outdent', '', isRTL ? icons.indent : icons.outdent],
fullScreen: ['se-code-view-enabled se-component-enabled', lang.fullScreen, 'fullScreen', '', icons.expansion],
showBlocks: ['', lang.showBlocks, 'showBlocks', '', icons.show_blocks],
codeView: ['se-code-view-enabled se-component-enabled', lang.codeView, 'codeView', '', icons.code_view],
markdownView: ['se-code-view-enabled se-component-enabled', lang.markdownView, 'markdownView', '', icons.markdown_view],
undo: ['se-component-enabled', lang.undo, 'undo', '', icons.undo],
redo: ['se-component-enabled', lang.redo, 'redo', '', icons.redo],
preview: ['se-component-enabled', lang.preview, 'preview', '', icons.preview],
print: ['se-component-enabled', lang.print, 'print', '', icons.print],
copy: ['', lang.copy, 'copy', '', icons.copy],
dir: ['', lang[isRTL ? 'dir_ltr' : 'dir_rtl'], 'dir', '', icons[isRTL ? 'dir_ltr' : 'dir_rtl']],
dir_ltr: ['', lang.dir_ltr, 'dir_ltr', '', icons.dir_ltr],
dir_rtl: ['', lang.dir_rtl, 'dir_rtl', '', icons.dir_rtl],
finder: ['se-component-enabled', lang.find, 'finder', '', icons.finder],
save: ['se-component-enabled', lang.save, 'save', '', icons.save],
newDocument: ['se-component-enabled', lang.newDocument, 'newDocument', '', icons.new_document],
selectAll: ['se-component-enabled', lang.selectAll, 'selectAll_full', '', icons.select_all],
pageBreak: ['se-component-enabled', lang.pageBreak, 'pageBreak', '', icons.page_break],
// document type buttons
pageUp: ['se-component-enabled', lang.pageUp, 'pageUp', '', icons.page_up],
pageDown: ['se-component-enabled', lang.pageDown, 'pageDown', '', icons.page_down],
pageNavigator: ['se-component-enabled', '', 'pageNavigator', 'input', ''],
};
}
/**
* @description Create a group div containing each module
* @returns {{div: Element, ul: Eleme