(function (global) { 'use strict'; var documentRef = global.document; if (!documentRef) { return; } var currentScriptRef = documentRef.currentScript || (function () { var scripts = documentRef.getElementsByTagName('script'); return scripts.length > 0 ? scripts[scripts.length - 1] : null; })(); var DEFAULT_CDN = global.HUSTOJ_VDITOR_CDN || getLocalCdnBase(); global.HUSTOJ_VDITOR_CDN = DEFAULT_CDN; var assetsPromise = null; var aceAssetsPromise = null; var styleInjected = false; function getLocalCdnBase() { var scriptSrc = currentScriptRef && currentScriptRef.src; if (!scriptSrc) { return 'include/vditor'; } return scriptSrc.replace(/\/vditor-adapter\.js(?:[?#].*)?$/, '/vditor'); } function getLocalAceBase() { var scriptSrc = currentScriptRef && currentScriptRef.src; if (!scriptSrc) { return 'ace'; } return scriptSrc.replace(/\/include\/vditor-adapter\.js(?:[?#].*)?$/, '/ace'); } function injectAdapterStyle() { var style; if (styleInjected) { return; } style = documentRef.createElement('style'); style.type = 'text/css'; style.setAttribute('data-hustoj-vditor', 'adapter-style'); style.textContent = [ '.hustoj-vditor-hidden{display:none !important;}', '.hustoj-vditor-wrapper{width:100%;margin:0 0 12px;}', '.hustoj-vditor-code{text-align:left;}', '.hustoj-vditor-code.ace_editor{position:relative;border:1px solid #d4d4d5;border-radius:6px;line-height:1.5;}', '.hustoj-vditor-code .ace_scroller,', '.hustoj-vditor-code .ace_gutter,', '.hustoj-vditor-code .ace_content{font-family:Consolas,Monaco,"Courier New",monospace !important;font-size:var(--hustoj-vditor-font-size) !important;line-height:1.5 !important;}', '.hustoj-vditor-code .ace_gutter{background:#f8f8f8;color:#8a8a8a;}', '.hustoj-vditor-dark.ace_editor{background:#272822;border-color:#4a4a4a;}', '.hustoj-vditor-dark .ace_gutter{background:#2f3129;color:#bcbcbc;}', '.hustoj-vditor-dark .ace_print-margin{background:#3a3d34;}' ].join(''); documentRef.head.appendChild(style); styleInjected = true; } function ensureStyleTag(href) { var existing = documentRef.querySelector('link[data-hustoj-vditor-css="' + href + '"]'); if (existing) { return; } var link = documentRef.createElement('link'); link.rel = 'stylesheet'; link.href = href; link.setAttribute('data-hustoj-vditor-css', href); documentRef.head.appendChild(link); } function ensureScript(src) { return new Promise(function (resolve, reject) { var existing = documentRef.querySelector('script[data-hustoj-vditor-js="' + src + '"]'); if (existing) { if (global.Vditor) { resolve(global.Vditor); } else { existing.addEventListener('load', function () { resolve(global.Vditor); }, { once: true }); existing.addEventListener('error', reject, { once: true }); } return; } var script = documentRef.createElement('script'); script.src = src; script.async = true; script.setAttribute('data-hustoj-vditor-js', src); script.onload = function () { resolve(global.Vditor); }; script.onerror = function () { reject(new Error('Failed to load ' + src)); }; documentRef.head.appendChild(script); }); } function ensureAssets(options) { var cdn = (options && options.cdn) || DEFAULT_CDN; global.HUSTOJ_VDITOR_CDN = cdn; if (global.Vditor) { injectAdapterStyle(); return Promise.resolve(global.Vditor); } if (assetsPromise) { return assetsPromise; } ensureStyleTag(cdn + '/dist/index.css'); injectAdapterStyle(); assetsPromise = ensureScript(cdn + '/dist/index.js').then(function () { return global.Vditor; }); return assetsPromise; } function ensureAceAssets(options) { var aceBase = (options && options.aceBase) || getLocalAceBase(); injectAdapterStyle(); if (global.ace) { if (global.ace.config && typeof global.ace.config.set === 'function') { global.ace.config.set('basePath', aceBase); } return Promise.resolve(global.ace); } if (aceAssetsPromise) { return aceAssetsPromise; } aceAssetsPromise = new Promise(function (resolve, reject) { var aceScriptSrc = aceBase + '/ace.js'; var languageToolsSrc = aceBase + '/ext-language_tools.js'; var existing = documentRef.querySelector('script[data-hustoj-ace-js="' + aceScriptSrc + '"]'); var loadLanguageTools = function () { var languageTools = documentRef.querySelector('script[data-hustoj-ace-js="' + languageToolsSrc + '"]'); var done = function () { finish(); }; if (languageTools) { languageTools.addEventListener('load', done, { once: true }); languageTools.addEventListener('error', done, { once: true }); return; } languageTools = documentRef.createElement('script'); languageTools.src = languageToolsSrc; languageTools.async = true; languageTools.setAttribute('data-hustoj-ace-js', languageToolsSrc); languageTools.onload = done; languageTools.onerror = done; documentRef.head.appendChild(languageTools); }; var finish = function () { if (global.ace && global.ace.config && typeof global.ace.config.set === 'function') { global.ace.config.set('basePath', aceBase); } resolve(global.ace); }; if (existing) { if (global.ace) { loadLanguageTools(); } else { existing.addEventListener('load', loadLanguageTools, { once: true }); existing.addEventListener('error', reject, { once: true }); } return; } var script = documentRef.createElement('script'); script.src = aceScriptSrc; script.async = true; script.setAttribute('data-hustoj-ace-js', aceScriptSrc); script.onload = loadLanguageTools; script.onerror = function () { reject(new Error('Failed to load ' + aceScriptSrc)); }; documentRef.head.appendChild(script); }); return aceAssetsPromise; } function resolveElement(target) { if (!target) { return null; } if (typeof target === 'string') { return documentRef.getElementById(target) || documentRef.querySelector(target); } return target; } function getElementValue(element) { if (!element) { return ''; } if (typeof element.value === 'string') { return element.value; } return element.textContent || ''; } function computeHeight(element, fallbackHeight) { var computed; var rows; var height; if (element && global.getComputedStyle) { computed = global.getComputedStyle(element); height = parseInt(computed.height, 10); if (!height) { height = parseInt(computed.minHeight, 10); } } if (!height && element) { rows = parseInt(element.getAttribute('rows'), 10); if (rows) { height = Math.max(rows * 24, fallbackHeight || 320); } } return height || fallbackHeight || 360; } function aceThemeToVditorTheme(themeName) { if (!themeName) { return 'classic'; } if (themeName.indexOf('monokai') !== -1 || themeName.indexOf('dark') !== -1) { return 'dark'; } return 'classic'; } function aceThemeToContentTheme(themeName) { return aceThemeToVditorTheme(themeName) === 'dark' ? 'dark' : 'light'; } function normalizeMode(modeName) { if (!modeName) { return ''; } return String(modeName).replace(/^ace\/mode\//, ''); } function dispatchNativeEvent(element, eventName) { var eventObject; if (!element || typeof Event === 'undefined') { return; } eventObject = new Event(eventName, { bubbles: true }); element.dispatchEvent(eventObject); } function syncFieldValue(field, value, onChange) { if (!field) { if (typeof onChange === 'function') { onChange(value, null); } if (typeof global.sync === 'function') { global.sync(); } return; } field.value = value; dispatchNativeEvent(field, 'input'); dispatchNativeEvent(field, 'change'); if (typeof onChange === 'function') { onChange(value, field); } if (typeof global.sync === 'function') { global.sync(); } } function decodeHtmlEntities(text) { var textarea = documentRef.createElement('textarea'); textarea.innerHTML = typeof text === 'string' ? text : ''; return textarea.value; } function toElementArray(target) { var resolved; if (!target) { return []; } if (typeof target === 'string') { return Array.prototype.slice.call(documentRef.querySelectorAll(target)); } if (target.nodeType === 1) { return [target]; } if (typeof target.length === 'number') { return Array.prototype.slice.call(target).map(function (item) { return resolveElement(item); }).filter(function (item) { return !!item; }); } resolved = resolveElement(target); return resolved ? [resolved] : []; } function mergeObjects(target, source) { var key; if (!source) { return target; } for (key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } return target; } function getMarkdownSource(element, options) { var rawSource; var source; if (!element) { return ''; } if (!options.refreshSource && typeof element._hustojVditorSource === 'string') { return element._hustojVditorSource; } if (typeof options.getSource === 'function') { rawSource = options.getSource(element); } else if (options.useTextContent) { rawSource = element.textContent || ''; } else { rawSource = element.innerHTML || ''; } source = String(rawSource || ''); if (options.decodeEntities !== false) { source = decodeHtmlEntities(source); } element._hustojVditorSource = source; return source; } function buildPreviewOptions(options, afterRender) { var previewOptions = { cdn: options.cdn || DEFAULT_CDN, mode: options.mode || 'light', anchor: typeof options.anchor === 'undefined' ? 0 : options.anchor, speech: { enable: false }, hljs: { style: options.codeTheme || 'github' }, markdown: { autoSpace: false, fixTermTypo: false, toc: false, codeBlockPreview: true, mathBlockPreview: true, sanitize: false }, math: { inlineDigit: true }, after: afterRender }; if (options.previewOptions) { mergeObjects(previewOptions, options.previewOptions); if (options.previewOptions.speech) { previewOptions.speech = mergeObjects(previewOptions.speech, options.previewOptions.speech); } if (options.previewOptions.hljs) { previewOptions.hljs = mergeObjects(previewOptions.hljs, options.previewOptions.hljs); } if (options.previewOptions.markdown) { previewOptions.markdown = mergeObjects(previewOptions.markdown, options.previewOptions.markdown); } if (options.previewOptions.math) { previewOptions.math = mergeObjects(previewOptions.math, options.previewOptions.math); } } previewOptions.after = afterRender; return previewOptions; } function renderMarkdownBlocks(target, options) { var renderOptions = options || {}; return ensureAssets(renderOptions).then(function () { var elements = toElementArray(target); var tasks = []; elements.forEach(function (element) { var source = getMarkdownSource(element, renderOptions); if (!source && !renderOptions.renderEmpty) { return; } tasks.push(new Promise(function (resolve, reject) { var previewOptions = buildPreviewOptions(renderOptions, function () { if (typeof renderOptions.afterEach === 'function') { renderOptions.afterEach(element, source); } resolve(element); }); try { global.Vditor.preview(element, source, previewOptions); } catch (error) { reject(error); } })); }); return Promise.all(tasks).then(function (result) { if (typeof renderOptions.afterAll === 'function') { renderOptions.afterAll(result); } return result; }); }); } function editorUploadFormatter(files, responseText) { var payload; var fileName = 'upload'; var result; try { payload = JSON.parse(responseText); } catch (error) { return JSON.stringify({ msg: '上传返回解析失败', code: 1, data: { errFiles: [fileName], succMap: {} } }); } if (files && files.length > 0 && files[0] && files[0].name) { fileName = files[0].name; } if (typeof payload.code !== 'undefined' && payload.data) { return responseText; } if (payload.error === 0 && payload.url) { result = { msg: '', code: 0, data: { errFiles: [], succMap: {} } }; result.data.succMap[fileName] = payload.url; return JSON.stringify(result); } return JSON.stringify({ msg: payload.message || payload.msg || '上传失败', code: 1, data: { errFiles: [fileName], succMap: {} } }); } function buildUploadOptions(options) { if (!options || !options.uploadUrl) { return undefined; } return { url: options.uploadUrl, max: options.uploadMax || 400 * 1024 * 1024, multiple: false, fieldName: options.fieldName || 'imgFile', accept: options.accept || '*/*', format: function (files, responseText) { return editorUploadFormatter(files, responseText); } }; } function getDefaultMarkdownToolbar() { return [ 'emoji', 'headings', 'bold', 'italic', 'strike', '|', 'line', 'quote', 'list', 'ordered-list', 'check', '|', 'link', 'upload', 'table', 'code', 'inline-code', '|', 'undo', 'redo', 'fullscreen', 'outline' ]; } function getDefaultMailToolbar() { return [ 'emoji', 'headings', 'bold', 'italic', 'strike', '|', 'link', 'upload', 'code', 'inline-code', '|', 'undo', 'redo', '|', 'fullscreen' ]; } function createTextareaEditor(textarea, options) { var wrapper = documentRef.createElement('div'); var height = computeHeight(textarea, options.height || 360); var vditor; wrapper.className = 'hustoj-vditor-wrapper hustoj-vditor-markdown'; textarea.classList.add('hustoj-vditor-hidden'); textarea.parentNode.insertBefore(wrapper, textarea.nextSibling); return new Promise(function (resolve) { vditor = new global.Vditor(wrapper, { cdn: options.cdn || DEFAULT_CDN, cache: { enable: false }, mode: options.mode || 'sv', value: textarea.value || '', lang: options.lang || 'zh_CN', theme: options.theme || 'classic', icon: options.icon || 'ant', width: options.width || '100%', minHeight: options.minHeight || Math.min(height, 320), height: options.height || height, toolbar: options.toolbar || getDefaultMarkdownToolbar(), toolbarConfig: options.toolbarConfig || { pin: true }, resize: options.resize || { enable: true, position: 'bottom' }, outline: options.outline || { enable: false }, preview: options.preview || { mode: 'both', delay: 0, actions: [], markdown: { sanitize: false, autoSpace: false, codeBlockPreview: true, mathBlockPreview: true, fixTermTypo: false } }, upload: buildUploadOptions(options), ctrlEnter: function (value) { var form; syncFieldValue(textarea, value, options.onChange); if (!options.ctrlEnterFormName) { return; } form = documentRef.querySelector('form[name="' + options.ctrlEnterFormName + '"]'); if (form) { form.submit(); } }, input: function (value) { syncFieldValue(textarea, value, options.onChange); }, blur: function (value) { syncFieldValue(textarea, value, options.onChange); }, after: function () { syncFieldValue(textarea, vditor.getValue(), options.onChange); resolve(vditor); } }); }); } function createCodeEditor(options) { return ensureAceAssets(options).then(function () { var container = resolveElement(options.container); var hiddenField = resolveElement(options.hiddenField); var sourceField = resolveElement(options.sourceField); var initialValue = options.initialValue; var adapter = null; var aceEditor = null; var session = null; var state; var aceBase = (options && options.aceBase) || getLocalAceBase(); if (!container) { throw new Error('Vditor code editor container not found'); } if (typeof initialValue === 'undefined') { initialValue = getElementValue(sourceField || container); } state = { theme: options.theme || 'ace/theme/chrome', fontSize: parseInt(options.fontSize, 10) || 18, mode: normalizeMode(options.mode || ''), wrap: true }; container.classList.add('hustoj-vditor-code'); container.style.setProperty('--hustoj-vditor-font-size', state.fontSize + 'px'); container.classList.toggle('hustoj-vditor-dark', aceThemeToVditorTheme(state.theme) === 'dark'); container.style.width = options.width || '100%'; if (typeof options.height === 'string') { container.style.height = options.height; } else { container.style.height = (options.height || computeHeight(container, 420)) + 'px'; } if (sourceField && sourceField !== container) { sourceField.classList.add('hustoj-vditor-hidden'); } container.textContent = initialValue || ''; function applyTheme() { var editorTheme = aceThemeToVditorTheme(state.theme); container.classList.toggle('hustoj-vditor-dark', editorTheme === 'dark'); if (aceEditor) { aceEditor.setTheme(state.theme); } } function applyFontSize() { container.style.setProperty('--hustoj-vditor-font-size', state.fontSize + 'px'); if (aceEditor) { aceEditor.setFontSize(state.fontSize); } } function syncSourceValue(value) { if (sourceField && sourceField !== container && typeof sourceField.value === 'string') { sourceField.value = value; } if (hiddenField && typeof hiddenField.value === 'string') { hiddenField.value = value; } if (typeof options.onChange === 'function') { options.onChange(value); } } session = { setMode: function (modeName) { state.mode = normalizeMode(modeName); if (aceEditor) { aceEditor.session.setMode(modeName || 'text'); } }, getValue: function () { return adapter.getValue(); }, setValue: function (value) { adapter.setValue(value); } }; return new Promise(function (resolve) { if (global.ace && global.ace.config && typeof global.ace.config.set === 'function') { global.ace.config.set('basePath', aceBase); } aceEditor = global.ace.edit(container); aceEditor.$blockScrolling = Infinity; aceEditor.setValue(initialValue || '', -1); aceEditor.session.setUseWorker(false); aceEditor.session.setUseWrapMode(true); aceEditor.renderer.setShowPrintMargin(false); aceEditor.setHighlightActiveLine(true); aceEditor.setOptions({ showFoldWidgets: false, tabSize: 4, useSoftTabs: true, behavioursEnabled: false, wrapBehavioursEnabled: false, fontSize: state.fontSize }); if (state.mode) { aceEditor.session.setMode('ace/mode/' + state.mode); } aceEditor.session.on('change', function () { syncSourceValue(aceEditor.getValue()); }); aceEditor.on('blur', function () { syncSourceValue(aceEditor.getValue()); }); adapter = { getValue: function () { return aceEditor.getValue(); }, setValue: function (value) { var nextValue = typeof value === 'undefined' ? '' : String(value); aceEditor.setValue(nextValue, -1); syncSourceValue(nextValue); return nextValue; }, getSession: function () { return aceEditor.getSession(); }, session: aceEditor.getSession(), setTheme: function (themeName) { state.theme = themeName || state.theme; applyTheme(); }, getTheme: function () { return state.theme; }, setOptions: function (editorOptions) { if (!editorOptions) { return; } if (editorOptions.fontSize) { this.setFontSize(editorOptions.fontSize); } if (typeof editorOptions.readOnly !== 'undefined') { this.setReadOnly(editorOptions.readOnly); } aceEditor.setOptions(editorOptions); }, setOption: function (name, value) { if (name === 'wrap') { state.wrap = value !== 'off'; aceEditor.session.setUseWrapMode(state.wrap); return; } aceEditor.setOption(name, value); }, resize: function () { aceEditor.resize(); applyFontSize(); }, getFontSize: function () { return String(state.fontSize); }, setFontSize: function (fontSize) { state.fontSize = parseInt(fontSize, 10) || state.fontSize; applyFontSize(); }, setReadOnly: function (readOnly) { aceEditor.setReadOnly(!!readOnly); }, focus: function () { aceEditor.focus(); }, _ace: aceEditor }; applyTheme(); applyFontSize(); if (options.readOnly) { adapter.setReadOnly(true); } syncSourceValue(aceEditor.getValue()); resolve(adapter); }); }); } function createDeferredCodeEditor(options) { var initialElement = resolveElement(options && (options.sourceField || options.container)); var proxy = { _impl: null, _theme: (options && options.theme) || 'ace/theme/chrome', _fontSize: parseInt(options && options.fontSize, 10) || 18, _value: typeof options !== 'undefined' && typeof options.initialValue !== 'undefined' ? options.initialValue : getElementValue(initialElement), _mode: normalizeMode(options && options.mode), _options: null, _readOnly: typeof options !== 'undefined' ? options.readOnly : undefined, _wrap: 'free' }; function getProxySession() { return { setMode: function (modeName) { proxy._mode = normalizeMode(modeName); if (proxy._impl) { proxy._impl.getSession().setMode(modeName); } }, getValue: function () { return proxy.getValue(); }, setValue: function (value) { proxy.setValue(value); } }; } proxy.getValue = function () { if (proxy._impl) { return proxy._impl.getValue(); } if (typeof proxy._value !== 'undefined' && proxy._value !== null && proxy._value !== '') { return proxy._value; } return getElementValue(resolveElement(options && (options.sourceField || options.container))); }; proxy.setValue = function (value) { proxy._value = value; if (proxy._impl) { return proxy._impl.setValue(value); } return value; }; proxy.getSession = function () { return getProxySession(); }; proxy.session = getProxySession(); proxy.setTheme = function (themeName) { proxy._theme = themeName || proxy._theme; if (proxy._impl) { proxy._impl.setTheme(proxy._theme); } }; proxy.getTheme = function () { if (proxy._impl) { return proxy._impl.getTheme(); } return proxy._theme; }; proxy.setOptions = function (editorOptions) { proxy._options = editorOptions; if (editorOptions && editorOptions.fontSize) { proxy._fontSize = parseInt(editorOptions.fontSize, 10) || proxy._fontSize; } if (proxy._impl) { proxy._impl.setOptions(editorOptions); } }; proxy.setOption = function (name, value) { if (name === 'wrap') { proxy._wrap = value; } if (proxy._impl) { proxy._impl.setOption(name, value); } }; proxy.resize = function () { if (proxy._impl) { proxy._impl.resize(); } }; proxy.getFontSize = function () { if (proxy._impl) { return proxy._impl.getFontSize(); } return String(proxy._fontSize); }; proxy.setFontSize = function (fontSize) { proxy._fontSize = parseInt(fontSize, 10) || proxy._fontSize; if (proxy._impl) { proxy._impl.setFontSize(proxy._fontSize); } }; proxy.setReadOnly = function (readOnly) { proxy._readOnly = readOnly; if (proxy._impl) { proxy._impl.setReadOnly(readOnly); } }; proxy.focus = function () { if (proxy._impl) { proxy._impl.focus(); } }; createCodeEditor(options || {}).then(function (instance) { proxy._impl = instance; instance.setTheme(proxy._theme); instance.setFontSize(proxy._fontSize); if (proxy._options) { instance.setOptions(proxy._options); } if (proxy._mode) { instance.getSession().setMode('ace/mode/' + proxy._mode); } if (typeof proxy._readOnly !== 'undefined') { instance.setReadOnly(proxy._readOnly); } instance.setOption('wrap', proxy._wrap); if (typeof proxy._value !== 'undefined' && proxy._value !== null) { instance.setValue(proxy._value); } }).catch(function (error) { console.error('Failed to initialize deferred Vditor code editor.', error); }); return proxy; } function createRichTextEditor(options) { return ensureAssets(options).then(function () { var container = resolveElement(options.container); var hiddenField = resolveElement(options.hiddenField); var initialValue = options.initialValue || ''; var vditor = null; var adapter = null; if (!container) { throw new Error('Vditor rich editor container not found'); } container.classList.add('hustoj-vditor-mail'); container.textContent = ''; function syncHtml() { var html = vditor.getHTML(); if (hiddenField && typeof hiddenField.value === 'string') { hiddenField.value = html; } if (typeof options.onChange === 'function') { options.onChange(html); } } return new Promise(function (resolve) { vditor = new global.Vditor(container, { cdn: options.cdn || DEFAULT_CDN, cache: { enable: false }, mode: options.mode || 'wysiwyg', lang: options.lang || 'zh_CN', value: initialValue, theme: options.theme || 'classic', icon: options.icon || 'ant', width: options.width || '100%', minHeight: options.minHeight || computeHeight(container, 200), height: options.height || computeHeight(container, 200), toolbar: options.toolbar || getDefaultMailToolbar(), toolbarConfig: options.toolbarConfig || { pin: true }, preview: options.preview || { mode: 'editor', delay: 0, actions: [], markdown: { sanitize: false, autoSpace: false, fixTermTypo: false } }, upload: buildUploadOptions(options), input: syncHtml, blur: syncHtml, after: function () { adapter = { getHTML: function () { return vditor.getHTML(); }, getValue: function () { return vditor.getValue(); }, setValue: function (value) { vditor.setValue(value || '', true); syncHtml(); }, focus: function () { vditor.focus(); }, txt: { html: function () { return vditor.getHTML(); } }, _vditor: vditor }; syncHtml(); resolve(adapter); } }); }); }); } function initTextareaVditors(options) { return ensureAssets(options).then(function () { var selector = (options && options.selector) || 'textarea.vditor-editor'; var textareas = documentRef.querySelectorAll(selector); var tasks = []; Array.prototype.forEach.call(textareas, function (textarea) { tasks.push(createTextareaEditor(textarea, options || {})); }); return Promise.all(tasks); }); } global.HustOJVditor = { ensureAssets: ensureAssets, initTextareaVditors: initTextareaVditors, createCodeEditor: createCodeEditor, createDeferredCodeEditor: createDeferredCodeEditor, createRichTextEditor: createRichTextEditor, renderMarkdownBlocks: renderMarkdownBlocks, aceThemeToVditorTheme: aceThemeToVditorTheme, decodeHtmlEntities: decodeHtmlEntities, editorUploadFormatter: editorUploadFormatter }; })(window);