suneditor
Version:
Vanilla JavaScript based WYSIWYG web editor
1,425 lines (1,307 loc) • 66.4 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;
let editorInstanceId = 0;
/**
* @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>',
);
const editorFormFieldPrefix = 'suneditor-' + ++editorInstanceId;
/** --- 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', popover: 'manual' });
editor_carrier_wrapper.appendChild(menuTray);
// focus temp element — used by the legacy `keydown` Enter path (environments without `beforeinput`)
// to force-end a mobile virtual-keyboard IME session. See ports.enterPrevent / useEnterFromBeforeInput.
const focusTemp = /** @type {HTMLInputElement} */ (
dom.utils.createElement('INPUT', {
type: 'text',
id: editorFormFieldPrefix + '-focus-temp',
class: '__se__focus__temp__',
autocomplete: 'off',
'aria-hidden': 'true',
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',
popover: 'manual',
});
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;',
popover: 'manual',
});
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', popover: 'manual' }));
const loadingBoxEl = /** @type {HTMLElement} */ (loadingBox.cloneNode(true));
loadingBoxEl.setAttribute('popover', 'manual');
editor_carrier_wrapper.appendChild(loadingBoxEl);
// drag cursor
const dragCursor = dom.utils.createElement('DIV', { class: 'se-drag-cursor', popover: 'manual' });
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 isBlockHandle = !!o.get('blockHandle');
const editor_div = dom.utils.createElement('DIV', {
class:
'se-wrapper' +
(isBlockHandle ? ' se-mode-block' : '') +
(o.get('type') === 'document' ? ' se-type-document' : '') +
(o.get('_type_options').includes('header') ? ' se-type-document-header' : ''),
});
const editorInnerW = to.get('innerWidth');
if (editorInnerW && editorInnerW !== 'auto')
editor_div.style.setProperty('--se-editor-inner-width', editorInnerW);
container.appendChild(dom.utils.createElement('DIV', { class: 'se-toolbar-shadow' }));
// init element
const initElements = _initTargetElements(editTarget.key, o, top_div, to, editorFormFieldPrefix);
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',
{
id: editorFormFieldPrefix + '-code-view-line-' + (key || 'default'),
class: 'se-code-view-line',
readonly: 'true',
autocomplete: 'off',
'aria-hidden': 'true',
tabindex: '-1',
},
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',
{
id: editorFormFieldPrefix + '-markdown-view-line-' + (key || 'default'),
class: 'se-markdown-view-line',
readonly: 'true',
autocomplete: 'off',
'aria-hidden': 'true',
tabindex: '-1',
},
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,
);
}
// block handle
let blockHandleArea = null;
if (isBlockHandle) {
blockHandleArea = dom.utils.createElement('DIV', { class: 'se-block-handle-area' });
const blockHandleGroup = dom.utils.createElement('DIV', { class: 'se-block-handle', popover: 'manual' });
const blockHandlePlus = dom.utils.createElement(
'DIV',
{ class: 'se-block-handle-btn se-block-handle-plus' },
icons.plus,
);
const blockHandleDrag = dom.utils.createElement(
'DIV',
{ class: 'se-block-handle-btn se-block-handle-drag' },
icons.dragHandle,
);
blockHandleGroup.appendChild(blockHandlePlus);
blockHandleGroup.appendChild(blockHandleDrag);
blockHandleArea.appendChild(blockHandleGroup);
}
// 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,
blockHandleArea,
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);
}
}
/**
* @description Append tooltip span
* @param {Element} tooptipBtn
* @param {boolean} shift
* @param {string} shortcut
*/
function _addTooltip(tooptipBtn, shift, shortcut) {
const tooltip = dom.utils.createElement(
'SPAN',
{ class: 'se-shortcut' },
env.cmdIcon + (shift ? env.shiftIcon : '') + '+<span class="se-shortcut-key">' + shortcut + '</span>',
);
const prevTooltip = tooptipBtn.querySelector('.se-shortcut');
if (prevTooltip) {
tooptipBtn.replaceChild(tooltip, prevTooltip);
} else {
tooptipBtn.appendChild(tooltip);
}
}
/**
* @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(
'blockHandle',
options.blockHandle ? (typeof options.blockHandle === 'object' ? options.blockHandle : {}) : null,
);
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('_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_innerWidth',
options.toolbar_innerWidth
? numbers.is(options.toolbar_innerWidth)
? options.toolbar_innerWidth + 'px'
: options.toolbar_innerWidth
: '',
);
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 _ls = o.get('__listCommonStyle');
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 = _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 placeholder_line = o.placeholder_line === undefined ? origin.placeholder_line : o.placeholder_line;
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 innerWidth = o.innerWidth === undefined ? origin.innerWidth : o.innerWidth;
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('placeholder_line', placeholder_line);
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('innerWidth', innerWidth ? String(numbers.is(innerWidth) ? innerWidth + 'px' : innerWidth) : '');
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`
* @param {string} formFieldPrefix - Prefix for generated form field ids
* @returns {{bottomBar: ReturnType<CreateStatusbar>, wysiwygFrame: HTMLElement, codeView: HTMLElement, markdownView: HTMLElement, placeholder: HTMLElement}}
*/
function _initTargetElements(key, options, topDiv, targetOptions, formFieldPrefix) {
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');
if (options.get('_rtl')) wysiwygDiv.dir = 'rtl';
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', 'allow-scripts'];
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 allow-scripts';
}
// 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', {
id: formFieldPrefix + '-code-viewer-' + (key || 'default'),
class: 'se-wrapper-inner se-code-viewer',
style: editorStyles.frame,
autocomplete: 'off',
});
// textarea for markdown view
const markdownTextarea = dom.utils.createElement('TEXTAREA', {
id: formFieldPrefix + '-markdown-viewer-' + (key || 'default'),
class: 'se-wrapper-inner se-markdown-viewer',
style: editorStyles.frame,
autocomplete: 'off',
});
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: RegE