mirror of
https://gitcode.com/gh_mirrors/hy/Hydro.git
synced 2026-10-20 01:38:19 +08:00
ui: milkdown
This commit is contained in:
1 parent
4f4e6f9377
commit
8e95482246
11 files changed
+542
-155
No files matched your search
@@ -1,12 +1,7 @@
|
||||
.vditor
|
||||
margin-bottom: rem(10px)
|
||||
|
||||
.vditor-preview__action
|
||||
display: none
|
||||
|
||||
.vditor ol
|
||||
list-style: decimal
|
||||
|
||||
.decoration-hide
|
||||
color: grey !important
|
||||
background-color: grey !important
|
||||
|
||||
.code-fence_selector
|
||||
height: 2.2em !important
|
||||
width: 10em !important
|
||||
@@ -1,32 +1,11 @@
|
||||
import _ from 'lodash';
|
||||
import Notification from 'vj/components/notification';
|
||||
import DOMAttachedObject from 'vj/components/DOMAttachedObject';
|
||||
import type { Node } from 'prosemirror-model';
|
||||
import { nanoid } from 'nanoid';
|
||||
import i18n from 'vj/utils/i18n';
|
||||
import request from 'vj/utils/request';
|
||||
|
||||
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,
|
||||
},
|
||||
},
|
||||
};
|
||||
import tpl from 'vj/utils/tpl';
|
||||
|
||||
interface MonacoOptions {
|
||||
language?: string;
|
||||
@@ -38,26 +17,35 @@ interface MonacoOptions {
|
||||
value?: string;
|
||||
hide?: string[];
|
||||
}
|
||||
interface VditorOptions {
|
||||
theme?: 'classic' | 'dark'
|
||||
}
|
||||
type Options = MonacoOptions & VditorOptions;
|
||||
type Options = MonacoOptions;
|
||||
|
||||
export default class Editor extends DOMAttachedObject {
|
||||
export default class VjEditor extends DOMAttachedObject {
|
||||
static DOMAttachKey = 'vjEditorInstance';
|
||||
isValid: boolean;
|
||||
private lock: boolean;
|
||||
model: import('../monaco').default.editor.IModel;
|
||||
editor: import('../monaco').default.editor.IStandaloneCodeEditor;
|
||||
vditor: import('vditor').default;
|
||||
isValid: boolean;
|
||||
milkdown: import('@milkdown/core').Editor & {
|
||||
setValue?: (val: string) => void;
|
||||
getValue?: () => string;
|
||||
dispose?: () => void;
|
||||
};
|
||||
|
||||
height = 0;
|
||||
|
||||
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();
|
||||
$dom.hide();
|
||||
const root = $('<div class="row editor-root" style="width: 100%;"></div>');
|
||||
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 {
|
||||
onChange, language = 'markdown',
|
||||
@@ -67,16 +55,9 @@ export default class Editor extends DOMAttachedObject {
|
||||
hide = [],
|
||||
} = this.options;
|
||||
const { monaco, registerAction } = await load([language]);
|
||||
const { $dom } = this;
|
||||
const hasFocus = $dom.is(':focus') || $dom.hasClass('autofocus');
|
||||
const origin = $dom.get(0);
|
||||
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
|
||||
const hasFocus = this.$dom.is(':focus') || this.$dom.hasClass('autofocus');
|
||||
if (!autoResize && this.$dom.height()) $(ele).height(this.$dom.height());
|
||||
const value = this.options.value || this.$dom.val();
|
||||
this.model = typeof model === 'string'
|
||||
? monaco.editor.getModel(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;
|
||||
const lineHeight = this.editor.getOption(monaco.editor.EditorOption.lineHeight);
|
||||
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 (window.innerHeight * 1.5 < height) {
|
||||
height = window.innerHeight;
|
||||
@@ -147,9 +128,13 @@ export default class Editor extends DOMAttachedObject {
|
||||
}
|
||||
this.editor.onDidChangeModelContent(() => {
|
||||
const val = this.editor.getValue({ lineEnding: '\n', preserveBOM: false });
|
||||
$dom.val(val);
|
||||
$dom.text(val);
|
||||
if (onChange) onChange(val);
|
||||
this.$dom.val(val);
|
||||
this.$dom.text(val);
|
||||
if (this.lock) return;
|
||||
this.lock = true;
|
||||
this.milkdown?.setValue(val);
|
||||
onChange?.(val);
|
||||
this.lock = false;
|
||||
});
|
||||
this.isValid = true;
|
||||
if (hasFocus) this.focus();
|
||||
@@ -160,97 +145,111 @@ export default class Editor extends DOMAttachedObject {
|
||||
window.editor = this.editor;
|
||||
}
|
||||
|
||||
async initVditor() {
|
||||
async initMilkdown(ele) {
|
||||
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 { $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) {
|
||||
$dom.val(v);
|
||||
$dom.text(v);
|
||||
if (onChange) onChange(v);
|
||||
},
|
||||
value,
|
||||
cache: { id: Math.random().toString() },
|
||||
const [
|
||||
{
|
||||
Editor, rootCtx, defaultValueCtx, editorViewCtx, parserCtx, serializerCtx,
|
||||
},
|
||||
{ nord }, { Slice }, { gfm }, { listener, listenerCtx },
|
||||
{ math }, { history }, { clipboard }, { tooltip }, { slash },
|
||||
{ emoji }, { upload, uploadPlugin }, { diagram }, { prism }, { cursor },
|
||||
{ menu },
|
||||
] = await Promise.all([
|
||||
import('@milkdown/core'), import('./theme'), import('@milkdown/prose'),
|
||||
import('@milkdown/preset-gfm'), import('@milkdown/plugin-listener'),
|
||||
import('@milkdown/plugin-math'),
|
||||
import('@milkdown/plugin-history'),
|
||||
import('@milkdown/plugin-clipboard'),
|
||||
import('@milkdown/plugin-tooltip'),
|
||||
import('@milkdown/plugin-slash'),
|
||||
import('@milkdown/plugin-emoji'),
|
||||
import('@milkdown/plugin-upload'),
|
||||
import('@milkdown/plugin-diagram'),
|
||||
import('@milkdown/plugin-prism'),
|
||||
import('@milkdown/plugin-cursor'),
|
||||
import('@milkdown/plugin-menu'),
|
||||
]);
|
||||
|
||||
const uploader: import('@milkdown/plugin-upload').Uploader = async (files, schema) => {
|
||||
const images: File[] = [];
|
||||
for (let i = 0; i < files.length; i++) {
|
||||
const file = files.item(i);
|
||||
if (!file) continue;
|
||||
if (!file.type.includes('image')) continue;
|
||||
images.push(file);
|
||||
}
|
||||
const nodes: Node[] = await Promise.all(
|
||||
images.map(async (image) => {
|
||||
console.log(image);
|
||||
// TODO
|
||||
return null;
|
||||
const src = await YourUploadAPI(image);
|
||||
const alt = image.name;
|
||||
return schema.nodes.image.createAndFill({
|
||||
src,
|
||||
alt,
|
||||
}) as Node;
|
||||
}),
|
||||
);
|
||||
return nodes;
|
||||
};
|
||||
|
||||
this.milkdown = Editor.make()
|
||||
.use(nord).use(listener).use(gfm)
|
||||
.use(math).use(history).use(clipboard).use(tooltip).use(slash)
|
||||
.use(emoji).use(diagram).use(prism).use(cursor).use(menu)
|
||||
.use(upload.configure(uploadPlugin, { uploader }))
|
||||
.config((ctx) => {
|
||||
ctx.set(defaultValueCtx, this.$dom.val());
|
||||
ctx.set(rootCtx, ele);
|
||||
ctx.get(listenerCtx).markdownUpdated((c, markdown) => {
|
||||
this.$dom.val(markdown);
|
||||
this.$dom.text(markdown);
|
||||
if (this.lock) return;
|
||||
this.lock = true;
|
||||
if (this.model) {
|
||||
const range = this.model.getFullModelRange();
|
||||
this.model.pushEditOperations([], [{ range, text: markdown }], undefined);
|
||||
} else this.editor?.setValue?.(markdown);
|
||||
this.options?.onChange?.(markdown);
|
||||
this.lock = false;
|
||||
});
|
||||
ctx.get(listenerCtx).updated((c) => {
|
||||
this.height = $(ele).height();
|
||||
});
|
||||
});
|
||||
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');
|
||||
$dom.hide();
|
||||
origin.parentElement.appendChild(ele);
|
||||
this.milkdown.dispose = () => {
|
||||
const view = this.milkdown.action((ctx) => ctx.get(editorViewCtx));
|
||||
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;
|
||||
if (hasFocus) this.focus();
|
||||
}
|
||||
|
||||
destory() {
|
||||
this.detach();
|
||||
if (this.vditor?.destroy) this.vditor.destroy();
|
||||
else if (this.editor?.dispose) this.editor.dispose();
|
||||
this.editor?.dispose();
|
||||
this.milkdown?.dispose();
|
||||
}
|
||||
|
||||
ensureValid() {
|
||||
@@ -259,8 +258,12 @@ 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') {
|
||||
this.editor?.setValue(val);
|
||||
this.milkdown?.setValue(val);
|
||||
return val;
|
||||
}
|
||||
return (this.editor || this.milkdown)?.getValue({ lineEnding: '\n', preserveBOM: false });
|
||||
}
|
||||
|
||||
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'
|
||||
|
||||
.button
|
||||
&:not(.codicon)
|
||||
&:not(.codicon):not(.milkdown-nutoqn)
|
||||
background-color: lighten($default-color, 50%)
|
||||
border: 1px solid $default-color
|
||||
transition: background-color .2s, box-shadow .5s
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
@import './var.inc.styl'
|
||||
|
||||
.button
|
||||
&:not(.codicon)
|
||||
&:not(.codicon):not(.milkdown-nutoqn)
|
||||
font-size: rem($font-size-secondary)
|
||||
line-height: rem($form-control-height - 2)
|
||||
height: rem($form-control-height)
|
||||
|
||||
Reference in new issue
Block a user