mirror of
https://gitcode.com/gh_mirrors/hy/Hydro.git
synced 2026-10-17 09:36:08 +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
|
||||
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;
|
||||
};
|
||||
}
|
||||
@@ -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 `\
|
||||
<object classid="clsid:${randomUUID().toUpperCase()}">
|
||||
<object classid="clsid:${uuid().toUpperCase()}">
|
||||
<param name="SRC" value="${src}" >
|
||||
<embed width="100%" style="min-height: 100vh;border: none;" fullscreen="yes" src="${src}">
|
||||
<noembed></noembed>
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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/' },
|
||||
],
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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
|
||||
+103
-109
@@ -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 = [''];
|
||||
[, 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 data-mode="monaco">Monaco Editor</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 <MdEditor
|
||||
className='textbox'
|
||||
autoFocus={hasFocus}
|
||||
codeTheme='github'
|
||||
codeStyleReverse={false}
|
||||
ref={renderCallback}
|
||||
modelValue={val}
|
||||
noMermaid
|
||||
noPrettier
|
||||
autoDetectCode
|
||||
toolbarsExclude={[
|
||||
// 'bold',
|
||||
// 'underline',
|
||||
// 'italic',
|
||||
// '-',
|
||||
// 'strikeThrough',
|
||||
// 'title',
|
||||
// 'sub',
|
||||
// 'sup',
|
||||
// 'quote',
|
||||
// 'unorderedList',
|
||||
// 'orderedList',
|
||||
// 'task',
|
||||
// '-',
|
||||
// 'codeRow',
|
||||
// 'code',
|
||||
// 'link',
|
||||
// 'image',
|
||||
// 'table',
|
||||
'mermaid',
|
||||
// 'katex',
|
||||
// '-',
|
||||
// 'revoke',
|
||||
// 'next',
|
||||
'save',
|
||||
// '=',
|
||||
'pageFullscreen',
|
||||
'fullscreen',
|
||||
// 'preview',
|
||||
'previewOnly',
|
||||
'htmlPreview',
|
||||
// 'catalog',
|
||||
'github',
|
||||
]}
|
||||
onChange={(v) => {
|
||||
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(<EditorComponent />);
|
||||
$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();
|
||||
@@ -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;
|
||||
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');
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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';
|
||||
|
||||
Reference in new issue
Block a user