✨ feat: 更新问题页面和提交页面,集成Vditor适配器,优化Markdown渲染
This commit is contained in:
@@ -85,7 +85,7 @@ static $OJ_RANK_HIDDEN="'admin','super','szx','sen'"; // 管理员不显示在
|
||||
static $OJ_FRIENDLY_LEVEL=0; //系统友好级别,暂定0-9级,级别越高越傻瓜,系统易用度高的同时将降低安全性,仅供非专业用途,造成泄题、抄袭概不负责。
|
||||
static $OJ_FREE_PRACTICE=true; //自由练习,不受比赛作业用题限制
|
||||
static $OJ_SUBMIT_COOLDOWN_TIME=1; //提交冷却时间,连续两次提交的最小间隔,单位秒。
|
||||
static $OJ_MARKDOWN='markdown-it'; // 开启MARKDOWN,开启后在后台编辑题目时默认为源码模式,用[md] # Markdown [/md] 格式插入markdown代码, 如果需要用到[]也可以用<div class='md'> </div>。
|
||||
static $OJ_MARKDOWN='vditor'; // 开启 Markdown 渲染,当前统一由 Vditor 处理;后台编辑题目时默认为源码模式,用[md] # Markdown [/md] 格式插入 markdown 代码,如需直接包裹内容也可以用<div class='md'> </div>。
|
||||
static $OJ_INDEX_NEWS_TITLE='HelloWorld!'; // 在syzoj的首页显示哪一篇标题的文章(可以有多个相同标题)
|
||||
static $OJ_DIV_FILTER=true; // 过滤题面中的div,修复显示异常,特别是来自其他OJ系统的题面。
|
||||
static $OJ_LIMIT_TO_1_IP=true; // 限制用户同一时刻只能在一个IP地址登录
|
||||
|
||||
976
web/include/vditor-adapter.js
Normal file
976
web/include/vditor-adapter.js
Normal file
@@ -0,0 +1,976 @@
|
||||
(function (global) {
|
||||
'use strict';
|
||||
|
||||
var documentRef = global.document;
|
||||
if (!documentRef) {
|
||||
return;
|
||||
}
|
||||
|
||||
var DEFAULT_CDN = global.HUSTOJ_VDITOR_CDN || 'https://cdn.jsdelivr.net/npm/vditor@3.10.6';
|
||||
var assetsPromise = null;
|
||||
var styleInjected = false;
|
||||
|
||||
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{--hustoj-vditor-font-size:18px;}',
|
||||
'.hustoj-vditor-code .vditor-toolbar{display:none !important;}',
|
||||
'.hustoj-vditor-code .vditor-reset,',
|
||||
'.hustoj-vditor-code .vditor-content,',
|
||||
'.hustoj-vditor-code textarea{font-family:Consolas,Monaco,"Courier New",monospace !important;font-size:var(--hustoj-vditor-font-size) !important;line-height:1.5 !important;}',
|
||||
'.hustoj-vditor-code .vditor-sv,',
|
||||
'.hustoj-vditor-code .vditor-ir{min-height:100%;}',
|
||||
'.hustoj-vditor-dark .vditor{background:#272822;border-color:#4a4a4a;}',
|
||||
'.hustoj-vditor-dark .vditor-reset,',
|
||||
'.hustoj-vditor-dark .vditor-content,',
|
||||
'.hustoj-vditor-dark textarea{color:#f8f8f2 !important;background:#272822 !important;}',
|
||||
'.hustoj-vditor-dark .vditor-toolbar svg{fill:#f8f8f2;}',
|
||||
'.hustoj-vditor-mail .vditor-toolbar{border-bottom:1px solid #ddd;}',
|
||||
'.hustoj-vditor-mail .vditor{height:100%;min-height:180px;}'
|
||||
].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;
|
||||
if (global.Vditor) {
|
||||
injectAdapterStyle();
|
||||
return Promise.resolve(global.Vditor);
|
||||
}
|
||||
if (assetsPromise) {
|
||||
return assetsPromise;
|
||||
}
|
||||
|
||||
ensureStyleTag(cdn + '/dist/index.css');
|
||||
injectAdapterStyle();
|
||||
assetsPromise = ensureScript(cdn + '/dist/index.min.js').then(function () {
|
||||
return global.Vditor;
|
||||
});
|
||||
return assetsPromise;
|
||||
}
|
||||
|
||||
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 kindEditorUploadFormatter(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 kindEditorUploadFormatter(files, responseText);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
function getDefaultMarkdownToolbar() {
|
||||
return [
|
||||
'emoji',
|
||||
'headings',
|
||||
'bold',
|
||||
'italic',
|
||||
'strike',
|
||||
'|',
|
||||
'line',
|
||||
'quote',
|
||||
'list',
|
||||
'ordered-list',
|
||||
'check',
|
||||
'|',
|
||||
'link',
|
||||
'upload',
|
||||
'table',
|
||||
'code',
|
||||
'inline-code',
|
||||
'|',
|
||||
'undo',
|
||||
'redo',
|
||||
'|',
|
||||
'edit-mode',
|
||||
'both',
|
||||
'preview',
|
||||
'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 ensureAssets(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 vditor = null;
|
||||
var session = null;
|
||||
var state;
|
||||
|
||||
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');
|
||||
|
||||
if (sourceField && sourceField !== container) {
|
||||
sourceField.classList.add('hustoj-vditor-hidden');
|
||||
}
|
||||
|
||||
container.textContent = '';
|
||||
|
||||
function applyTheme() {
|
||||
var editorTheme = aceThemeToVditorTheme(state.theme);
|
||||
var contentTheme = aceThemeToContentTheme(state.theme);
|
||||
|
||||
container.classList.toggle('hustoj-vditor-dark', editorTheme === 'dark');
|
||||
if (vditor && typeof vditor.setTheme === 'function') {
|
||||
vditor.setTheme(editorTheme, contentTheme, 'github');
|
||||
}
|
||||
}
|
||||
|
||||
function applyFontSize() {
|
||||
container.style.setProperty('--hustoj-vditor-font-size', state.fontSize + 'px');
|
||||
}
|
||||
|
||||
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);
|
||||
},
|
||||
getValue: function () {
|
||||
return adapter.getValue();
|
||||
},
|
||||
setValue: function (value) {
|
||||
adapter.setValue(value);
|
||||
}
|
||||
};
|
||||
|
||||
return new Promise(function (resolve) {
|
||||
vditor = new global.Vditor(container, {
|
||||
cdn: options.cdn || DEFAULT_CDN,
|
||||
cache: { enable: false },
|
||||
mode: options.editorMode || 'sv',
|
||||
lang: options.lang || 'zh_CN',
|
||||
value: initialValue || '',
|
||||
icon: options.icon || 'ant',
|
||||
theme: aceThemeToVditorTheme(state.theme),
|
||||
width: options.width || '100%',
|
||||
minHeight: options.minHeight || computeHeight(container, 420),
|
||||
height: options.height || computeHeight(container, 420),
|
||||
toolbar: options.toolbar || [],
|
||||
toolbarConfig: options.toolbarConfig || { hide: true, pin: false },
|
||||
resize: options.resize || { enable: false },
|
||||
outline: options.outline || { enable: false },
|
||||
counter: options.counter || { enable: false },
|
||||
preview: options.preview || {
|
||||
mode: 'editor',
|
||||
delay: 0,
|
||||
actions: [],
|
||||
markdown: {
|
||||
sanitize: false,
|
||||
autoSpace: false,
|
||||
codeBlockPreview: false,
|
||||
mathBlockPreview: false,
|
||||
fixTermTypo: false
|
||||
}
|
||||
},
|
||||
input: function (value) {
|
||||
syncSourceValue(value);
|
||||
},
|
||||
blur: function (value) {
|
||||
syncSourceValue(value);
|
||||
},
|
||||
after: function () {
|
||||
adapter = {
|
||||
getValue: function () {
|
||||
return vditor.getValue();
|
||||
},
|
||||
setValue: function (value) {
|
||||
var nextValue = typeof value === 'undefined' ? '' : String(value);
|
||||
vditor.setValue(nextValue, true);
|
||||
syncSourceValue(nextValue);
|
||||
return nextValue;
|
||||
},
|
||||
getSession: function () {
|
||||
return session;
|
||||
},
|
||||
session: session,
|
||||
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);
|
||||
}
|
||||
},
|
||||
setOption: function (name, value) {
|
||||
if (name === 'wrap') {
|
||||
state.wrap = value !== 'off';
|
||||
}
|
||||
},
|
||||
resize: function () {
|
||||
applyFontSize();
|
||||
},
|
||||
getFontSize: function () {
|
||||
return String(state.fontSize);
|
||||
},
|
||||
setFontSize: function (fontSize) {
|
||||
state.fontSize = parseInt(fontSize, 10) || state.fontSize;
|
||||
applyFontSize();
|
||||
},
|
||||
setReadOnly: function (readOnly) {
|
||||
if (readOnly) {
|
||||
vditor.disabled();
|
||||
} else {
|
||||
vditor.enable();
|
||||
}
|
||||
},
|
||||
focus: function () {
|
||||
vditor.focus();
|
||||
},
|
||||
_vditor: vditor
|
||||
};
|
||||
|
||||
applyTheme();
|
||||
applyFontSize();
|
||||
if (options.readOnly) {
|
||||
adapter.setReadOnly(true);
|
||||
}
|
||||
syncSourceValue(vditor.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.kindeditor';
|
||||
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,
|
||||
kindEditorUploadFormatter: kindEditorUploadFormatter
|
||||
};
|
||||
})(window);
|
||||
Reference in New Issue
Block a user