mirror of
https://gitcode.com/gh_mirrors/hy/Hydro.git
synced 2026-10-20 01:38:19 +08:00
ui: drop vditor
This commit is contained in:
16 files changed
+399
-153
No files matched your search
@@ -0,0 +1,235 @@
|
|||||||
|
// Process 
|
||||||
|
// ^^^^^^^^ 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]( <href> "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]( <href> "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]( <href> "title" )
|
||||||
|
// ^^ skipping these spaces
|
||||||
|
start = pos;
|
||||||
|
for (; pos < max; pos++) {
|
||||||
|
code = state.src.charCodeAt(pos);
|
||||||
|
if (code !== 0x20 && code !== 0x0A) { break; }
|
||||||
|
}
|
||||||
|
|
||||||
|
// [link]( <href> "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]( <href> "title" )
|
||||||
|
// ^^ skipping these spaces
|
||||||
|
for (; pos < max; pos++) {
|
||||||
|
code = state.src.charCodeAt(pos);
|
||||||
|
if (code !== 0x20 && code !== 0x0A) { break; }
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
title = '';
|
||||||
|
}
|
||||||
|
|
||||||
|
// [link]( <href> "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]( <href> "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;
|
||||||
|
});
|
||||||
|
};
|
||||||
+6
-5
@@ -1,7 +1,8 @@
|
|||||||
// https://github.com/waylonflinn/markdown-it-katex/blob/master/index.js
|
// 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) {
|
function isValidDelim(state, pos) {
|
||||||
const max = state.posMax;
|
const max = state.posMax;
|
||||||
@@ -109,8 +110,8 @@ function block(state, start, end, silent) {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = function plugin(md) {
|
export default function plugin(md) {
|
||||||
const options = { throwOnError: false, strict: 'ignore' };
|
const options = { throwOnError: false, strict: 'ignore', displayMode: false };
|
||||||
const render = (latex: string, displayMode = false) => {
|
const render = (latex: string, displayMode = false) => {
|
||||||
options.displayMode = displayMode;
|
options.displayMode = displayMode;
|
||||||
try {
|
try {
|
||||||
@@ -136,4 +137,4 @@ module.exports = function plugin(md) {
|
|||||||
});
|
});
|
||||||
md.renderer.rules.math_inline = inlineRenderer;
|
md.renderer.rules.math_inline = inlineRenderer;
|
||||||
md.renderer.rules.math_block = blockRenderer;
|
md.renderer.rules.math_block = blockRenderer;
|
||||||
};
|
}
|
||||||
@@ -1,8 +1,8 @@
|
|||||||
/* eslint-disable max-len */
|
/* eslint-disable max-len */
|
||||||
/* eslint-disable prefer-destructuring */
|
/* eslint-disable prefer-destructuring */
|
||||||
|
|
||||||
import { randomUUID } from 'crypto';
|
import type MarkdownIt from 'markdown-it';
|
||||||
import MarkdownIt from 'markdown-it';
|
import { v4 as uuid } from 'uuid';
|
||||||
|
|
||||||
const allowFullScreen = ' webkitallowfullscreen mozallowfullscreen allowfullscreen';
|
const allowFullScreen = ' webkitallowfullscreen mozallowfullscreen allowfullscreen';
|
||||||
|
|
||||||
@@ -110,7 +110,7 @@ export function Media(md: MarkdownIt) {
|
|||||||
if (service === 'pdf') {
|
if (service === 'pdf') {
|
||||||
if (src.startsWith('file://') || src.startsWith('./')) src += src.includes('?') ? '&noDisposition=1' : '?noDisposition=1';
|
if (src.startsWith('file://') || src.startsWith('./')) src += src.includes('?') ? '&noDisposition=1' : '?noDisposition=1';
|
||||||
return `\
|
return `\
|
||||||
<object classid="clsid:${randomUUID().toUpperCase()}">
|
<object classid="clsid:${uuid().toUpperCase()}">
|
||||||
<param name="SRC" value="${src}" >
|
<param name="SRC" value="${src}" >
|
||||||
<embed width="100%" style="min-height: 100vh;border: none;" fullscreen="yes" src="${src}">
|
<embed width="100%" style="min-height: 100vh;border: none;" fullscreen="yes" src="${src}">
|
||||||
<noembed></noembed>
|
<noembed></noembed>
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ const MergeCells = require('markdown-it-merge-cells');
|
|||||||
// @[youtube](https://youtube.com/watch?v=xxx)
|
// @[youtube](https://youtube.com/watch?v=xxx)
|
||||||
// @[pdf](https://foo.com/bar.pdf)
|
// @[pdf](https://foo.com/bar.pdf)
|
||||||
const { Media } = require('./markdown-it-media');
|
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');
|
const { xssProtector } = require('./markdown-it-xss');
|
||||||
|
|
||||||
class Markdown extends MarkdownIt {
|
class Markdown extends MarkdownIt {
|
||||||
|
|||||||
@@ -269,14 +269,13 @@ export default async function (env: { watch?: boolean, production?: boolean, mea
|
|||||||
filename: '[name].css?[fullhash:6]',
|
filename: '[name].css?[fullhash:6]',
|
||||||
}),
|
}),
|
||||||
new WebpackManifestPlugin({}),
|
new WebpackManifestPlugin({}),
|
||||||
new webpack.IgnorePlugin({ resourceRegExp: /(^\.\/locale$|mathjax|abcjs|vditor.+\.d\.ts)/ }),
|
new webpack.IgnorePlugin({ resourceRegExp: /(^\.\/locale$)/ }),
|
||||||
new CopyWebpackPlugin({
|
new CopyWebpackPlugin({
|
||||||
patterns: [
|
patterns: [
|
||||||
{ from: root('static') },
|
{ from: root('static') },
|
||||||
{ from: root('components/navigation/nav-logo-small_dark.png'), to: 'components/navigation/nav-logo-small_dark.png' },
|
{ 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'))}/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('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: 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/' },
|
{ from: `${dirname(require.resolve('monaco-themes/package.json'))}/themes`, to: 'monaco/themes/' },
|
||||||
],
|
],
|
||||||
|
|||||||
@@ -127,11 +127,6 @@ async function main() {
|
|||||||
if (fs.existsSync('public/theme.css')) {
|
if (fs.existsSync('public/theme.css')) {
|
||||||
fs.copyFileSync('public/theme.css', `public/theme-${pkg.version}.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)));
|
await Promise.all(globbySync('public/**/*.map').map((i) => fs.remove(i)));
|
||||||
}
|
}
|
||||||
process.chdir(dir);
|
process.chdir(dir);
|
||||||
|
|||||||
@@ -1,12 +1,3 @@
|
|||||||
.vditor
|
|
||||||
margin-bottom: rem(10px)
|
|
||||||
|
|
||||||
.vditor-preview__action
|
|
||||||
display: none
|
|
||||||
|
|
||||||
.vditor ol
|
|
||||||
list-style: decimal
|
|
||||||
|
|
||||||
.decoration-hide
|
.decoration-hide
|
||||||
color: grey !important
|
color: grey !important
|
||||||
background-color: grey !important
|
background-color: grey !important
|
||||||
+103
-109
@@ -1,35 +1,10 @@
|
|||||||
import $ from 'jquery';
|
import $ from 'jquery';
|
||||||
import { nanoid } from 'nanoid';
|
import { nanoid } from 'nanoid';
|
||||||
|
import React from 'react';
|
||||||
|
import ReactDOM from 'react-dom/client';
|
||||||
import DOMAttachedObject from 'vj/components/DOMAttachedObject';
|
import DOMAttachedObject from 'vj/components/DOMAttachedObject';
|
||||||
import Notification from 'vj/components/notification';
|
import { i18n } from 'vj/utils';
|
||||||
import { i18n, request } from 'vj/utils';
|
import uploadFiles from '../upload';
|
||||||
|
|
||||||
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',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
interface MonacoOptions {
|
interface MonacoOptions {
|
||||||
language?: string;
|
language?: string;
|
||||||
@@ -42,23 +17,23 @@ interface MonacoOptions {
|
|||||||
hide?: string[];
|
hide?: string[];
|
||||||
lineNumbers?: 'on' | 'off' | 'relative' | 'interval';
|
lineNumbers?: 'on' | 'off' | 'relative' | 'interval';
|
||||||
}
|
}
|
||||||
interface VditorOptions {
|
type Options = MonacoOptions;
|
||||||
theme?: 'classic' | 'dark'
|
|
||||||
}
|
|
||||||
type Options = MonacoOptions & VditorOptions;
|
|
||||||
|
|
||||||
export default class Editor extends DOMAttachedObject {
|
export default class Editor extends DOMAttachedObject {
|
||||||
static DOMAttachKey = 'vjEditorInstance';
|
static DOMAttachKey = 'vjEditorInstance';
|
||||||
model: import('../monaco').default.editor.IModel;
|
model: import('../monaco').default.editor.IModel;
|
||||||
editor: import('../monaco').default.editor.IStandaloneCodeEditor;
|
editor: import('../monaco').default.editor.IStandaloneCodeEditor;
|
||||||
vditor: import('vditor').default;
|
markdownEditor: any;
|
||||||
|
valueCache?: string;
|
||||||
|
setMarkdownEditorValue?: (v: string) => void;
|
||||||
|
reactRoot?: ReactDOM.Root;
|
||||||
isValid: boolean;
|
isValid: boolean;
|
||||||
|
|
||||||
constructor($dom, public options: Options = {}) {
|
constructor($dom, public options: Options = {}) {
|
||||||
super($dom);
|
super($dom);
|
||||||
if (UserContext.preferredEditorType === 'monaco') this.initMonaco();
|
if (UserContext.preferredEditorType === 'monaco') this.initMonaco();
|
||||||
else if (options.language && options.language !== 'markdown') this.initMonaco();
|
else if (options.language && options.language !== 'markdown') this.initMonaco();
|
||||||
else this.initVditor();
|
else this.initMarkdownEditor();
|
||||||
}
|
}
|
||||||
|
|
||||||
async initMonaco() {
|
async initMonaco() {
|
||||||
@@ -168,87 +143,102 @@ export default class Editor extends DOMAttachedObject {
|
|||||||
window.editor = this.editor;
|
window.editor = this.editor;
|
||||||
}
|
}
|
||||||
|
|
||||||
async initVditor() {
|
async initMarkdownEditor() {
|
||||||
const pagename = document.documentElement.getAttribute('data-page');
|
const pagename = document.documentElement.getAttribute('data-page');
|
||||||
const isProblemPage = ['problem_create', 'problem_edit'].includes(pagename);
|
const isProblemPage = ['problem_create', 'problem_edit'].includes(pagename);
|
||||||
const isProblemEdit = pagename === 'problem_edit';
|
const isProblemEdit = pagename === 'problem_edit';
|
||||||
const { default: Vditor } = await import('vditor');
|
const that = this;
|
||||||
const { $dom } = this;
|
const { $dom } = this;
|
||||||
const hasFocus = $dom.is(':focus') || $dom.hasClass('autofocus');
|
const hasFocus = $dom.is(':focus') || $dom.hasClass('autofocus');
|
||||||
const origin = $dom.get(0);
|
const origin = $dom.get(0);
|
||||||
const ele = document.createElement('div');
|
const ele = document.createElement('div');
|
||||||
const value = $dom.val();
|
const value = $dom.val();
|
||||||
const { onChange } = this.options;
|
const { onChange } = this.options;
|
||||||
await new Promise((resolve) => {
|
const { MdEditor } = await import('./mdeditor');
|
||||||
this.vditor = new Vditor(ele, {
|
|
||||||
...config,
|
const renderCallback = (ref) => {
|
||||||
upload: {
|
this.markdownEditor = ref;
|
||||||
multiple: false,
|
};
|
||||||
handler: (files) => {
|
|
||||||
let wrapper = ['', ''];
|
function EditorComponent() {
|
||||||
let ext: string;
|
const [val, setVal] = React.useState(value);
|
||||||
const matches = files[0].type.match(/^image\/(png|jpg|jpeg|gif)$/i);
|
that.setMarkdownEditorValue = setVal;
|
||||||
if (matches) {
|
return <MdEditor
|
||||||
wrapper = [''];
|
className='textbox'
|
||||||
[, ext] = matches;
|
autoFocus={hasFocus}
|
||||||
} else if (files[0].type === 'application/x-zip-compressed') {
|
codeTheme='github'
|
||||||
wrapper = ['[file](', ')'];
|
codeStyleReverse={false}
|
||||||
ext = 'zip';
|
ref={renderCallback}
|
||||||
}
|
modelValue={val}
|
||||||
if (!ext) return i18n('No Supported file type.');
|
noMermaid
|
||||||
const filename = `${nanoid()}.${ext}`;
|
noPrettier
|
||||||
const data = new FormData();
|
autoDetectCode
|
||||||
data.append('filename', filename);
|
toolbarsExclude={[
|
||||||
data.append('file', files[0]);
|
// 'bold',
|
||||||
data.append('operation', 'upload_file');
|
// 'underline',
|
||||||
if (isProblemEdit) data.append('type', 'additional_file');
|
// 'italic',
|
||||||
let progress = 0;
|
// '-',
|
||||||
request.postFile(isProblemEdit ? './files' : '/file', data, {
|
// 'strikeThrough',
|
||||||
xhr: () => {
|
// 'title',
|
||||||
const xhr = new XMLHttpRequest();
|
// 'sub',
|
||||||
xhr.upload.addEventListener('loadstart', () => this.vditor.vditor.tip.show(i18n('Uploading...'), 0));
|
// 'sup',
|
||||||
xhr.upload.addEventListener('progress', (e) => {
|
// 'quote',
|
||||||
if (!e.lengthComputable) return;
|
// 'unorderedList',
|
||||||
const percentComplete = Math.round((e.loaded / e.total) * 100);
|
// 'orderedList',
|
||||||
if (percentComplete === progress) return;
|
// 'task',
|
||||||
progress = percentComplete;
|
// '-',
|
||||||
this.vditor.vditor.tip.show(`${i18n('Uploading...')} ${percentComplete}%`, 0);
|
// 'codeRow',
|
||||||
}, false);
|
// 'code',
|
||||||
return xhr;
|
// 'link',
|
||||||
},
|
// 'image',
|
||||||
})
|
// 'table',
|
||||||
.then(() => {
|
'mermaid',
|
||||||
this.vditor.insertValue(`${wrapper.join(`${isProblemPage ? 'file://' : `/file/${UserContext._id}/`}${filename}`)} `);
|
// 'katex',
|
||||||
this.vditor.vditor.tip.hide();
|
// '-',
|
||||||
})
|
// 'revoke',
|
||||||
.catch((e: { message: string; }) => {
|
// 'next',
|
||||||
console.error(e);
|
'save',
|
||||||
return `${i18n('Upload Failed')}: ${e.message}`;
|
// '=',
|
||||||
});
|
'pageFullscreen',
|
||||||
return null;
|
'fullscreen',
|
||||||
},
|
// 'preview',
|
||||||
},
|
'previewOnly',
|
||||||
...this.options,
|
'htmlPreview',
|
||||||
after: () => {
|
// 'catalog',
|
||||||
const pos = $(ele).find('button[data-mode="sv"]').get();
|
'github',
|
||||||
const button = $('<button data-mode="monaco">Monaco Editor</button>');
|
]}
|
||||||
button.on('click', (e) => {
|
onChange={(v) => {
|
||||||
Notification.info('You can select this in PreferenceSettings');
|
that.valueCache = v;
|
||||||
e.preventDefault();
|
setVal(v);
|
||||||
});
|
|
||||||
button.insertAfter(pos);
|
|
||||||
resolve(null);
|
|
||||||
},
|
|
||||||
input(v) {
|
|
||||||
$dom.val(v);
|
$dom.val(v);
|
||||||
$dom.text(v);
|
$dom.text(v);
|
||||||
if (onChange) onChange(v);
|
onChange?.(v);
|
||||||
},
|
}}
|
||||||
value,
|
onUploadImg={async (files, callback) => {
|
||||||
cache: { id: Math.random().toString() },
|
let ext: string;
|
||||||
});
|
const matches = files[0].type.match(/^image\/(png|jpg|jpeg|gif)$/i);
|
||||||
});
|
if (matches) {
|
||||||
$(ele).addClass('textbox');
|
[, 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(<EditorComponent />);
|
||||||
$dom.hide();
|
$dom.hide();
|
||||||
origin.parentElement.appendChild(ele);
|
origin.parentElement.appendChild(ele);
|
||||||
this.isValid = true;
|
this.isValid = true;
|
||||||
@@ -257,7 +247,7 @@ export default class Editor extends DOMAttachedObject {
|
|||||||
|
|
||||||
destroy() {
|
destroy() {
|
||||||
this.detach();
|
this.detach();
|
||||||
if (this.vditor?.destroy) this.vditor.destroy();
|
if (this.reactRoot) this.reactRoot.unmount();
|
||||||
else if (this.editor?.dispose) this.editor.dispose();
|
else if (this.editor?.dispose) this.editor.dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -267,13 +257,17 @@ export default class Editor extends DOMAttachedObject {
|
|||||||
|
|
||||||
value(val?: string) {
|
value(val?: string) {
|
||||||
this.ensureValid();
|
this.ensureValid();
|
||||||
if (typeof val === 'string') return (this.editor || this.vditor).setValue(val);
|
if (typeof val === 'string') {
|
||||||
return (this.editor || this.vditor).getValue({ lineEnding: '\n', preserveBOM: false });
|
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() {
|
focus() {
|
||||||
this.ensureValid();
|
this.ensureValid();
|
||||||
this.vditor?.focus();
|
|
||||||
if (!this.editor || !this.model) return;
|
if (!this.editor || !this.model) return;
|
||||||
this.editor.focus();
|
this.editor.focus();
|
||||||
const range = this.model.getFullModelRange();
|
const range = this.model.getFullModelRange();
|
||||||
@@ -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 };
|
||||||
@@ -13,6 +13,7 @@ interface UploadOptions {
|
|||||||
pjax?: boolean;
|
pjax?: boolean;
|
||||||
sidebar?: boolean;
|
sidebar?: boolean;
|
||||||
singleFileUploadCallback?: (file: File) => any;
|
singleFileUploadCallback?: (file: File) => any;
|
||||||
|
filenameCallback?: (file: File) => string;
|
||||||
}
|
}
|
||||||
export default async function uploadFiles(endpoint = '', files: File[] | FileList = [], options: UploadOptions = {}) {
|
export default async function uploadFiles(endpoint = '', files: File[] | FileList = [], options: UploadOptions = {}) {
|
||||||
const dialog = new Dialog({
|
const dialog = new Dialog({
|
||||||
@@ -38,7 +39,7 @@ export default async function uploadFiles(endpoint = '', files: File[] | FileLis
|
|||||||
if (Number.isNaN(+i)) continue;
|
if (Number.isNaN(+i)) continue;
|
||||||
const file = files[i];
|
const file = files[i];
|
||||||
const data = new FormData();
|
const data = new FormData();
|
||||||
data.append('filename', file.name);
|
data.append('filename', options.filenameCallback?.(file) || file.name);
|
||||||
data.append('file', file);
|
data.append('file', file);
|
||||||
if (options.type) data.append('type', options.type);
|
if (options.type) data.append('type', options.type);
|
||||||
data.append('operation', 'upload_file');
|
data.append('operation', 'upload_file');
|
||||||
|
|||||||
@@ -25,7 +25,7 @@
|
|||||||
"@fontsource/source-code-pro": "^5.0.18",
|
"@fontsource/source-code-pro": "^5.0.18",
|
||||||
"@fontsource/ubuntu-mono": "^5.0.20",
|
"@fontsource/ubuntu-mono": "^5.0.20",
|
||||||
"@hydrooj/utils": "workspace:^",
|
"@hydrooj/utils": "workspace:^",
|
||||||
"@sentry/browser": "^8.12.0",
|
"@sentry/browser": "^8.13.0",
|
||||||
"@sentry/webpack-plugin": "^2.20.1",
|
"@sentry/webpack-plugin": "^2.20.1",
|
||||||
"@simplewebauthn/browser": "7.4.0",
|
"@simplewebauthn/browser": "7.4.0",
|
||||||
"@svgr/webpack": "^8.1.0",
|
"@svgr/webpack": "^8.1.0",
|
||||||
@@ -40,6 +40,7 @@
|
|||||||
"@types/react": "^18.3.3",
|
"@types/react": "^18.3.3",
|
||||||
"@types/react-dom": "^18.3.0",
|
"@types/react-dom": "^18.3.0",
|
||||||
"@types/redux-logger": "^3.0.13",
|
"@types/redux-logger": "^3.0.13",
|
||||||
|
"@types/uuid": "^10",
|
||||||
"@types/webpack-env": "^1.18.5",
|
"@types/webpack-env": "^1.18.5",
|
||||||
"@vscode/codicons": "^0.0.36",
|
"@vscode/codicons": "^0.0.36",
|
||||||
"ajv": "^8.16.0",
|
"ajv": "^8.16.0",
|
||||||
@@ -56,7 +57,7 @@
|
|||||||
"css-loader": "^6.11.0",
|
"css-loader": "^6.11.0",
|
||||||
"diff": "^5.2.0",
|
"diff": "^5.2.0",
|
||||||
"diff-dom": "^5.1.3",
|
"diff-dom": "^5.1.3",
|
||||||
"echarts": "^5.5.0",
|
"echarts": "^5.5.1",
|
||||||
"emojis-keywords": "2.0.0",
|
"emojis-keywords": "2.0.0",
|
||||||
"emojis-list": "2.1.0",
|
"emojis-list": "2.1.0",
|
||||||
"esbuild-loader": "^4.2.0",
|
"esbuild-loader": "^4.2.0",
|
||||||
@@ -73,9 +74,10 @@
|
|||||||
"jquery.easing": "^1.4.1",
|
"jquery.easing": "^1.4.1",
|
||||||
"jquery.transit": "^0.9.12",
|
"jquery.transit": "^0.9.12",
|
||||||
"matchmedia-polyfill": "^0.3.2",
|
"matchmedia-polyfill": "^0.3.2",
|
||||||
|
"md-editor-rt": "^4.17.0",
|
||||||
"mini-css-extract-plugin": "^2.9.0",
|
"mini-css-extract-plugin": "^2.9.0",
|
||||||
"moment": "^2.30.1",
|
"moment": "^2.30.1",
|
||||||
"monaco-editor": "0.46.0",
|
"monaco-editor": "0.50.0",
|
||||||
"monaco-editor-nls": "^3.1.0",
|
"monaco-editor-nls": "^3.1.0",
|
||||||
"monaco-editor-webpack-plugin": "^7.1.0",
|
"monaco-editor-webpack-plugin": "^7.1.0",
|
||||||
"monaco-themes": "^0.4.4",
|
"monaco-themes": "^0.4.4",
|
||||||
@@ -118,7 +120,6 @@
|
|||||||
"timeago-react": "^3.0.6",
|
"timeago-react": "^3.0.6",
|
||||||
"timeago.js": "^4.0.2",
|
"timeago.js": "^4.0.2",
|
||||||
"ts-loader": "^9.5.1",
|
"ts-loader": "^9.5.1",
|
||||||
"vditor": "^3.10.4",
|
|
||||||
"vinyl-buffer": "^1.0.1",
|
"vinyl-buffer": "^1.0.1",
|
||||||
"web-streams-polyfill": "^4.0.0",
|
"web-streams-polyfill": "^4.0.0",
|
||||||
"webpack": "^5.92.1",
|
"webpack": "^5.92.1",
|
||||||
@@ -147,6 +148,7 @@
|
|||||||
"nunjucks": "^3.2.4",
|
"nunjucks": "^3.2.4",
|
||||||
"schemastery-jsonschema": "^1.1.0",
|
"schemastery-jsonschema": "^1.1.0",
|
||||||
"streamsaver": "^2.0.6",
|
"streamsaver": "^2.0.6",
|
||||||
|
"uuid": "^10.0.0",
|
||||||
"xss": "^1.0.15"
|
"xss": "^1.0.15"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -154,7 +154,7 @@ async function handleSection(ev, sidebar, type) {
|
|||||||
$section.removeClass('animating');
|
$section.removeClass('animating');
|
||||||
}
|
}
|
||||||
|
|
||||||
export default new NamedPage(['problem_create', 'problem_edit'], (pagename) => {
|
export default new NamedPage(['problem_create', 'problem_edit'], () => {
|
||||||
let confirmed = false;
|
let confirmed = false;
|
||||||
$(document).on('click', '[name="operation"]', (ev) => {
|
$(document).on('click', '[name="operation"]', (ev) => {
|
||||||
ev.preventDefault();
|
ev.preventDefault();
|
||||||
@@ -240,14 +240,6 @@ export default new NamedPage(['problem_create', 'problem_edit'], (pagename) => {
|
|||||||
await download(`${pdoc.docId} ${pdoc.title}.zip`, targets);
|
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 $main = $('textarea[data-editor]');
|
||||||
const $field = $('textarea[data-markdown-upload]');
|
const $field = $('textarea[data-markdown-upload]');
|
||||||
let content = $field.val();
|
let content = $field.val();
|
||||||
|
|||||||
@@ -89,7 +89,7 @@ self.addEventListener('notificationclick', (event) => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
const PRECACHE = `precache-${process.env.VERSION}`;
|
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) {
|
function shouldCachePath(path: string) {
|
||||||
if (!path.split('?')[0].split('/').pop()) return false;
|
if (!path.split('?')[0].split('/').pop()) return false;
|
||||||
|
|||||||
@@ -43,8 +43,6 @@ nav_logo_dark:
|
|||||||
default: /components/navigation/nav-logo-small_dark.png
|
default: /components/navigation/nav-logo-small_dark.png
|
||||||
preferredEditorType:
|
preferredEditorType:
|
||||||
type:
|
type:
|
||||||
wysiwyg: WYSIWYG
|
|
||||||
ir: Instant Rendering
|
|
||||||
sv: Split View
|
sv: Split View
|
||||||
monaco: Monaco Editor
|
monaco: Monaco Editor
|
||||||
family: markdown
|
family: markdown
|
||||||
|
|||||||
@@ -196,7 +196,7 @@ $hover-background-color = #424242
|
|||||||
background-color: #171717
|
background-color: #171717
|
||||||
color: #999
|
color: #999
|
||||||
|
|
||||||
pre:not(.vditor-sv), code
|
pre, code
|
||||||
background-color: #171717
|
background-color: #171717
|
||||||
|
|
||||||
.scratchpad__toolbar
|
.scratchpad__toolbar
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
import 'normalize.css/normalize.css';
|
import 'normalize.css/normalize.css';
|
||||||
import 'vditor/dist/index.css';
|
|
||||||
import 'allotment/dist/style.css';
|
import 'allotment/dist/style.css';
|
||||||
import 'pickadate/lib/themes/classic.css';
|
import 'pickadate/lib/themes/classic.css';
|
||||||
import 'pickadate/lib/themes/classic.date.css';
|
import 'pickadate/lib/themes/classic.date.css';
|
||||||
|
|||||||
Reference in new issue
Block a user