UNPKG

suneditor

Version:

Vanilla JavaScript WYSIWYG web editor (2.x legacy version, actively maintained)

902 lines (812 loc) 52 kB
/* * wysiwyg web editor * * suneditor.js * Copyright 2017 JiHong Lee. * MIT license. */ 'use strict'; import _icons from '../assets/defaultIcons'; import _defaultLang from '../lang/en'; import util from './util'; export default { /** * @description document create * @param {Element} element Textarea * @param {Object} options Options * @returns {Object} */ init: function (element, options) { if (typeof options !== 'object') options = {}; const doc = document; /** --- init options --- */ this._initOptions(element, options); // suneditor div const top_div = doc.createElement('DIV'); top_div.className = 'sun-editor' + (options.rtl ? ' se-rtl' : ''); if (element.id) top_div.id = 'suneditor_' + element.id; // relative div const relative = doc.createElement('DIV'); relative.className = 'se-container'; // toolbar const tool_bar = this._createToolBar(doc, options.buttonList, options.plugins, options); const toolbarShadow = tool_bar.element.cloneNode(false); toolbarShadow.className += ' se-toolbar-shadow'; tool_bar.element.style.visibility = 'hidden'; if (tool_bar.pluginCallButtons.math) this._checkKatexMath(options.katex); const arrow = doc.createElement('DIV'); arrow.className = 'se-arrow'; // sticky toolbar dummy const sticky_dummy = doc.createElement('DIV'); sticky_dummy.className = 'se-toolbar-sticky-dummy'; // inner editor div const editor_div = doc.createElement('DIV'); editor_div.className = 'se-wrapper'; /** --- init elements and create bottom bar --- */ const initElements = this._initElements(options, top_div, tool_bar.element, arrow); const bottomBar = initElements.bottomBar; const wysiwyg_div = initElements.wysiwygFrame; const placeholder_span = initElements.placeholder; let textarea = initElements.codeView; // resizing bar const resizing_bar = bottomBar.resizingBar; const navigation = bottomBar.navigation; const char_wrapper = bottomBar.charWrapper; const char_counter = bottomBar.charCounter; // loading box const loading_box = doc.createElement('DIV'); loading_box.className = 'se-loading-box sun-editor-common'; loading_box.innerHTML = '<div class="se-loading-effect"></div>'; // enter line const line_breaker = doc.createElement('DIV'); line_breaker.className = 'se-line-breaker'; line_breaker.innerHTML = '<button class="se-btn">' + options.icons.line_break + '</button>'; const line_breaker_t = doc.createElement('DIV'); line_breaker_t.className += 'se-line-breaker-component'; const line_breaker_b = line_breaker_t.cloneNode(true); line_breaker_t.innerHTML = line_breaker_b.innerHTML = options.icons.line_break; // resize operation background const resize_back = doc.createElement('DIV'); resize_back.className = 'se-resizing-back'; /// focus temp const focusTemp = doc.createElement('INPUT'); focusTemp.tabIndex = -1; focusTemp.style.cssText = 'position: fixed !important; top: -10000px !important; display: block !important; width: 0 !important; height: 0 !important; margin: 0 !important; padding: 0 !important;'; // toolbar container const toolbarContainer = options.toolbarContainer; if (toolbarContainer) { toolbarContainer.appendChild(tool_bar.element); toolbarContainer.appendChild(toolbarShadow); } // resizingbar const resizingBarContainer = options.resizingBarContainer; if (resizing_bar && resizingBarContainer) resizingBarContainer.appendChild(resizing_bar); /** append html */ editor_div.appendChild(textarea); if (placeholder_span) editor_div.appendChild(placeholder_span); if (!toolbarContainer) { relative.appendChild(tool_bar.element); relative.appendChild(toolbarShadow); } relative.appendChild(sticky_dummy); relative.appendChild(editor_div); relative.appendChild(resize_back); relative.appendChild(loading_box); relative.appendChild(line_breaker); relative.appendChild(line_breaker_t); relative.appendChild(line_breaker_b); relative.appendChild(focusTemp); if (resizing_bar && !resizingBarContainer) relative.appendChild(resizing_bar); top_div.appendChild(relative); textarea = this._checkCodeMirror(options, textarea); return { constructed: { _top: top_div, _relative: relative, _toolBar: tool_bar.element, _toolbarShadow: toolbarShadow, _menuTray: tool_bar._menuTray, _editorArea: editor_div, _wysiwygArea: wysiwyg_div, _codeArea: textarea, _placeholder: placeholder_span, _resizingBar: resizing_bar, _navigation: navigation, _charWrapper: char_wrapper, _charCounter: char_counter, _loading: loading_box, _lineBreaker: line_breaker, _lineBreaker_t: line_breaker_t, _lineBreaker_b: line_breaker_b, _resizeBack: resize_back, _stickyDummy: sticky_dummy, _arrow: arrow, _focusTemp: focusTemp }, options: options, plugins: tool_bar.plugins, pluginCallButtons: tool_bar.pluginCallButtons, _responsiveButtons: tool_bar.responsiveButtons }; }, /** * @description Check the CodeMirror option to apply the CodeMirror and return the CodeMirror element. * @param {Object} options options * @param {Element} textarea textarea element * @private */ _checkCodeMirror: function (options, textarea) { if (options.codeMirror) { const cmOptions = [{ mode: 'htmlmixed', htmlMode: true, lineNumbers: true, lineWrapping: true }, (options.codeMirror.options || {})].reduce(function (init, option) { for (let key in option) { if (util.hasOwn(option, key)) init[key] = option[key]; } return init; }, {}); if (options.height === 'auto') { cmOptions.viewportMargin = Infinity; cmOptions.height = 'auto'; } const cm = options.codeMirror.src.fromTextArea(textarea, cmOptions); cm.display.wrapper.style.cssText = textarea.style.cssText; options.codeMirrorEditor = cm; textarea = cm.display.wrapper; textarea.className += ' se-wrapper-code-mirror'; } return textarea; }, /** * @description Check for a katex object. * @param {Object} katex katex object * @private */ _checkKatexMath: function (katex) { if (!katex) throw Error('[SUNEDITOR.create.fail] To use the math button you need to add a "katex" object to the options.'); const katexOptions = [{ throwOnError: false, }, (katex.options || {})].reduce(function (init, option) { for (let key in option) { if (util.hasOwn(option, key)) init[key] = option[key]; } return init; }, {}); katex.options = katexOptions; }, /** * @description Add or reset options * @param {Object} mergeOptions New options property * @param {Object} context Context object of core * @param {Object} originOptions Origin options * @returns {Object} pluginCallButtons * @private */ _setOptions: function (mergeOptions, context, originOptions) { this._initOptions(context.element.originElement, mergeOptions); const el = context.element; const relative = el.relative; const editorArea = el.editorArea; const isNewToolbarContainer = mergeOptions.toolbarContainer && mergeOptions.toolbarContainer !== originOptions.toolbarContainer; const isNewToolbar = mergeOptions.lang !== originOptions.lang || mergeOptions.buttonList !== originOptions.buttonList || mergeOptions.mode !== originOptions.mode || isNewToolbarContainer; const tool_bar = this._createToolBar(document, (isNewToolbar ? mergeOptions.buttonList : originOptions.buttonList), mergeOptions.plugins, mergeOptions); if (tool_bar.pluginCallButtons.math) this._checkKatexMath(mergeOptions.katex); const arrow = document.createElement('DIV'); arrow.className = 'se-arrow'; if (isNewToolbar) { tool_bar.element.style.visibility = 'hidden'; // toolbar container if (isNewToolbarContainer) { mergeOptions.toolbarContainer.appendChild(tool_bar.element); el.toolbar.parentElement.removeChild(el.toolbar); } else { el.toolbar.parentElement.replaceChild(tool_bar.element, el.toolbar); } el.toolbar = tool_bar.element; el._menuTray = tool_bar._menuTray; el._arrow = arrow; } const initElements = this._initElements(mergeOptions, el.topArea, (isNewToolbar ? tool_bar.element : el.toolbar), arrow); const bottomBar = initElements.bottomBar; const wysiwygFrame = initElements.wysiwygFrame; const placeholder_span = initElements.placeholder; let code = initElements.codeView; if (el.resizingBar) util.removeItem(el.resizingBar); if (bottomBar.resizingBar) { if (mergeOptions.resizingBarContainer && mergeOptions.resizingBarContainer !== originOptions.resizingBarContainer) { mergeOptions.resizingBarContainer.appendChild(bottomBar.resizingBar); } else { relative.appendChild(bottomBar.resizingBar); } } editorArea.innerHTML = ''; editorArea.appendChild(code); if (placeholder_span) editorArea.appendChild(placeholder_span); code = this._checkCodeMirror(mergeOptions, code); el.resizingBar = bottomBar.resizingBar; el.navigation = bottomBar.navigation; el.charWrapper = bottomBar.charWrapper; el.charCounter = bottomBar.charCounter; el.wysiwygFrame = wysiwygFrame; el.code = code; el.placeholder = placeholder_span; if (mergeOptions.rtl) util.addClass(el.topArea, 'se-rtl'); else util.removeClass(el.topArea, 'se-rtl'); return { callButtons: tool_bar.pluginCallButtons, plugins: tool_bar.plugins, toolbar: tool_bar }; }, /** * @description Initialize property of suneditor elements * @param {Object} options Options * @param {Element} topDiv Suneditor top div * @param {Element} toolBar Tool bar * @param {Element} toolBarArrow Tool bar arrow (balloon editor) * @returns {Object} Bottom bar elements (resizingBar, navigation, charWrapper, charCounter) * @private */ _initElements: function (options, topDiv, toolBar, toolBarArrow) { /** top div */ topDiv.style.cssText = options._editorStyles.top; /** toolbar */ if (/inline/i.test(options.mode)) { toolBar.className += ' se-toolbar-inline'; toolBar.style.width = options.toolbarWidth; } else if (/balloon/i.test(options.mode)) { toolBar.className += ' se-toolbar-balloon'; toolBar.style.width = options.toolbarWidth; toolBar.appendChild(toolBarArrow); } /** editor */ // wysiwyg div or iframe const wysiwygDiv = document.createElement(!options.iframe ? 'DIV' : 'IFRAME'); wysiwygDiv.className = 'se-wrapper-inner se-wrapper-wysiwyg'; if (!options.iframe) { wysiwygDiv.setAttribute('contenteditable', true); wysiwygDiv.setAttribute('autocorrect', "off"); wysiwygDiv.setAttribute('scrolling', 'auto'); for (let key in options.iframeAttributes) { wysiwygDiv.setAttribute(key, options.iframeAttributes[key]); } wysiwygDiv.className += ' ' + options._editableClass; wysiwygDiv.style.cssText = options._editorStyles.frame + options._editorStyles.editor; wysiwygDiv.className += options.className; } else { wysiwygDiv.allowFullscreen = true; wysiwygDiv.frameBorder = 0; wysiwygDiv.style.cssText = options._editorStyles.frame; wysiwygDiv.className += options.className; } // textarea for code view const textarea = document.createElement('TEXTAREA'); textarea.className = 'se-wrapper-inner se-wrapper-code' + options.className; textarea.style.cssText = options._editorStyles.frame; textarea.style.display = 'none'; if (options.height === 'auto') textarea.style.overflow = 'hidden'; /** resize bar */ let resizingBar = null; let navigation = null; let charWrapper = null; let charCounter = null; if (options.resizingBar) { resizingBar = document.createElement('DIV'); resizingBar.className = 'se-resizing-bar sun-editor-common'; /** navigation */ navigation = document.createElement('DIV'); navigation.className = 'se-navigation sun-editor-common'; resizingBar.appendChild(navigation); /** char counter */ if (options.charCounter) { charWrapper = document.createElement('DIV'); charWrapper.className = 'se-char-counter-wrapper'; if (options.charCounterLabel) { const charLabel = document.createElement('SPAN'); charLabel.className = 'se-char-label'; charLabel.textContent = options.charCounterLabel; charWrapper.appendChild(charLabel); } charCounter = document.createElement('SPAN'); charCounter.className = 'se-char-counter'; charCounter.textContent = '0'; charWrapper.appendChild(charCounter); if (options.maxCharCount > 0) { const char_max = document.createElement('SPAN'); char_max.textContent = ' / ' + options.maxCharCount; charWrapper.appendChild(char_max); } resizingBar.appendChild(charWrapper); } } let placeholder = null; if (options.placeholder) { placeholder = document.createElement('SPAN'); placeholder.className = 'se-placeholder'; placeholder.innerText = options.placeholder; } return { bottomBar: { resizingBar: resizingBar, navigation: navigation, charWrapper: charWrapper, charCounter: charCounter }, wysiwygFrame: wysiwygDiv, codeView: textarea, placeholder: placeholder }; }, /** * @description Initialize options * @param {Element} element Options object * @param {Object} options Options object * @private */ _initOptions: function (element, options) { const plugins = {}; if (options.plugins) { const _plugins = options.plugins; const pluginsValues = _plugins.length ? _plugins : Object.keys(_plugins).map(function(name) { return _plugins[name]; }); for (let i = 0, len = pluginsValues.length, p; i < len; i++) { p = pluginsValues[i].default || pluginsValues[i]; plugins[p.name] = p; } } options.plugins = plugins; /** Values */ options.strictMode = options.strictMode !== false; options.strictHTMLValidation = options.strictHTMLValidation !== false; options.lang = options.lang || _defaultLang; options.value = typeof options.value === 'string' ? options.value : null; options.allowedClassNames = new util._w.RegExp((options.allowedClassNames && typeof options.allowedClassNames === 'string' ? options.allowedClassNames + '|' : '') + '^__se__|se-|katex'); options.historyStackDelayTime = typeof options.historyStackDelayTime === 'number' ? options.historyStackDelayTime : 400; options.frameAttrbutes = options.frameAttrbutes || {}; // tag style options.defaultTag = typeof options.defaultTag === 'string' && options.defaultTag.length > 0 ? options.defaultTag : 'p'; const textTags = options.textTags = [{bold: 'STRONG', underline: 'U', italic: 'EM', strike: 'DEL', sub: 'SUB', sup: 'SUP'}, (options.textTags || {})].reduce(function (_default, _new) { for (let key in _new) { _default[key] = _new[key]; } return _default; }, {}); options._textTagsMap = { 'strong': textTags.bold.toLowerCase(), 'b': textTags.bold.toLowerCase(), 'u': textTags.underline.toLowerCase(), 'ins': textTags.underline.toLowerCase(), 'em': textTags.italic.toLowerCase(), 'i': textTags.italic.toLowerCase(), 'del': textTags.strike.toLowerCase(), 'strike': textTags.strike.toLowerCase(), 's': textTags.strike.toLowerCase(), 'sub': textTags.sub.toLowerCase(), 'sup': textTags.sup.toLowerCase() }; options._defaultCommand = { bold: options.textTags.bold, underline: options.textTags.underline, italic: options.textTags.italic, strike: options.textTags.strike, subscript: options.textTags.sub, superscript: options.textTags.sup }; /** Whitelist, Blacklist */ options.__allowedScriptTag = options.__allowedScriptTag === true; const whitelist = 'br|p|div|pre|blockquote|h1|h2|h3|h4|h5|h6|ol|ul|li|hr|figure|figcaption|img|iframe|audio|video|source|table|thead|tbody|tr|th|td|a|b|strong|var|i|em|u|ins|s|span|strike|del|sub|sup|code|svg|path|details|summary'; // tags options.tagsBlacklist = options.tagsBlacklist || ''; options._defaultTagsWhitelist = (typeof options._defaultTagsWhitelist === 'string' ? options._defaultTagsWhitelist : whitelist) + (options.__allowedScriptTag ? '|script' : ''); options._editorTagsWhitelist = options.addTagsWhitelist === '*' ? '*' : this._setWhitelist(options._defaultTagsWhitelist + (typeof options.addTagsWhitelist === 'string' && options.addTagsWhitelist.length > 0 ? '|' + options.addTagsWhitelist : ''), options.tagsBlacklist); // paste tags options.pasteTagsBlacklist = options.tagsBlacklist + (options.tagsBlacklist && options.pasteTagsBlacklist ? ('|' + options.pasteTagsBlacklist) : (options.pasteTagsBlacklist || '')); options.pasteTagsWhitelist = options.pasteTagsWhitelist === '*' ? '*' : this._setWhitelist(typeof options.pasteTagsWhitelist === 'string' ? options.pasteTagsWhitelist : options._editorTagsWhitelist, options.pasteTagsBlacklist); // tag attributes options.attributesWhitelist = (!options.attributesWhitelist || typeof options.attributesWhitelist !== 'object') ? null : options.attributesWhitelist; options.attributesBlacklist = (!options.attributesBlacklist || typeof options.attributesBlacklist !== 'object') ? null : options.attributesBlacklist; /** Layout */ options.mode = options.mode || 'classic'; // classic, inline, balloon, balloon-always options.rtl = !!options.rtl; options.lineAttrReset = ['id'].concat((options.lineAttrReset && typeof options.lineAttrReset === 'string' ? options.lineAttrReset.toLowerCase().split('|') : [])); options._editableClass = 'sun-editor-editable' + (options.rtl ? ' se-rtl' : ''); options._printClass = typeof options._printClass === 'string' ? options._printClass : null; options.toolbarWidth = options.toolbarWidth ? (util.isNumber(options.toolbarWidth) ? options.toolbarWidth + 'px' : options.toolbarWidth) : 'auto'; options.toolbarContainer = typeof options.toolbarContainer === 'string' ? document.querySelector(options.toolbarContainer) : options.toolbarContainer; options.stickyToolbar = (/balloon/i.test(options.mode) || !!options.toolbarContainer) ? -1 : options.stickyToolbar === undefined ? 0 : (/^\d+/.test(options.stickyToolbar) ? util.getNumber(options.stickyToolbar, 0) : -1); options.hideToolbar = !!options.hideToolbar; options.fullScreenOffset = options.fullScreenOffset === undefined ? 0 : (/^\d+/.test(options.fullScreenOffset) ? util.getNumber(options.fullScreenOffset, 0) : 0); options.fullPage = !!options.fullPage; options.iframe = options.fullPage || !!options.iframe; options.iframeAttributes = options.iframeAttributes || {}; options.iframeCSSFileName = options.iframe ? typeof options.iframeCSSFileName === 'string' ? [options.iframeCSSFileName] : (options.iframeCSSFileName || ['suneditor']) : null; options.previewTemplate = typeof options.previewTemplate === 'string' ? options.previewTemplate : null; options.printTemplate = typeof options.printTemplate === 'string' ? options.printTemplate : null; /** CodeMirror object */ options.codeMirror = options.codeMirror ? options.codeMirror.src ? options.codeMirror : {src: options.codeMirror} : null; /** katex object (Math plugin) */ options.katex = options.katex ? options.katex.src ? options.katex : {src: options.katex} : null; options.mathFontSize = !!options.mathFontSize ? options.mathFontSize : [ {text: '1', value: '1em'}, {text: '1.5', value: '1.5em'}, {text: '2', value: '2em'}, {text: '2.5', value: '2.5em'} ]; /** Display */ options.position = typeof options.position === 'string' ? options.position : null; options.display = options.display || (element.style.display === 'none' || !element.style.display ? 'block' : element.style.display); options.popupDisplay = options.popupDisplay || 'full'; /** Bottom resizing bar */ options.resizingBar = options.resizingBar === undefined ? (/inline|balloon/i.test(options.mode) ? false : true) : options.resizingBar; options.showPathLabel = !options.resizingBar ? false : typeof options.showPathLabel === 'boolean' ? options.showPathLabel : true; options.resizeEnable = options.resizeEnable === undefined ? true : !!options.resizeEnable; options.resizingBarContainer = typeof options.resizingBarContainer === 'string' ? document.querySelector(options.resizingBarContainer) : options.resizingBarContainer; /** Character count */ options.charCounter = options.maxCharCount > 0 ? true : typeof options.charCounter === 'boolean' ? options.charCounter : false; options.charCounterType = typeof options.charCounterType === 'string' ? options.charCounterType : 'char'; options.charCounterLabel = typeof options.charCounterLabel === 'string' ? options.charCounterLabel.trim() : null; options.maxCharCount = util.isNumber(options.maxCharCount) && options.maxCharCount > -1 ? options.maxCharCount * 1 : null; /** Width size */ options.width = options.width ? (util.isNumber(options.width) ? options.width + 'px' : options.width) : (element.clientWidth ? element.clientWidth + 'px' : '100%'); options.minWidth = (util.isNumber(options.minWidth) ? options.minWidth + 'px' : options.minWidth) || ''; options.maxWidth = (util.isNumber(options.maxWidth) ? options.maxWidth + 'px' : options.maxWidth) || ''; /** Height size */ options.height = options.height ? (util.isNumber(options.height) ? options.height + 'px' : options.height) : (element.clientHeight ? element.clientHeight + 'px' : 'auto'); options.minHeight = (util.isNumber(options.minHeight) ? options.minHeight + 'px' : options.minHeight) || ''; options.maxHeight = (util.isNumber(options.maxHeight) ? options.maxHeight + 'px' : options.maxHeight) || ''; /** Editing area */ options.className = (typeof options.className === 'string' && options.className.length > 0) ? ' ' + options.className : ''; options.defaultStyle = typeof options.defaultStyle === 'string' ? options.defaultStyle : ''; /** Defining menu items */ options.font = !options.font ? ['Arial', 'Comic Sans MS', 'Courier New', 'Impact', 'Georgia', 'tahoma', 'Trebuchet MS', 'Verdana'] : options.font; options.fontSize = !options.fontSize ? null : options.fontSize; options.formats = !options.formats ? null : options.formats; options.colorList = !options.colorList ? null : options.colorList; options.lineHeights = !options.lineHeights ? null : options.lineHeights; options.paragraphStyles = !options.paragraphStyles ? null : options.paragraphStyles; options.textStyles = !options.textStyles ? null : options.textStyles; options.fontSizeUnit = typeof options.fontSizeUnit === 'string' ? (options.fontSizeUnit.trim().toLowerCase() || 'px') : 'px'; options.alignItems = typeof options.alignItems === 'object' ? options.alignItems : (options.rtl ? ['right', 'center', 'left', 'justify'] : ['left', 'center', 'right', 'justify']); /** Image */ options.imageResizing = options.imageResizing === undefined ? true : options.imageResizing; options.imageHeightShow = options.imageHeightShow === undefined ? true : !!options.imageHeightShow; options.imageAlignShow = options.imageAlignShow === undefined ? true : !!options.imageAlignShow; options.imageWidth = !options.imageWidth ? 'auto' : util.isNumber(options.imageWidth) ? options.imageWidth + 'px' : options.imageWidth; options.imageHeight = !options.imageHeight ? 'auto' : util.isNumber(options.imageHeight) ? options.imageHeight + 'px' : options.imageHeight; options.imageSizeOnlyPercentage = !!options.imageSizeOnlyPercentage; options._imageSizeUnit = options.imageSizeOnlyPercentage ? '%' : 'px'; options.imageRotation = options.imageRotation !== undefined ? options.imageRotation : !(options.imageSizeOnlyPercentage || !options.imageHeightShow); options.imageFileInput = options.imageFileInput === undefined ? true : options.imageFileInput; options.imageUrlInput = (options.imageUrlInput === undefined || !options.imageFileInput) ? true : options.imageUrlInput; options.imageUploadHeader = options.imageUploadHeader || null; options.imageUploadUrl = typeof options.imageUploadUrl === 'string' ? options.imageUploadUrl : null; options.imageUploadSizeLimit = /\d+/.test(options.imageUploadSizeLimit) ? util.getNumber(options.imageUploadSizeLimit, 0) : null; options.imageMultipleFile = !!options.imageMultipleFile; options.imageAccept = (typeof options.imageAccept !== 'string' || options.imageAccept.trim() === "*") ? 'image/*' : options.imageAccept.trim() || 'image/*'; /** Image - image gallery */ options.imageGalleryData = options.imageGalleryData || null; options.imageGalleryUrl = typeof options.imageGalleryUrl === 'string' ? options.imageGalleryUrl : null; options.imageGalleryHeader = options.imageGalleryHeader || null; /** Video */ options.videoResizing = options.videoResizing === undefined ? true : options.videoResizing; options.videoHeightShow = options.videoHeightShow === undefined ? true : !!options.videoHeightShow; options.videoAlignShow = options.videoAlignShow === undefined ? true : !!options.videoAlignShow; options.videoRatioShow = options.videoRatioShow === undefined ? true : !!options.videoRatioShow; options.videoWidth = !options.videoWidth || !util.getNumber(options.videoWidth, 0) ? '' : util.isNumber(options.videoWidth) ? options.videoWidth + 'px' : options.videoWidth; options.videoHeight = !options.videoHeight || !util.getNumber(options.videoHeight, 0) ? '' : util.isNumber(options.videoHeight) ? options.videoHeight + 'px' : options.videoHeight; options.videoSizeOnlyPercentage = !!options.videoSizeOnlyPercentage; options._videoSizeUnit = options.videoSizeOnlyPercentage ? '%' : 'px'; options.videoRotation = options.videoRotation !== undefined ? options.videoRotation : !(options.videoSizeOnlyPercentage || !options.videoHeightShow); options.videoRatio = (util.getNumber(options.videoRatio, 4) || 0.5625); options.videoRatioList = !options.videoRatioList ? null : options.videoRatioList; options.youtubeQuery = (options.youtubeQuery || '').replace('?', ''); options.vimeoQuery = (options.vimeoQuery || '').replace('?', ''); options.videoFileInput = !!options.videoFileInput; options.videoUrlInput = (options.videoUrlInput === undefined || !options.videoFileInput) ? true : options.videoUrlInput; options.videoUploadHeader = options.videoUploadHeader || null; options.videoUploadUrl = typeof options.videoUploadUrl === 'string' ? options.videoUploadUrl : null; options.videoUploadSizeLimit = /\d+/.test(options.videoUploadSizeLimit) ? util.getNumber(options.videoUploadSizeLimit, 0) : null; options.videoMultipleFile = !!options.videoMultipleFile; options.videoTagAttrs = options.videoTagAttrs || null; options.videoIframeAttrs = options.videoIframeAttrs || null; options.videoAccept = (typeof options.videoAccept !== 'string' || options.videoAccept.trim() === "*") ? 'video/*' : options.videoAccept.trim() || 'video/*'; /** Audio */ options.audioWidth = !options.audioWidth ? '' : util.isNumber(options.audioWidth) ? options.audioWidth + 'px' : options.audioWidth; options.audioHeight = !options.audioHeight ? '' : util.isNumber(options.audioHeight) ? options.audioHeight + 'px' : options.audioHeight; options.audioFileInput = !!options.audioFileInput; options.audioUrlInput = (options.audioUrlInput === undefined || !options.audioFileInput) ? true : options.audioUrlInput; options.audioUploadHeader = options.audioUploadHeader || null; options.audioUploadUrl = typeof options.audioUploadUrl === 'string' ? options.audioUploadUrl : null; options.audioUploadSizeLimit = /\d+/.test(options.audioUploadSizeLimit) ? util.getNumber(options.audioUploadSizeLimit, 0) : null; options.audioMultipleFile = !!options.audioMultipleFile; options.audioTagAttrs = options.audioTagAttrs || null; options.audioAccept = (typeof options.audioAccept !== 'string' || options.audioAccept.trim() === "*") ? 'audio/*' : options.audioAccept.trim() || 'audio/*'; /** Table */ options.tableCellControllerPosition = typeof options.tableCellControllerPosition === 'string' ? options.tableCellControllerPosition.toLowerCase() : 'cell'; /** Link */ options.linkTargetNewWindow = !!options.linkTargetNewWindow; options.linkProtocol = typeof options.linkProtocol === 'string' ? options.linkProtocol : null; options.linkRel = Array.isArray(options.linkRel) ? options.linkRel : []; options.linkRelDefault = options.linkRelDefault || {}; /** HR */ // options.hrItems = options.hrItems; /** Key actions */ options.tabDisable = !!options.tabDisable; options.shortcutsDisable = Array.isArray(options.shortcutsDisable) ? options.shortcutsDisable : []; options.shortcutsHint = options.shortcutsHint === undefined ? true : !!options.shortcutsHint; /** Defining save button */ options.callBackSave = !options.callBackSave ? null : options.callBackSave; /** Templates Array */ options.templates = !options.templates ? null : options.templates; /** ETC */ options.placeholder = typeof options.placeholder === 'string' ? options.placeholder : null; options.mediaAutoSelect = options.mediaAutoSelect === undefined ? true : !!options.mediaAutoSelect; /** Buttons */ options.buttonList = !!options.buttonList ? options.buttonList : [ ['undo', 'redo'], ['bold', 'underline', 'italic', 'strike', 'subscript', 'superscript'], ['removeFormat'], ['outdent', 'indent'], ['fullScreen', 'showBlocks', 'codeView'], ['preview', 'print'] ]; /** RTL - buttons */ if (options.rtl) { options.buttonList = options.buttonList.reverse(); } /** --- Define icons --- */ // custom icons options.icons = (!options.icons || typeof options.icons !== 'object') ? _icons : [_icons, options.icons].reduce(function (_default, _new) { for (let key in _new) { if (util.hasOwn(_new, key)) _default[key] = _new[key]; } return _default; }, {}); // rtl icons options.icons = !options.rtl ? options.icons : [options.icons, options.icons.rtl].reduce(function (_default, _new) { for (let key in _new) { if (util.hasOwn(_new, key)) _default[key] = _new[key]; } return _default; }, {}); /** Private options */ options.__listCommonStyle = options.__listCommonStyle || ['fontSize', 'color', 'fontFamily', 'fontWeight', 'fontStyle']; /** _init options */ options._editorStyles = util._setDefaultOptionStyle(options, options.defaultStyle); }, _setWhitelist: function (whitelist, blacklist) { if (typeof blacklist !== 'string') return whitelist; blacklist = blacklist.split('|'); whitelist = whitelist.split('|'); for (let i = 0, len = blacklist.length, index; i < len; i++) { index = whitelist.indexOf(blacklist[i]); if (index > -1) whitelist.splice(index, 1); } return whitelist.join('|'); }, /** * @description Suneditor's Default button list * @param {Object} options options * @private */ _defaultButtons: function (options) { const icons = options.icons; const lang = options.lang; const cmd = util.isOSX_IOS ? '⌘' : 'CTRL'; const addShift = util.isOSX_IOS ? '⇧' : '+SHIFT'; const shortcutsDisable = !options.shortcutsHint ? ['bold', 'strike', 'underline', 'italic', 'undo', 'indent', 'save'] : options.shortcutsDisable; const indentKey = options.rtl ? ['[',']'] : [']','[']; const indentIcon = options.rtl ? [icons.outdent, icons.indent] : [icons.indent, icons.outdent]; return { /** default command */ bold: ['', lang.toolbar.bold + '<span class="se-shortcut">' + (shortcutsDisable.indexOf('bold') > -1 ? '' : cmd + '+<span class="se-shortcut-key">B</span>') + '</span>', 'bold', '', icons.bold], underline: ['', lang.toolbar.underline + '<span class="se-shortcut">' + (shortcutsDisable.indexOf('underline') > -1 ? '' : cmd + '+<span class="se-shortcut-key">U</span>') + '</span>', 'underline', '', icons.underline], italic: ['', lang.toolbar.italic + '<span class="se-shortcut">' + (shortcutsDisable.indexOf('italic') > -1 ? '' : cmd + '+<span class="se-shortcut-key">I</span>') + '</span>', 'italic', '', icons.italic], strike: ['', lang.toolbar.strike + '<span class="se-shortcut">' + (shortcutsDisable.indexOf('strike') > -1 ? '' : cmd + addShift + '+<span class="se-shortcut-key">S</span>') + '</span>', 'strike', '', icons.strike], subscript: ['', lang.toolbar.subscript, 'SUB', '', icons.subscript], superscript: ['', lang.toolbar.superscript, 'SUP', '', icons.superscript], removeFormat: ['', lang.toolbar.removeFormat, 'removeFormat', '', icons.erase], indent: ['', lang.toolbar.indent + '<span class="se-shortcut">' + (shortcutsDisable.indexOf('indent') > -1 ? '' : cmd + '+<span class="se-shortcut-key">' + indentKey[0] + '</span>') + '</span>', 'indent', '', indentIcon[0]], outdent: ['', lang.toolbar.outdent + '<span class="se-shortcut">' + (shortcutsDisable.indexOf('indent') > -1 ? '' : cmd + '+<span class="se-shortcut-key">' + indentKey[1] + '</span>') + '</span>', 'outdent', '', indentIcon[1]], fullScreen: ['se-code-view-enabled se-resizing-enabled', lang.toolbar.fullScreen, 'fullScreen', '', icons.expansion], showBlocks: ['', lang.toolbar.showBlocks, 'showBlocks', '', icons.show_blocks], codeView: ['se-code-view-enabled se-resizing-enabled', lang.toolbar.codeView, 'codeView', '', icons.code_view], undo: ['', lang.toolbar.undo + '<span class="se-shortcut">' + (shortcutsDisable.indexOf('undo') > -1 ? '' : cmd + '+<span class="se-shortcut-key">Z</span>') + '</span>', 'undo', '', icons.undo], redo: ['', lang.toolbar.redo + '<span class="se-shortcut">' + (shortcutsDisable.indexOf('undo') > -1 ? '' : cmd + '+<span class="se-shortcut-key">Y</span> / ' + cmd + addShift + '+<span class="se-shortcut-key">Z</span>') + '</span>', 'redo', '', icons.redo], preview: ['se-resizing-enabled', lang.toolbar.preview, 'preview', '', icons.preview], print: ['se-resizing-enabled', lang.toolbar.print, 'print', '', icons.print], dir: ['', lang.toolbar[options.rtl ? 'dir_ltr' : 'dir_rtl'], 'dir', '', icons[options.rtl ? 'dir_ltr' : 'dir_rtl']], dir_ltr: ['', lang.toolbar.dir_ltr, 'dir_ltr', '', icons.dir_ltr], dir_rtl: ['', lang.toolbar.dir_rtl, 'dir_rtl', '', icons.dir_rtl], save: ['se-resizing-enabled', lang.toolbar.save + '<span class="se-shortcut">' + (shortcutsDisable.indexOf('save') > -1 ? '' : cmd + '+<span class="se-shortcut-key">S</span>') + '</span>', 'save', '', icons.save], /** plugins - command */ blockquote: ['', lang.toolbar.tag_blockquote, 'blockquote', 'command', icons.blockquote], /** plugins - submenu */ font: ['se-btn-select se-btn-tool-font', lang.toolbar.font, 'font', 'submenu', '<span class="txt">' + lang.toolbar.font + '</span>' + icons.arrow_down], formatBlock: ['se-btn-select se-btn-tool-format', lang.toolbar.formats, 'formatBlock', 'submenu', '<span class="txt">' + lang.toolbar.formats + '</span>' + icons.arrow_down], fontSize: ['se-btn-select se-btn-tool-size', lang.toolbar.fontSize, 'fontSize', 'submenu', '<span class="txt">' + lang.toolbar.fontSize + '</span>' + icons.arrow_down], fontColor: ['', lang.toolbar.fontColor, 'fontColor', 'submenu', icons.font_color], hiliteColor: ['', lang.toolbar.hiliteColor, 'hiliteColor', 'submenu', icons.highlight_color], align: ['se-btn-align', lang.toolbar.align, 'align', 'submenu', (options.rtl ? icons.align_right : icons.align_left)], list: ['', lang.toolbar.list, 'list', 'submenu', icons.list_number], horizontalRule: ['btn_line', lang.toolbar.horizontalRule, 'horizontalRule', 'submenu', icons.horizontal_rule], table: ['', lang.toolbar.table, 'table', 'submenu', icons.table], lineHeight: ['', lang.toolbar.lineHeight, 'lineHeight', 'submenu', icons.line_height], template: ['', lang.toolbar.template, 'template', 'submenu', icons.template], paragraphStyle: ['', lang.toolbar.paragraphStyle, 'paragraphStyle', 'submenu', icons.paragraph_style], textStyle: ['', lang.toolbar.textStyle, 'textStyle', 'submenu', icons.text_style], /** plugins - dialog */ link: ['', lang.toolbar.link, 'link', 'dialog', icons.link], image: ['', lang.toolbar.image, 'image', 'dialog', icons.image], video: ['', lang.toolbar.video, 'video', 'dialog', icons.video], audio: ['', lang.toolbar.audio, 'audio', 'dialog', icons.audio], math: ['', lang.toolbar.math, 'math', 'dialog', icons.math], /** plugins - fileBrowser */ imageGallery: ['', lang.toolbar.imageGallery, 'imageGallery', 'fileBrowser', icons.image_gallery] }; }, /** * @description Create a group div containing each module * @returns {Object} * @private */ _createModuleGroup: function () { const oDiv = util.createElement('DIV'); oDiv.className = 'se-btn-module se-btn-module-border'; const oUl = util.createElement('UL'); oUl.className = 'se-menu-list'; oDiv.appendChild(oUl); return { 'div': oDiv, 'ul': oUl }; }, /** * @description Create a button element * @param {string} buttonClass className in button * @param {string} title Title in button * @param {string} dataCommand The data-command property of the button * @param {string} dataDisplay The data-display property of the button ('dialog', 'submenu', 'command') * @param {string} innerHTML Html in button * @param {string} _disabled Button disabled * @param {Object} _icons Icons * @returns {Object} * @private */ _createButton: function (buttonClass, title, dataCommand, dataDisplay, innerHTML, _disabled, _icons) { const oLi = util.createElement('LI'); const oButton = util.createElement('BUTTON'); const label = (title || dataCommand); oButton.setAttribute('type', 'button'); oButton.setAttribute('class', 'se-btn' + (buttonClass ? ' ' + buttonClass : '') + ' se-tooltip'); oButton.setAttribute('data-command', dataCommand); oButton.setAttribute('data-display', dataDisplay); oButton.setAttribute('aria-label', label.replace(/<span .+<\/span>/, '')); oButton.setAttribute('tabindex', '-1'); if (!innerHTML) innerHTML = '<span class="se-icon-text">!</span>'; if (/^default\./i.test(innerHTML)) { innerHTML = _icons[innerHTML.replace(/^default\./i, '')]; } if (/^text\./i.test(innerHTML)) { innerHTML = innerHTML.replace(/^text\./i, ''); oButton.className += ' se-btn-more-text'; } innerHTML += '<span class="se-tooltip-inner"><span class="se-tooltip-text">' + label + '</span></span>'; if (_disabled) oButton.setAttribute('disabled', true); oButton.innerHTML = innerHTML; oLi.appendChild(oButton); return { 'li': oLi, 'button': oButton }; }, /** * @description Create editor HTML * @param {Array} doc document object * @param {Array} buttonList option.buttonList * @param {Object|null} plugins Plugins * @param {Array} options options * @returns {Object} { element: (Element) Toolbar element, plugins: (Array|null) Plugins Array, pluginCallButtons: (Object), responsiveButtons: (Array) } * @private */ _createToolBar: function (doc, buttonList, plugins, options) { const separator_vertical = doc.createElement('DIV'); separator_vertical.className = 'se-toolbar-separator-vertical'; const tool_bar = doc.createElement('DIV'); tool_bar.className = 'se-toolbar sun-editor-common'; const _buttonTray = doc.createElement('DIV'); _buttonTray.className = 'se-btn-tray'; tool_bar.appendChild(_buttonTray); /** create button list */ buttonList = JSON.parse(JSON.stringify(buttonList)); const icons = options.icons; const defaultButtonList = this._defaultButtons(options); const pluginCallButtons = {}; const responsiveButtons = []; let module = null; let button = null; let moduleElement = null; let buttonElement = null; let pluginName = ''; let vertical = false; const moreLayer = util.createElement('DIV'); moreLayer.className = 'se-toolbar-more-layer'; buttonGroupLoop: for (let i = 0, more, moreContainer, moreCommand, buttonGroup, align; i < buttonList.length; i++) { more = false; align = ''; buttonGroup = buttonList[i]; moduleElement = this._createModuleGroup(); // button object if (typeof buttonGroup === 'object') { // buttons loop for (let j = 0, moreButton; j < buttonGroup.length; j++) { button = buttonGroup[j]; moreButton = false; if (/^\%\d+/.test(button) && j === 0) { buttonGroup[0] = button.replace(/[^\d]/g, ''); responsiveButtons.push(buttonGroup); buttonList.splice(i--, 1); continue buttonGroupLoop; } if (typeof button === 'object') { if (typeof button.add === 'function') { pluginName = button.name; module = defaultButtonList[pluginName]; plugins[pluginName] = button; } else { pluginName = button.name; module = [button.buttonClass, button.title, button.name, button.dataDisplay, button.innerHTML, button._disabled]; } } else { // align if (/^\-/.test(button)) { align = button.substr(1); moduleElement.div.className += ' module-float-' + align; continue; } // rtl fix if (/^\#/.test(button)) { const option = button.substr(1); if (option === 'fix') moduleElement.ul.className += ' se-menu-dir-fix'; continue; } // more button if (/^\:/.test(button)) { moreButton = true; const matched = button.match(/^\:([^\-]+)\-([^\-]+)\-([^\-]+)/); moreCommand = '__se__' + matched[1].trim(); const title = matched[2].trim(); const innerHTML = matched[3].trim(); module = ['se-btn-more', title, moreCommand, 'MORE', innerHTML]; } // buttons else { module = defaultButtonList[button]; } pluginName = button; if (!module) { const custom = plugins[pluginName]; if (!custom) throw Error('[SUNEDITOR.create.toolbar.fail] The button name of a plugin that does not exist. [' + pluginName + ']'); module = [custom.buttonClass, custom.title, custom.name, custom.display, custom.innerHTML, custom._disabled]; } } buttonElement = this._createButton(module[0], module[1], module[2], module[3], module[4], module[5], icons); (more ? moreContainer : moduleElement.ul).appendChild(buttonElement.li); if (plugins[pluginName]) { pluginCallButtons[pluginName] = buttonElement.button; } // more button if (moreButton) { more = true; moreContainer = util.createElement('DIV'); moreContainer.className = 'se-more-layer ' + moreCommand; moreContainer.innerHTML = '<div class="se-more-form"><ul class="se-menu-list"' + (align ? ' style="float: ' + align + ';"' : '') + '></ul></div>'; moreLayer.appendChild(moreContainer); moreContainer = moreContainer.firstElementChild.firstElementChild; } } if (vertical) { const sv = separator