From 909c9279864a945659b9def015b80d9de86dc2d3 Mon Sep 17 00:00:00 2001 From: undefined Date: Sun, 30 Jun 2024 00:24:41 +0800 Subject: [PATCH] ui: drop vditor --- .../backendlib/markdown-it-imsize.ts | 235 ++++++++++++++++++ ...kdown-it-katex.js => markdown-it-katex.ts} | 11 +- .../backendlib/markdown-it-media.ts | 6 +- packages/ui-default/backendlib/markdown.js | 2 +- packages/ui-default/build/config/webpack.ts | 3 +- packages/ui-default/build/main.ts | 5 - .../components/editor/cmeditor.styl | 9 - .../components/editor/{index.ts => index.tsx} | 212 ++++++++-------- .../ui-default/components/editor/mdeditor.ts | 39 +++ packages/ui-default/components/upload.ts | 3 +- packages/ui-default/package.json | 10 +- .../ui-default/pages/problem_edit.page.js | 10 +- packages/ui-default/service-worker.ts | 2 +- packages/ui-default/setting.yaml | 2 - packages/ui-default/theme/dark.styl | 2 +- packages/ui-default/theme/default.js | 1 - 16 files changed, 399 insertions(+), 153 deletions(-) create mode 100644 packages/ui-default/backendlib/markdown-it-imsize.ts rename packages/ui-default/backendlib/{markdown-it-katex.js => markdown-it-katex.ts} (95%) rename packages/ui-default/components/editor/{index.ts => index.tsx} (59%) create mode 100644 packages/ui-default/components/editor/mdeditor.ts diff --git a/packages/ui-default/backendlib/markdown-it-imsize.ts b/packages/ui-default/backendlib/markdown-it-imsize.ts new file mode 100644 index 000000000..5a43ab588 --- /dev/null +++ b/packages/ui-default/backendlib/markdown-it-imsize.ts @@ -0,0 +1,235 @@ +// Process ![test]( x =100x200) +// ^^^^^^^^ this size specification +function parseNextNumber(str, pos, max) { + const start = pos; + const result = { + ok: false, + pos, + value: '', + }; + let code = str.charCodeAt(pos); + while (pos < max && (code >= 0x30 /* 0 */ && code <= 0x39 /* 9 */) || code === 0x25 /* % */) { + code = str.charCodeAt(++pos); + } + result.ok = true; + result.pos = pos; + result.value = str.slice(start, pos); + return result; +} + +function parseImageSize(str, pos, max) { + const result = { + ok: false, + pos: 0, + width: '', + height: '', + }; + if (pos >= max) { return result; } + let code = str.charCodeAt(pos); + if (code !== 0x3d /* = */) { return result; } + pos++; + code = str.charCodeAt(pos); + if (code !== 0x78 /* x */ && (code < 0x30 || code > 0x39) /* [0-9] */) { + return result; + } + // parse width + const resultW = parseNextNumber(str, pos, max); + pos = resultW.pos; + // next charactor must be 'x' + code = str.charCodeAt(pos); + if (code !== 0x78 /* x */) { return result; } + pos++; + // parse height + const resultH = parseNextNumber(str, pos, max); + pos = resultH.pos; + result.width = resultW.value; + result.height = resultH.value; + result.pos = pos; + result.ok = true; + return result; +} + +export default function plugin(md) { + md.inline.ruler.before('emphasis', 'image', (state, silent) => { + let attrs; + let code; + let label; + let pos; + let ref; + let res; + let title; + let width = ''; + let height = ''; + let token; + let tokens; + let start; + let href = ''; + const oldPos = state.pos; + const max = state.posMax; + + if (state.src.charCodeAt(state.pos) !== 0x21/* ! */) { return false; } + if (state.src.charCodeAt(state.pos + 1) !== 0x5B/* [ */) { return false; } + + const labelStart = state.pos + 2; + const labelEnd = md.helpers.parseLinkLabel(state, state.pos + 1, false); + + // parser failed to find ']', so it's not a valid link + if (labelEnd < 0) { return false; } + + pos = labelEnd + 1; + if (pos < max && state.src.charCodeAt(pos) === 0x28/* ( */) { + // + // Inline link + // + + // [link]( "title" ) + // ^^ skipping these spaces + pos++; + for (; pos < max; pos++) { + code = state.src.charCodeAt(pos); + if (code !== 0x20 && code !== 0x0A) { break; } + } + if (pos >= max) { return false; } + + // [link]( "title" ) + // ^^^^^^ parsing link destination + start = pos; + res = md.helpers.parseLinkDestination(state.src, pos, state.posMax); + if (res.ok) { + href = state.md.normalizeLink(res.str); + if (state.md.validateLink(href)) { + pos = res.pos; + } else { + href = ''; + } + } + + // [link]( "title" ) + // ^^ skipping these spaces + start = pos; + for (; pos < max; pos++) { + code = state.src.charCodeAt(pos); + if (code !== 0x20 && code !== 0x0A) { break; } + } + + // [link]( "title" ) + // ^^^^^^^ parsing link title + res = md.helpers.parseLinkTitle(state.src, pos, state.posMax); + if (pos < max && start !== pos && res.ok) { + title = res.str; + pos = res.pos; + + // [link]( "title" ) + // ^^ skipping these spaces + for (; pos < max; pos++) { + code = state.src.charCodeAt(pos); + if (code !== 0x20 && code !== 0x0A) { break; } + } + } else { + title = ''; + } + + // [link]( "title" =WxH ) + // ^^^^ parsing image size + if (pos - 1 >= 0) { + code = state.src.charCodeAt(pos - 1); + + // there must be at least one white spaces + // between previous field and the size + if (code === 0x20) { + res = parseImageSize(state.src, pos, state.posMax); + if (res.ok) { + width = res.width; + height = res.height; + pos = res.pos; + + // [link]( "title" =WxH ) + // ^^ skipping these spaces + for (; pos < max; pos++) { + code = state.src.charCodeAt(pos); + if (code !== 0x20 && code !== 0x0A) { break; } + } + } + } + } + + if (pos >= max || state.src.charCodeAt(pos) !== 0x29/* ) */) { + state.pos = oldPos; + return false; + } + pos++; + } else { + // + // Link reference + // + if (typeof state.env.references === 'undefined') { return false; } + + // [foo] [bar] + // ^^ optional whitespace (can include newlines) + for (; pos < max; pos++) { + code = state.src.charCodeAt(pos); + if (code !== 0x20 && code !== 0x0A) { break; } + } + + if (pos < max && state.src.charCodeAt(pos) === 0x5B/* [ */) { + start = pos + 1; + pos = md.helpers.parseLinkLabel(state, pos); + if (pos >= 0) { + label = state.src.slice(start, pos++); + } else { + pos = labelEnd + 1; + } + } else { + pos = labelEnd + 1; + } + + // covers label === '' and label === undefined + // (collapsed reference link and shortcut reference link respectively) + label ||= state.src.slice(labelStart, labelEnd); + + ref = state.env.references[md.utils.normalizeReference(label)]; + if (!ref) { + state.pos = oldPos; + return false; + } + href = ref.href; + title = ref.title; + } + + // + // We found the end of the link, and know for a fact it's a valid link; + // so all that's left to do is to call tokenizer. + // + if (!silent) { + state.pos = labelStart; + state.posMax = labelEnd; + + const newState = new state.md.inline.State( + state.src.slice(labelStart, labelEnd), + state.md, + state.env, + tokens = [], + ); + newState.md.inline.tokenize(newState); + token = state.push('image', 'img', 0); + token.attrs = attrs = [['src', href], + ['alt', '']]; + token.children = tokens; + if (title) { + attrs.push(['title', title]); + } + + if (width !== '') { + attrs.push(['width', width]); + } + + if (height !== '') { + attrs.push(['height', height]); + } + } + + state.pos = pos; + state.posMax = max; + return true; + }); +}; diff --git a/packages/ui-default/backendlib/markdown-it-katex.js b/packages/ui-default/backendlib/markdown-it-katex.ts similarity index 95% rename from packages/ui-default/backendlib/markdown-it-katex.js rename to packages/ui-default/backendlib/markdown-it-katex.ts index 20a2cc8fc..cd49f5e9a 100644 --- a/packages/ui-default/backendlib/markdown-it-katex.js +++ b/packages/ui-default/backendlib/markdown-it-katex.ts @@ -1,7 +1,8 @@ // https://github.com/waylonflinn/markdown-it-katex/blob/master/index.js -const katex = require('katex'); +import { Logger } from 'hydrooj'; +import katex from 'katex'; -const logger = new global.Hydro.Logger('katex'); +const logger = new Logger('katex'); function isValidDelim(state, pos) { const max = state.posMax; @@ -109,8 +110,8 @@ function block(state, start, end, silent) { return true; } -module.exports = function plugin(md) { - const options = { throwOnError: false, strict: 'ignore' }; +export default function plugin(md) { + const options = { throwOnError: false, strict: 'ignore', displayMode: false }; const render = (latex: string, displayMode = false) => { options.displayMode = displayMode; try { @@ -136,4 +137,4 @@ module.exports = function plugin(md) { }); md.renderer.rules.math_inline = inlineRenderer; md.renderer.rules.math_block = blockRenderer; -}; +} diff --git a/packages/ui-default/backendlib/markdown-it-media.ts b/packages/ui-default/backendlib/markdown-it-media.ts index ce990fef7..fcd8be461 100644 --- a/packages/ui-default/backendlib/markdown-it-media.ts +++ b/packages/ui-default/backendlib/markdown-it-media.ts @@ -1,8 +1,8 @@ /* eslint-disable max-len */ /* eslint-disable prefer-destructuring */ -import { randomUUID } from 'crypto'; -import MarkdownIt from 'markdown-it'; +import type MarkdownIt from 'markdown-it'; +import { v4 as uuid } from 'uuid'; const allowFullScreen = ' webkitallowfullscreen mozallowfullscreen allowfullscreen'; @@ -110,7 +110,7 @@ export function Media(md: MarkdownIt) { if (service === 'pdf') { if (src.startsWith('file://') || src.startsWith('./')) src += src.includes('?') ? '&noDisposition=1' : '?noDisposition=1'; return `\ - + diff --git a/packages/ui-default/backendlib/markdown.js b/packages/ui-default/backendlib/markdown.js index 8d03c4b0d..ad675fa9a 100644 --- a/packages/ui-default/backendlib/markdown.js +++ b/packages/ui-default/backendlib/markdown.js @@ -23,7 +23,7 @@ const MergeCells = require('markdown-it-merge-cells'); // @[youtube](https://youtube.com/watch?v=xxx) // @[pdf](https://foo.com/bar.pdf) const { Media } = require('./markdown-it-media'); -const Katex = require('./markdown-it-katex'); +const { default: Katex } = require('./markdown-it-katex'); const { xssProtector } = require('./markdown-it-xss'); class Markdown extends MarkdownIt { diff --git a/packages/ui-default/build/config/webpack.ts b/packages/ui-default/build/config/webpack.ts index ab02df446..0647aabae 100644 --- a/packages/ui-default/build/config/webpack.ts +++ b/packages/ui-default/build/config/webpack.ts @@ -269,14 +269,13 @@ export default async function (env: { watch?: boolean, production?: boolean, mea filename: '[name].css?[fullhash:6]', }), new WebpackManifestPlugin({}), - new webpack.IgnorePlugin({ resourceRegExp: /(^\.\/locale$|mathjax|abcjs|vditor.+\.d\.ts)/ }), + new webpack.IgnorePlugin({ resourceRegExp: /(^\.\/locale$)/ }), new CopyWebpackPlugin({ patterns: [ { from: root('static') }, { from: root('components/navigation/nav-logo-small_dark.png'), to: 'components/navigation/nav-logo-small_dark.png' }, { from: root(`${dirname(require.resolve('streamsaver/package.json'))}/mitm.html`), to: 'streamsaver/mitm.html' }, { from: root(`${dirname(require.resolve('streamsaver/package.json'))}/sw.js`), to: 'streamsaver/sw.js' }, - { from: root(`${dirname(require.resolve('vditor/package.json'))}/dist`), to: 'vditor/dist' }, { from: root(`${dirname(require.resolve('graphiql/package.json'))}/graphiql.min.css`), to: 'graphiql.min.css' }, { from: `${dirname(require.resolve('monaco-themes/package.json'))}/themes`, to: 'monaco/themes/' }, ], diff --git a/packages/ui-default/build/main.ts b/packages/ui-default/build/main.ts index cfc9d4dba..d0c251daa 100644 --- a/packages/ui-default/build/main.ts +++ b/packages/ui-default/build/main.ts @@ -127,11 +127,6 @@ async function main() { if (fs.existsSync('public/theme.css')) { fs.copyFileSync('public/theme.css', `public/theme-${pkg.version}.css`); } - if (argv.options.production) { - for (const f of ['echarts', 'graphviz', 'mermaid', 'mathjax']) { - fs.removeSync(`public/vditor/dist/js/${f}`); - } - } await Promise.all(globbySync('public/**/*.map').map((i) => fs.remove(i))); } process.chdir(dir); diff --git a/packages/ui-default/components/editor/cmeditor.styl b/packages/ui-default/components/editor/cmeditor.styl index c5237498b..f104bc1dc 100644 --- a/packages/ui-default/components/editor/cmeditor.styl +++ b/packages/ui-default/components/editor/cmeditor.styl @@ -1,12 +1,3 @@ -.vditor - margin-bottom: rem(10px) - -.vditor-preview__action - display: none - -.vditor ol - list-style: decimal - .decoration-hide color: grey !important background-color: grey !important diff --git a/packages/ui-default/components/editor/index.ts b/packages/ui-default/components/editor/index.tsx similarity index 59% rename from packages/ui-default/components/editor/index.ts rename to packages/ui-default/components/editor/index.tsx index c7573ac1f..a00757453 100644 --- a/packages/ui-default/components/editor/index.ts +++ b/packages/ui-default/components/editor/index.tsx @@ -1,35 +1,10 @@ import $ from 'jquery'; import { nanoid } from 'nanoid'; +import React from 'react'; +import ReactDOM from 'react-dom/client'; import DOMAttachedObject from 'vj/components/DOMAttachedObject'; -import Notification from 'vj/components/notification'; -import { i18n, request } from 'vj/utils'; - -export const config = { - toolbar: [ - 'emoji', 'headings', 'bold', 'italic', 'strike', 'link', '|', - 'list', 'ordered-list', 'check', 'outdent', 'indent', '|', - 'quote', 'line', 'code', 'inline-code', 'table', '|', - 'upload', 'edit-mode', 'fullscreen', 'export', - ], - mode: UserContext.preferredEditorType || 'ir', - toolbarConfig: { - pin: true, - }, - cdn: `${UiContext.cdn_prefix}vditor`, - counter: { - enable: true, - max: 65536, - }, - preview: { - math: { - inlineDigit: true, - }, - theme: { - path: `${UiContext.cdn_prefix}vditor/dist/css/content-theme`, - current: 'light', - }, - }, -}; +import { i18n } from 'vj/utils'; +import uploadFiles from '../upload'; interface MonacoOptions { language?: string; @@ -42,23 +17,23 @@ interface MonacoOptions { hide?: string[]; lineNumbers?: 'on' | 'off' | 'relative' | 'interval'; } -interface VditorOptions { - theme?: 'classic' | 'dark' -} -type Options = MonacoOptions & VditorOptions; +type Options = MonacoOptions; export default class Editor extends DOMAttachedObject { static DOMAttachKey = 'vjEditorInstance'; model: import('../monaco').default.editor.IModel; editor: import('../monaco').default.editor.IStandaloneCodeEditor; - vditor: import('vditor').default; + markdownEditor: any; + valueCache?: string; + setMarkdownEditorValue?: (v: string) => void; + reactRoot?: ReactDOM.Root; isValid: boolean; constructor($dom, public options: Options = {}) { super($dom); if (UserContext.preferredEditorType === 'monaco') this.initMonaco(); else if (options.language && options.language !== 'markdown') this.initMonaco(); - else this.initVditor(); + else this.initMarkdownEditor(); } async initMonaco() { @@ -168,87 +143,102 @@ export default class Editor extends DOMAttachedObject { window.editor = this.editor; } - async initVditor() { + async initMarkdownEditor() { const pagename = document.documentElement.getAttribute('data-page'); const isProblemPage = ['problem_create', 'problem_edit'].includes(pagename); const isProblemEdit = pagename === 'problem_edit'; - const { default: Vditor } = await import('vditor'); + const that = this; const { $dom } = this; const hasFocus = $dom.is(':focus') || $dom.hasClass('autofocus'); const origin = $dom.get(0); const ele = document.createElement('div'); const value = $dom.val(); const { onChange } = this.options; - await new Promise((resolve) => { - this.vditor = new Vditor(ele, { - ...config, - upload: { - multiple: false, - handler: (files) => { - let wrapper = ['', '']; - let ext: string; - const matches = files[0].type.match(/^image\/(png|jpg|jpeg|gif)$/i); - if (matches) { - wrapper = ['![image](', ')']; - [, ext] = matches; - } else if (files[0].type === 'application/x-zip-compressed') { - wrapper = ['[file](', ')']; - ext = 'zip'; - } - if (!ext) return i18n('No Supported file type.'); - const filename = `${nanoid()}.${ext}`; - const data = new FormData(); - data.append('filename', filename); - data.append('file', files[0]); - data.append('operation', 'upload_file'); - if (isProblemEdit) data.append('type', 'additional_file'); - let progress = 0; - request.postFile(isProblemEdit ? './files' : '/file', data, { - xhr: () => { - const xhr = new XMLHttpRequest(); - xhr.upload.addEventListener('loadstart', () => this.vditor.vditor.tip.show(i18n('Uploading...'), 0)); - xhr.upload.addEventListener('progress', (e) => { - if (!e.lengthComputable) return; - const percentComplete = Math.round((e.loaded / e.total) * 100); - if (percentComplete === progress) return; - progress = percentComplete; - this.vditor.vditor.tip.show(`${i18n('Uploading...')} ${percentComplete}%`, 0); - }, false); - return xhr; - }, - }) - .then(() => { - this.vditor.insertValue(`${wrapper.join(`${isProblemPage ? 'file://' : `/file/${UserContext._id}/`}${filename}`)} `); - this.vditor.vditor.tip.hide(); - }) - .catch((e: { message: string; }) => { - console.error(e); - return `${i18n('Upload Failed')}: ${e.message}`; - }); - return null; - }, - }, - ...this.options, - after: () => { - const pos = $(ele).find('button[data-mode="sv"]').get(); - const button = $(''); - button.on('click', (e) => { - Notification.info('You can select this in PreferenceSettings'); - e.preventDefault(); - }); - button.insertAfter(pos); - resolve(null); - }, - input(v) { + const { MdEditor } = await import('./mdeditor'); + + const renderCallback = (ref) => { + this.markdownEditor = ref; + }; + + function EditorComponent() { + const [val, setVal] = React.useState(value); + that.setMarkdownEditorValue = setVal; + return { + that.valueCache = v; + setVal(v); $dom.val(v); $dom.text(v); - if (onChange) onChange(v); - }, - value, - cache: { id: Math.random().toString() }, - }); - }); - $(ele).addClass('textbox'); + onChange?.(v); + }} + onUploadImg={async (files, callback) => { + let ext: string; + const matches = files[0].type.match(/^image\/(png|jpg|jpeg|gif)$/i); + if (matches) { + [, ext] = matches; + } else if (files[0].type === 'application/x-zip-compressed') { + ext = 'zip'; + } + if (!ext) return i18n('No Supported file type.'); + const filename = `${nanoid()}.${ext}`; + await uploadFiles(isProblemEdit ? './files' : '/file', [files[0]], { + type: isProblemEdit ? 'additional_file' : undefined, + filenameCallback: () => filename, + }).then(() => { + callback([`${isProblemPage ? 'file://' : `/file/${UserContext._id}/`}${filename}`]); + }).catch(() => { + callback([]); + }); + return null; + }} + />; + } + + this.reactRoot = ReactDOM.createRoot(ele); + this.reactRoot.render(); $dom.hide(); origin.parentElement.appendChild(ele); this.isValid = true; @@ -257,7 +247,7 @@ export default class Editor extends DOMAttachedObject { destroy() { this.detach(); - if (this.vditor?.destroy) this.vditor.destroy(); + if (this.reactRoot) this.reactRoot.unmount(); else if (this.editor?.dispose) this.editor.dispose(); } @@ -267,13 +257,17 @@ export default class Editor extends DOMAttachedObject { value(val?: string) { this.ensureValid(); - if (typeof val === 'string') return (this.editor || this.vditor).setValue(val); - return (this.editor || this.vditor).getValue({ lineEnding: '\n', preserveBOM: false }); + if (typeof val === 'string') { + if (this.editor) return this.editor.setValue(val); + this.setMarkdownEditorValue?.(val); + this.markdownEditor?.resetHistory?.(); + } + if (this.editor) return this.editor.getValue({ lineEnding: '\n', preserveBOM: false }); + return this.valueCache; } focus() { this.ensureValid(); - this.vditor?.focus(); if (!this.editor || !this.model) return; this.editor.focus(); const range = this.model.getFullModelRange(); diff --git a/packages/ui-default/components/editor/mdeditor.ts b/packages/ui-default/components/editor/mdeditor.ts new file mode 100644 index 000000000..62e6ef4ac --- /dev/null +++ b/packages/ui-default/components/editor/mdeditor.ts @@ -0,0 +1,39 @@ +import 'md-editor-rt/lib/style.css'; + +import Anchor from 'markdown-it-anchor'; +import Footnote from 'markdown-it-footnote'; +import Mark from 'markdown-it-mark'; +import MergeCells from 'markdown-it-merge-cells'; +import TOC from 'markdown-it-table-of-contents'; +import { config, MdEditor } from 'md-editor-rt'; +import Imsize from '../../backendlib/markdown-it-imsize'; +import { Media } from '../../backendlib/markdown-it-media'; +import { xssProtector } from '../../backendlib/markdown-it-xss'; + +const pagename = document.documentElement.getAttribute('data-page'); +const isProblemPage = ['problem_create', 'problem_edit'].includes(pagename); + +config({ + markdownItConfig(mdit) { + mdit.options.html = true; + mdit.options.linkify = true; + mdit.linkify.tlds('.py', false); + mdit.linkify.tlds('.zip', false); + mdit.linkify.tlds('.mov', false); + mdit.use(Media); + mdit.use(Footnote); + mdit.use(Mark); + mdit.use(Imsize); + mdit.use(Anchor); + mdit.use(TOC); + mdit.use(MergeCells); + mdit.use(xssProtector); + if (isProblemPage) { + mdit.core.ruler.before('normalize', 'xss', (state) => { + state.src = state.src.replace(/file:\/\//g, pagename === 'problem_create' ? `/file/${UserContext._id}/` : './file/'); + }); + } + }, +}); + +export { MdEditor }; diff --git a/packages/ui-default/components/upload.ts b/packages/ui-default/components/upload.ts index 3be3ec434..d70855b13 100644 --- a/packages/ui-default/components/upload.ts +++ b/packages/ui-default/components/upload.ts @@ -13,6 +13,7 @@ interface UploadOptions { pjax?: boolean; sidebar?: boolean; singleFileUploadCallback?: (file: File) => any; + filenameCallback?: (file: File) => string; } export default async function uploadFiles(endpoint = '', files: File[] | FileList = [], options: UploadOptions = {}) { const dialog = new Dialog({ @@ -38,7 +39,7 @@ export default async function uploadFiles(endpoint = '', files: File[] | FileLis if (Number.isNaN(+i)) continue; const file = files[i]; const data = new FormData(); - data.append('filename', file.name); + data.append('filename', options.filenameCallback?.(file) || file.name); data.append('file', file); if (options.type) data.append('type', options.type); data.append('operation', 'upload_file'); diff --git a/packages/ui-default/package.json b/packages/ui-default/package.json index 9490db1c1..133a41467 100644 --- a/packages/ui-default/package.json +++ b/packages/ui-default/package.json @@ -25,7 +25,7 @@ "@fontsource/source-code-pro": "^5.0.18", "@fontsource/ubuntu-mono": "^5.0.20", "@hydrooj/utils": "workspace:^", - "@sentry/browser": "^8.12.0", + "@sentry/browser": "^8.13.0", "@sentry/webpack-plugin": "^2.20.1", "@simplewebauthn/browser": "7.4.0", "@svgr/webpack": "^8.1.0", @@ -40,6 +40,7 @@ "@types/react": "^18.3.3", "@types/react-dom": "^18.3.0", "@types/redux-logger": "^3.0.13", + "@types/uuid": "^10", "@types/webpack-env": "^1.18.5", "@vscode/codicons": "^0.0.36", "ajv": "^8.16.0", @@ -56,7 +57,7 @@ "css-loader": "^6.11.0", "diff": "^5.2.0", "diff-dom": "^5.1.3", - "echarts": "^5.5.0", + "echarts": "^5.5.1", "emojis-keywords": "2.0.0", "emojis-list": "2.1.0", "esbuild-loader": "^4.2.0", @@ -73,9 +74,10 @@ "jquery.easing": "^1.4.1", "jquery.transit": "^0.9.12", "matchmedia-polyfill": "^0.3.2", + "md-editor-rt": "^4.17.0", "mini-css-extract-plugin": "^2.9.0", "moment": "^2.30.1", - "monaco-editor": "0.46.0", + "monaco-editor": "0.50.0", "monaco-editor-nls": "^3.1.0", "monaco-editor-webpack-plugin": "^7.1.0", "monaco-themes": "^0.4.4", @@ -118,7 +120,6 @@ "timeago-react": "^3.0.6", "timeago.js": "^4.0.2", "ts-loader": "^9.5.1", - "vditor": "^3.10.4", "vinyl-buffer": "^1.0.1", "web-streams-polyfill": "^4.0.0", "webpack": "^5.92.1", @@ -147,6 +148,7 @@ "nunjucks": "^3.2.4", "schemastery-jsonschema": "^1.1.0", "streamsaver": "^2.0.6", + "uuid": "^10.0.0", "xss": "^1.0.15" } } diff --git a/packages/ui-default/pages/problem_edit.page.js b/packages/ui-default/pages/problem_edit.page.js index cbf3a3b9d..5bd6fec1a 100644 --- a/packages/ui-default/pages/problem_edit.page.js +++ b/packages/ui-default/pages/problem_edit.page.js @@ -154,7 +154,7 @@ async function handleSection(ev, sidebar, type) { $section.removeClass('animating'); } -export default new NamedPage(['problem_create', 'problem_edit'], (pagename) => { +export default new NamedPage(['problem_create', 'problem_edit'], () => { let confirmed = false; $(document).on('click', '[name="operation"]', (ev) => { ev.preventDefault(); @@ -240,14 +240,6 @@ export default new NamedPage(['problem_create', 'problem_edit'], (pagename) => { await download(`${pdoc.docId} ${pdoc.title}.zip`, targets); } - setInterval(() => { - $('img').each(function () { - if (this.src.startsWith('file://')) { - $(this).attr('src', $(this).attr('src').replace('file://', (pagename === 'problem_create' ? `/file/${UserContext._id}/` : './file/'))); - } - }); - }, 500); - const $main = $('textarea[data-editor]'); const $field = $('textarea[data-markdown-upload]'); let content = $field.val(); diff --git a/packages/ui-default/service-worker.ts b/packages/ui-default/service-worker.ts index 514212c0b..511b5748a 100644 --- a/packages/ui-default/service-worker.ts +++ b/packages/ui-default/service-worker.ts @@ -89,7 +89,7 @@ self.addEventListener('notificationclick', (event) => { }); const PRECACHE = `precache-${process.env.VERSION}`; -const DO_NOT_PRECACHE = ['vditor', '.worker.js', 'fonts', 'i.monaco']; +const DO_NOT_PRECACHE = ['.worker.js', 'fonts', 'i.monaco']; function shouldCachePath(path: string) { if (!path.split('?')[0].split('/').pop()) return false; diff --git a/packages/ui-default/setting.yaml b/packages/ui-default/setting.yaml index 236cfd036..58fb242a7 100644 --- a/packages/ui-default/setting.yaml +++ b/packages/ui-default/setting.yaml @@ -43,8 +43,6 @@ nav_logo_dark: default: /components/navigation/nav-logo-small_dark.png preferredEditorType: type: - wysiwyg: WYSIWYG - ir: Instant Rendering sv: Split View monaco: Monaco Editor family: markdown diff --git a/packages/ui-default/theme/dark.styl b/packages/ui-default/theme/dark.styl index 00bd75e1e..409553552 100644 --- a/packages/ui-default/theme/dark.styl +++ b/packages/ui-default/theme/dark.styl @@ -196,7 +196,7 @@ $hover-background-color = #424242 background-color: #171717 color: #999 - pre:not(.vditor-sv), code + pre, code background-color: #171717 .scratchpad__toolbar diff --git a/packages/ui-default/theme/default.js b/packages/ui-default/theme/default.js index 0c7a2a817..1ce9815df 100644 --- a/packages/ui-default/theme/default.js +++ b/packages/ui-default/theme/default.js @@ -1,5 +1,4 @@ import 'normalize.css/normalize.css'; -import 'vditor/dist/index.css'; import 'allotment/dist/style.css'; import 'pickadate/lib/themes/classic.css'; import 'pickadate/lib/themes/classic.date.css';