ui: milkdown

This commit is contained in:
undefined committed 2022-05-03 23:59:47 +08:00
1 parent 4f4e6f9377
commit 8e95482246
11 files changed
+542 -155

No files matched your search

@@ -204,7 +204,6 @@ export default function (env = {}) {
new CopyWebpackPlugin({ new CopyWebpackPlugin({
patterns: [ patterns: [
{ from: root('static') }, { from: root('static') },
{ from: root(`${dirname(require.resolve('vditor/package.json'))}/dist`), to: 'vditor/dist' },
{ from: `${dirname(require.resolve('monaco-themes/package.json'))}/themes`, to: 'monaco/themes/' }, { from: `${dirname(require.resolve('monaco-themes/package.json'))}/themes`, to: 'monaco/themes/' },
{ from: root('.build/sharedworker.js'), to: 'sharedworker.js' }, { from: root('.build/sharedworker.js'), to: 'sharedworker.js' },
], ],
-7
View File
@@ -98,13 +98,6 @@ async function main() {
if (fs.existsSync('public/default.theme.css')) { if (fs.existsSync('public/default.theme.css')) {
fs.copyFileSync('public/default.theme.css', `public/default-${pkg.version}.theme.css`); fs.copyFileSync('public/default.theme.css', `public/default-${pkg.version}.theme.css`);
} }
if (argv.options.production) {
fs.removeSync('public/vditor/dist/js/mathjax');
fs.removeSync('public/vditor/dist/js/echarts');
fs.removeSync('public/vditor/dist/js/graphviz');
fs.removeSync('public/vditor/dist/js/mermaid');
fs.removeSync('public/vditor/dist/js/abcjs');
}
} }
process.chdir(dir); process.chdir(dir);
} }
@@ -1,12 +1,7 @@
.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
.code-fence_selector
height: 2.2em !important
width: 10em !important
+135 -132
View File
@@ -1,32 +1,11 @@
import _ from 'lodash'; import _ from 'lodash';
import Notification from 'vj/components/notification'; import Notification from 'vj/components/notification';
import DOMAttachedObject from 'vj/components/DOMAttachedObject'; import DOMAttachedObject from 'vj/components/DOMAttachedObject';
import type { Node } from 'prosemirror-model';
import { nanoid } from 'nanoid'; import { nanoid } from 'nanoid';
import i18n from 'vj/utils/i18n'; import i18n from 'vj/utils/i18n';
import request from 'vj/utils/request'; import request from 'vj/utils/request';
import tpl from 'vj/utils/tpl';
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,
},
},
};
interface MonacoOptions { interface MonacoOptions {
language?: string; language?: string;
@@ -38,26 +17,35 @@ interface MonacoOptions {
value?: string; value?: string;
hide?: string[]; hide?: string[];
} }
interface VditorOptions { type Options = MonacoOptions;
theme?: 'classic' | 'dark'
}
type Options = MonacoOptions & VditorOptions;
export default class Editor extends DOMAttachedObject { export default class VjEditor extends DOMAttachedObject {
static DOMAttachKey = 'vjEditorInstance'; static DOMAttachKey = 'vjEditorInstance';
isValid: boolean;
private lock: boolean;
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; milkdown: import('@milkdown/core').Editor & {
isValid: boolean; setValue?: (val: string) => void;
getValue?: () => string;
dispose?: () => void;
};
height = 0;
constructor($dom, public options: Options = {}) { constructor($dom, public options: Options = {}) {
super($dom); super($dom);
if (UserContext.preferredEditorType === 'monaco') this.initMonaco(); $dom.hide();
else if (options.language && options.language !== 'markdown') this.initMonaco(); const root = $('<div class="row editor-root" style="width: 100%;"></div>');
else this.initVditor(); const left = $('<div class="medium-6 columns textbox"></div>');
const right = $('<div class="medium-6 columns textbox typo"></div>');
root.append(left, right);
$($dom).parent().append(root);
this.initMonaco(left.get(0));
if ((options?.language || 'markdown') === 'markdown') this.initMilkdown(right.get(0));
} }
async initMonaco() { async initMonaco(ele) {
const { load } = await import('vj/components/monaco/loader'); const { load } = await import('vj/components/monaco/loader');
const { const {
onChange, language = 'markdown', onChange, language = 'markdown',
@@ -67,16 +55,9 @@ export default class Editor extends DOMAttachedObject {
hide = [], hide = [],
} = this.options; } = this.options;
const { monaco, registerAction } = await load([language]); const { monaco, registerAction } = await load([language]);
const { $dom } = this; const hasFocus = this.$dom.is(':focus') || this.$dom.hasClass('autofocus');
const hasFocus = $dom.is(':focus') || $dom.hasClass('autofocus'); if (!autoResize && this.$dom.height()) $(ele).height(this.$dom.height());
const origin = $dom.get(0); const value = this.options.value || this.$dom.val();
const ele = document.createElement('div');
$(ele).width('100%').addClass('textbox');
if (!autoResize && $dom.height()) $(ele).height($dom.height());
$dom.hide();
origin.parentElement.appendChild(ele);
const value = this.options.value || $dom.val();
// eslint-disable-next-line no-nested-ternary
this.model = typeof model === 'string' this.model = typeof model === 'string'
? monaco.editor.getModel(monaco.Uri.parse(model)) ? monaco.editor.getModel(monaco.Uri.parse(model))
|| monaco.editor.createModel(value, language === 'auto' ? undefined : language, monaco.Uri.parse(model)) || monaco.editor.createModel(value, language === 'auto' ? undefined : language, monaco.Uri.parse(model))
@@ -100,7 +81,7 @@ export default class Editor extends DOMAttachedObject {
if (!editorElement) return; if (!editorElement) return;
const lineHeight = this.editor.getOption(monaco.editor.EditorOption.lineHeight); const lineHeight = this.editor.getOption(monaco.editor.EditorOption.lineHeight);
const lineCount = this.editor.getModel()?.getLineCount() || 1; const lineCount = this.editor.getModel()?.getLineCount() || 1;
let height = this.editor.getTopForLineNumber(lineCount + 1) + lineHeight; let height = Math.max(this.editor.getTopForLineNumber(lineCount + 1) + lineHeight, this.height);
if (prevHeight !== height) { if (prevHeight !== height) {
if (window.innerHeight * 1.5 < height) { if (window.innerHeight * 1.5 < height) {
height = window.innerHeight; height = window.innerHeight;
@@ -147,9 +128,13 @@ export default class Editor extends DOMAttachedObject {
} }
this.editor.onDidChangeModelContent(() => { this.editor.onDidChangeModelContent(() => {
const val = this.editor.getValue({ lineEnding: '\n', preserveBOM: false }); const val = this.editor.getValue({ lineEnding: '\n', preserveBOM: false });
$dom.val(val); this.$dom.val(val);
$dom.text(val); this.$dom.text(val);
if (onChange) onChange(val); if (this.lock) return;
this.lock = true;
this.milkdown?.setValue(val);
onChange?.(val);
this.lock = false;
}); });
this.isValid = true; this.isValid = true;
if (hasFocus) this.focus(); if (hasFocus) this.focus();
@@ -160,97 +145,111 @@ export default class Editor extends DOMAttachedObject {
window.editor = this.editor; window.editor = this.editor;
} }
async initVditor() { async initMilkdown(ele) {
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 [
const { $dom } = this; {
const hasFocus = $dom.is(':focus') || $dom.hasClass('autofocus'); Editor, rootCtx, defaultValueCtx, editorViewCtx, parserCtx, serializerCtx,
const origin = $dom.get(0); },
const ele = document.createElement('div'); { nord }, { Slice }, { gfm }, { listener, listenerCtx },
const value = $dom.val(); { math }, { history }, { clipboard }, { tooltip }, { slash },
const { onChange } = this.options; { emoji }, { upload, uploadPlugin }, { diagram }, { prism }, { cursor },
await new Promise((resolve) => { { menu },
this.vditor = new Vditor(ele, { ] = await Promise.all([
...config, import('@milkdown/core'), import('./theme'), import('@milkdown/prose'),
upload: { import('@milkdown/preset-gfm'), import('@milkdown/plugin-listener'),
multiple: false, import('@milkdown/plugin-math'),
handler: (files) => { import('@milkdown/plugin-history'),
let wrapper = ['', '']; import('@milkdown/plugin-clipboard'),
let ext: string; import('@milkdown/plugin-tooltip'),
const matches = files[0].type.match(/^image\/(png|jpg|jpeg|gif)$/i); import('@milkdown/plugin-slash'),
if (matches) { import('@milkdown/plugin-emoji'),
wrapper = ['![image](', ')']; import('@milkdown/plugin-upload'),
[, ext] = matches; import('@milkdown/plugin-diagram'),
} else if (files[0].type === 'application/x-zip-compressed') { import('@milkdown/plugin-prism'),
wrapper = ['[file](', ')']; import('@milkdown/plugin-cursor'),
ext = 'zip'; import('@milkdown/plugin-menu'),
} ]);
if (!ext) return i18n('No Supported file type.');
const filename = `${nanoid()}.${ext}`; const uploader: import('@milkdown/plugin-upload').Uploader = async (files, schema) => {
const data = new FormData(); const images: File[] = [];
data.append('filename', filename); for (let i = 0; i < files.length; i++) {
data.append('file', files[0]); const file = files.item(i);
data.append('operation', 'upload_file'); if (!file) continue;
if (isProblemEdit) data.append('type', 'additional_file'); if (!file.type.includes('image')) continue;
let progress = 0; images.push(file);
request.postFile(isProblemEdit ? './files' : '/file', data, { }
xhr: () => { const nodes: Node[] = await Promise.all(
const xhr = new XMLHttpRequest(); images.map(async (image) => {
xhr.upload.addEventListener('loadstart', () => this.vditor.vditor.tip.show(i18n('Uploading...'), 0)); console.log(image);
xhr.upload.addEventListener('progress', (e) => { // TODO
if (!e.lengthComputable) return; return null;
const percentComplete = Math.round((e.loaded / e.total) * 100); const src = await YourUploadAPI(image);
if (percentComplete === progress) return; const alt = image.name;
progress = percentComplete; return schema.nodes.image.createAndFill({
this.vditor.vditor.tip.show(`${i18n('Uploading...')} ${percentComplete}%`, 0); src,
}, false); alt,
return xhr; }) as Node;
}, }),
}) );
.then(() => { return nodes;
this.vditor.insertValue(`${wrapper.join(`${isProblemPage ? 'file://' : `/file/${UserContext._id}/`}${filename}`)} `); };
this.vditor.vditor.tip.hide();
}) this.milkdown = Editor.make()
.catch((e: { message: string; }) => { .use(nord).use(listener).use(gfm)
console.error(e); .use(math).use(history).use(clipboard).use(tooltip).use(slash)
return `${i18n('Upload Failed')}: ${e.message}`; .use(emoji).use(diagram).use(prism).use(cursor).use(menu)
}); .use(upload.configure(uploadPlugin, { uploader }))
return null; .config((ctx) => {
}, ctx.set(defaultValueCtx, this.$dom.val());
}, ctx.set(rootCtx, ele);
...this.options, ctx.get(listenerCtx).markdownUpdated((c, markdown) => {
after: () => { this.$dom.val(markdown);
const pos = $(ele).find('button[data-mode="sv"]').get(); this.$dom.text(markdown);
const button = $('<button data-mode="monaco">Monaco Editor</button>'); if (this.lock) return;
button.on('click', (e) => { this.lock = true;
Notification.info('You can select this in PreferenceSettings'); if (this.model) {
e.preventDefault(); const range = this.model.getFullModelRange();
}); this.model.pushEditOperations([], [{ range, text: markdown }], undefined);
button.insertAfter(pos); } else this.editor?.setValue?.(markdown);
resolve(null); this.options?.onChange?.(markdown);
}, this.lock = false;
input(v) { });
$dom.val(v); ctx.get(listenerCtx).updated((c) => {
$dom.text(v); this.height = $(ele).height();
if (onChange) onChange(v); });
},
value,
cache: { id: Math.random().toString() },
}); });
this.milkdown.setValue = (val) => {
this.milkdown.action((ctx) => {
const view = ctx.get(editorViewCtx);
const doc = ctx.get(parserCtx)(val);
if (!doc) return;
view.dispatch(view.state.tr.replace(0, view.state.doc.content.size, new Slice(doc.content, 0, 0)));
});
};
this.milkdown.getValue = () => this.milkdown.action((ctx) => {
const editorView = ctx.get(editorViewCtx);
const serializer = ctx.get(serializerCtx);
return serializer(editorView.state.doc);
}); });
$(ele).addClass('textbox'); this.milkdown.dispose = () => {
$dom.hide(); const view = this.milkdown.action((ctx) => ctx.get(editorViewCtx));
origin.parentElement.appendChild(ele); const root = this.milkdown.action((ctx) => ctx.get(rootCtx)) as HTMLElement;
root?.firstChild?.remove();
view?.destroy();
};
await this.milkdown.create();
$('.editor-root').prepend($('<div class="editor-menu columns"></div>'));
$('.editor-menu').prepend($('.milkdown-menu'));
this.isValid = true; this.isValid = true;
if (hasFocus) this.focus();
} }
destory() { destory() {
this.detach(); this.detach();
if (this.vditor?.destroy) this.vditor.destroy(); this.editor?.dispose();
else if (this.editor?.dispose) this.editor.dispose(); this.milkdown?.dispose();
} }
ensureValid() { ensureValid() {
@@ -259,8 +258,12 @@ 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 }); this.editor?.setValue(val);
this.milkdown?.setValue(val);
return val;
}
return (this.editor || this.milkdown)?.getValue({ lineEnding: '\n', preserveBOM: false });
} }
focus() { focus() {
@@ -271,4 +274,4 @@ export default class Editor extends DOMAttachedObject {
} }
} }
_.assign(Editor, DOMAttachedObject); _.assign(VjEditor, DOMAttachedObject);
@@ -0,0 +1,380 @@
/* Copyright 2021, Milkdown by Mirone. */
/* eslint-disable consistent-return */
import {
Emotion, ThemeBorder, ThemeFont, ThemeManager, ThemeScrollbar, ThemeShadow, ThemeSize,
hex2rgb, ThemeColor, themeFactory, ThemeGlobal, ThemeIcon,
} from '@milkdown/core';
import { getPalette } from '@milkdown/design-system';
import { injectProsemirrorView, useAllPresetRenderer } from '@milkdown/theme-pack-helper';
import { code, typography } from '@milkdown/theme-nord/src/font';
import { getIcon } from '@milkdown/theme-nord/src/icon';
import { darkColor, lightColor } from '@milkdown/theme-nord/src/nord';
export const font = {
typography,
code,
};
export const size = {
radius: '4px',
lineWidth: '1px',
};
export const getStyle = (manager: ThemeManager, emotion: Emotion) => {
const { injectGlobal, css } = emotion;
const palette = getPalette(manager);
const radius = manager.get(ThemeSize, 'radius');
const neutral = palette('neutral', 0.87);
const surface = palette('surface');
const line = palette('line');
const highlight = palette('secondary', 0.38);
const selection = css`
.ProseMirror-selectednode {
outline: ${manager.get(ThemeSize, 'lineWidth')} solid ${line};
}
li.ProseMirror-selectednode {
outline: none;
}
li.ProseMirror-selectednode::after {
${manager.get(ThemeBorder, undefined)};
}
& ::selection {
background: ${highlight};
}
`;
const editorLayout = css`
padding: 1.25em;
outline: none;
`;
const blockquote = css`
blockquote {
padding-left: 1.875em;
line-height: 1.75em;
border-left: 4px solid ${palette('primary')};
margin-left: 0;
margin-right: 0;
* {
font-size: 1em;
line-height: 1.5em;
}
}
`;
const hr = css`
hr {
height: ${manager.get(ThemeSize, 'lineWidth')};
background-color: ${line};
border-width: 0;
}
`;
const list = css`
.list-item,
.list-item > * {
margin: 0.5em 0;
}
li {
&::marker {
color: ${palette('primary')};
}
}
.task-list-item {
display: flex;
flex-direction: row;
align-items: flex-start;
&_checkbox {
margin: 0.5em 0.5em 0.5em 0;
height: 1em;
}
}
`;
// eslint-disable-next-line no-shadow
const code = css`
.code-fence {
pre {
font-family: ${manager.get(ThemeFont, 'code')};
margin: 0 1.2em !important;
white-space: pre;
overflow: auto;
${manager.get(ThemeScrollbar, ['x'])}
background-color: ${palette('background')};
color: ${palette('neutral')};
font-size: 0.875em;
border-radius: ${radius};
code {
line-height: 1.5;
font-family: ${manager.get(ThemeFont, 'code')};
}
}
}
`;
const img = css`
.image {
display: inline-block;
margin: 0 auto;
object-fit: contain;
width: 100%;
position: relative;
height: auto;
text-align: center;
}
`;
const inline = css`
.code-inline {
background-color: ${palette('neutral')};
color: ${palette('background')};
border-radius: ${radius};
font-weight: 500;
font-family: ${code};
padding: 0 0.2em;
font-size: 1.2em;
}
.strong {
font-weight: 600;
}
.link,
a {
color: ${palette('secondary')};
cursor: pointer;
transition: all 0.4s ease-in-out;
font-weight: 500;
&:hover {
background-color: ${palette('line')};
box-shadow: 0 0.2em ${palette('line')}, 0 -0.2em ${palette('line')};
}
}
.strike-through {
text-decoration-color: ${palette('secondary')};
}
`;
const footnote = css`
.footnote-definition {
${manager.get(ThemeBorder, undefined)};
border-radius: ${manager.get(ThemeSize, 'radius')};
background-color: ${palette('background')};
padding: 1em;
display: flex;
flex-direction: row;
& > .footnote-definition_content {
flex: 1;
width: calc(100% - 1em);
& > dd {
margin-inline-start: 1em;
}
& > dt {
color: ${palette('secondary')};
font-weight: 500;
}
}
& > .footnote-definition_anchor {
width: 1em;
}
}
`;
const table = css`
.tableWrapper {
overflow-x: auto;
margin: 0;
${manager.get(ThemeScrollbar, ['x'])}
width: 100%;
* {
margin: 0;
box-sizing: border-box;
font-size: 1em;
}
}
table {
border-collapse: collapse;
table-layout: fixed;
width: 100%;
overflow: auto;
border-radius: ${manager.get(ThemeSize, 'radius')};
p {
line-height: unset;
}
}
tr {
${manager.get(ThemeBorder, 'bottom')};
}
td,
th {
padding: 0 1em;
vertical-align: top;
box-sizing: border-box;
position: relative;
min-width: 100px;
${manager.get(ThemeBorder, undefined)};
text-align: left;
line-height: 3;
height: 3em;
}
th {
background: ${palette('background', 0.5)};
font-weight: 400;
}
.column-resize-handle {
position: absolute;
right: -2px;
top: 0;
bottom: 0;
z-index: 20;
pointer-events: none;
background: ${palette('secondary')};
width: ${manager.get(ThemeSize, 'lineWidth')};
}
.resize-cursor {
cursor: ew-resize;
cursor: col-resize;
}
.selectedCell {
&::after {
z-index: 2;
position: absolute;
content: '';
left: 0;
right: 0;
top: 0;
bottom: 0;
background: ${palette('secondary', 0.38)};
pointer-events: none;
}
& ::selection {
background: transparent;
}
}
`;
injectProsemirrorView(emotion);
// eslint-disable-next-line no-unused-expressions
injectGlobal`
.milkdown {
.material-icons-outlined {
font-size: 1.5em;
}
position: relative;
margin-left: auto;
margin-right: auto;
box-sizing: border-box;
color: ${neutral};
background: ${surface};
font-family: ${manager.get(ThemeFont, 'typography')};
${manager.get(ThemeShadow, undefined)}
${manager.get(ThemeScrollbar, undefined)}
${selection};
.editor {
${editorLayout};
${blockquote};
${hr};
${list};
${code};
${img};
${table};
${footnote};
${inline};
}
}
`;
};
export const createTheme = (isDarkMode: boolean) => (emotion: Emotion, manager: ThemeManager) => {
const { css } = emotion;
const colorSet = isDarkMode ? darkColor : lightColor;
manager.set(ThemeColor, (options) => {
if (!options) return;
const [key, opacity] = options;
const hex = colorSet[key];
const rgb = hex2rgb(hex);
if (!rgb) return;
return `rgba(${rgb?.join(', ')}, ${opacity || 1})`;
});
manager.set(ThemeSize, (key) => {
if (!key) return;
return size[key];
});
manager.set(ThemeFont, (key) => {
if (!key) return;
return font[key].join(', ');
});
manager.set(ThemeScrollbar, ([direction = 'y', type = 'normal'] = ['y', 'normal'] as never) => {
const main = manager.get(ThemeColor, ['secondary', 0.38]);
const bg = manager.get(ThemeColor, ['secondary', 0.12]);
const hover = manager.get(ThemeColor, ['secondary']);
return css`
scrollbar-width: thin;
scrollbar-color: ${main} ${bg};
-webkit-overflow-scrolling: touch;
&::-webkit-scrollbar {
${direction === 'y' ? 'width' : 'height'}: ${type === 'thin' ? 2 : 12}px;
background-color: transparent;
}
&::-webkit-scrollbar-track {
border-radius: 999px;
background: transparent;
border: 4px solid transparent;
}
&::-webkit-scrollbar-thumb {
border-radius: 999px;
background-color: ${main};
border: ${type === 'thin' ? 0 : 4}px solid transparent;
background-clip: content-box;
}
&::-webkit-scrollbar-thumb:hover {
background-color: ${hover};
}
`;
});
manager.set(ThemeShadow, () => {
const lineWidth = manager.get(ThemeSize, 'lineWidth');
const getShadow = (opacity: number) => manager.get(ThemeColor, ['shadow', opacity]);
return css`
box-shadow: 0 ${lineWidth} ${lineWidth} ${getShadow(0.14)}, 0 2px ${lineWidth} ${getShadow(0.12)},
0 ${lineWidth} 3px ${getShadow(0.2)};
`;
});
manager.set(ThemeBorder, (direction) => {
const lineWidth = manager.get(ThemeSize, 'lineWidth');
const line = manager.get(ThemeColor, ['line']);
if (!direction) {
return css`border: ${lineWidth} solid ${line};`;
}
return css`${`border-${direction}`}: ${lineWidth} solid ${line};`;
});
manager.set(ThemeIcon, (icon) => {
if (!icon) return;
return getIcon(icon);
});
manager.set(ThemeGlobal, () => {
getStyle(manager, emotion);
});
useAllPresetRenderer(manager, emotion);
};
export const getNord = (isDarkMode = false) => themeFactory((emotion, manager) => createTheme(isDarkMode)(emotion, manager));
export const nordDark = getNord(true);
export const nordLight = getNord(false);
const darkMode = Boolean(window.matchMedia?.('(prefers-color-scheme: dark)').matches);
export const nord = getNord(darkMode);
export { color, darkColor, lightColor } from '@milkdown/theme-nord/src/nord';
@@ -1,7 +1,7 @@
@import './var.inc.styl' @import './var.inc.styl'
.button .button
&:not(.codicon) &:not(.codicon):not(.milkdown-nutoqn)
background-color: lighten($default-color, 50%) background-color: lighten($default-color, 50%)
border: 1px solid $default-color border: 1px solid $default-color
transition: background-color .2s, box-shadow .5s transition: background-color .2s, box-shadow .5s
@@ -1,7 +1,7 @@
@import './var.inc.styl' @import './var.inc.styl'
.button .button
&:not(.codicon) &:not(.codicon):not(.milkdown-nutoqn)
font-size: rem($font-size-secondary) font-size: rem($font-size-secondary)
line-height: rem($form-control-height - 2) line-height: rem($form-control-height - 2)
height: rem($form-control-height) height: rem($form-control-height)
@@ -8,7 +8,7 @@
font-weight: normal font-weight: normal
font-style: normal font-style: normal
.icon .icon:not(.material-icons-outlined)
font-family: $icon-font-name, $primary-font-family !important font-family: $icon-font-name, $primary-font-family !important
speak: none speak: none
font-style: normal font-style: normal
+18 -1
View File
@@ -20,6 +20,23 @@
"@fontsource/source-code-pro": "^4.5.6", "@fontsource/source-code-pro": "^4.5.6",
"@fontsource/ubuntu-mono": "^4.5.6", "@fontsource/ubuntu-mono": "^4.5.6",
"@hydrooj/utils": "workspace:*", "@hydrooj/utils": "workspace:*",
"@milkdown/core": "^6.0.1",
"@milkdown/plugin-clipboard": "^6.0.1",
"@milkdown/plugin-cursor": "^6.0.1",
"@milkdown/plugin-diagram": "^6.0.1",
"@milkdown/plugin-emoji": "^6.0.1",
"@milkdown/plugin-history": "^6.0.1",
"@milkdown/plugin-listener": "^6.0.1",
"@milkdown/plugin-math": "^6.0.1",
"@milkdown/plugin-menu": "^6.0.1",
"@milkdown/plugin-prism": "^6.0.1",
"@milkdown/plugin-slash": "^6.0.1",
"@milkdown/plugin-tooltip": "^6.0.1",
"@milkdown/plugin-upload": "^6.0.1",
"@milkdown/preset-commonmark": "^6.0.1",
"@milkdown/preset-gfm": "^6.0.1",
"@milkdown/prose": "^6.0.1",
"@milkdown/theme-nord": "^6.0.1",
"@types/gulp-if": "^0.0.34", "@types/gulp-if": "^0.0.34",
"@types/jquery": "^3.5.14", "@types/jquery": "^3.5.14",
"@types/json-schema": "^7.0.11", "@types/json-schema": "^7.0.11",
@@ -57,6 +74,7 @@
"jquery-scroll-lock": "^3.1.3", "jquery-scroll-lock": "^3.1.3",
"jquery.easing": "^1.4.1", "jquery.easing": "^1.4.1",
"jquery.transit": "^0.9.12", "jquery.transit": "^0.9.12",
"material-icons": "^1.10.9",
"mini-css-extract-plugin": "^1.6.2", "mini-css-extract-plugin": "^1.6.2",
"moment": "^2.29.1", "moment": "^2.29.1",
"monaco-editor": "^0.31.1", "monaco-editor": "^0.31.1",
@@ -99,7 +117,6 @@
"through2": "^4.0.2", "through2": "^4.0.2",
"timeago-react": "^3.0.4", "timeago-react": "^3.0.4",
"timeago.js": "^4.0.2", "timeago.js": "^4.0.2",
"vditor": "^3.8.13",
"vinyl-buffer": "^1.0.1", "vinyl-buffer": "^1.0.1",
"web-streams-polyfill": "^3.2.0", "web-streams-polyfill": "^3.2.0",
"webpack": "^4.46.0", "webpack": "^4.46.0",
+1 -1
View File
@@ -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 -1
View File
@@ -1,10 +1,10 @@
import 'normalize.css/normalize.css'; import 'normalize.css/normalize.css';
import 'vditor/dist/index.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';
import 'pickadate/lib/themes/classic.time.css'; import 'pickadate/lib/themes/classic.time.css';
import 'katex/dist/katex.min.css'; import 'katex/dist/katex.min.css';
import '@blueprintjs/core/lib/css/blueprint.css'; import '@blueprintjs/core/lib/css/blueprint.css';
import 'material-icons/iconfont/material-icons.css';
import '@fontsource/fira-code'; import '@fontsource/fira-code';
import '@fontsource/source-code-pro'; import '@fontsource/source-code-pro';