You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
Hydro/packages/ui-default/components/monaco/index.ts

175 lines
6.7 KiB
TypeScript

import './monaco.styl';
import $ from 'jquery';
import { EditorAction, registerEditorAction } from 'monaco-editor/esm/vs/editor/browser/editorExtensions';
import * as monaco from 'monaco-editor/esm/vs/editor/editor.api';
import { IQuickInputService } from 'monaco-editor/esm/vs/platform/quickinput/common/quickInput';
import list from 'monaco-themes/themes/themelist.json';
import { nanoid } from 'nanoid';
import { i18n, request } from 'vj/utils';
export default monaco;
export const customOptions: monaco.editor.IStandaloneDiffEditorConstructionOptions = JSON.parse(localStorage.getItem('editor.config') || '{}');
export function saveCustomOptions() {
localStorage.setItem('editor.config', JSON.stringify(customOptions));
}
const loaded = {};
async function fetchTheme(id: string, label: string) {
if (loaded[id]) return;
const res = await fetch(`${UiContext.cdn_prefix}monaco/themes/${label}.json`);
const data = await res.json();
monaco.editor.defineTheme(id, data);
loaded[id] = true;
}
export const loadThemePromise = customOptions.theme
? fetchTheme(customOptions.theme, list[customOptions.theme])
: Promise.resolve();
// 这破烂 monaco 能不能给个 typings 啊
// 我翻源码真的很累的好吧
class ChangeThemeAction extends EditorAction {
constructor() {
super({
id: 'hydro.changeEditorTheme',
label: i18n('Change Theme'),
alias: 'Change Theme',
});
}
async run(accessor, editor: monaco.editor.IStandaloneCodeEditor) {
const items = Object.keys(list).map((key) => ({
label: list[key],
command: key,
}));
const quickInputService = accessor.get(IQuickInputService);
const oldTheme = (editor as any)._themeService._theme;
let focus = '';
const selected = await quickInputService.pick(items, {
canPickMany: false,
async onDidFocus(item) {
focus = item.command;
await fetchTheme(item.command, item.label);
if (focus === item.command) monaco.editor.setTheme(item.command);
},
});
if (!selected) {
const themeId = oldTheme.type ? `${oldTheme.base}-${oldTheme.type}` : oldTheme.id;
return monaco.editor.setTheme(themeId);
}
await fetchTheme(selected.command, selected.label);
customOptions.theme = selected.command;
saveCustomOptions();
return monaco.editor.setTheme(selected.command);
}
}
registerEditorAction(ChangeThemeAction);
const pagename = document.documentElement.getAttribute('data-page');
const isProblemPage = ['problem_create', 'problem_edit'].includes(pagename);
const isProblemEdit = pagename === 'problem_edit';
function handlePasteEvent(editor: monaco.editor.IStandaloneCodeEditor) {
window.addEventListener('paste', (ev: ClipboardEvent) => {
if (!editor.hasTextFocus()) return;
const selection = editor.getSelection();
const { items } = ev.clipboardData;
let wrapper = ['', ''];
let ext;
const matches = items[0].type.match(/^image\/(png|jpg|jpeg|gif)$/i);
if (matches) {
wrapper = ['![image](', ')'];
[, ext] = matches;
} else if (items[0].type === 'application/x-zip-compressed') {
wrapper = ['[file](', ')'];
ext = 'zip';
}
if (!ext) return;
const filename = `${nanoid()}.${ext}`;
const data = new FormData();
data.append('filename', filename);
data.append('file', items[0].getAsFile());
data.append('operation', 'upload_file');
if (isProblemEdit) data.append('type', 'additional_file');
let range: monaco.Range = null;
editor.executeEdits('', [{
range: new monaco.Range(selection.endLineNumber, selection.endColumn, selection.endLineNumber, selection.endColumn),
text: `![image](${i18n('Preparing Upload...')}) `,
}], (inverseOp) => { range = inverseOp[0].range; return null; });
editor.setPosition(editor.getSelection().getEndPosition());
function updateText(text: string) {
const pos = editor.getSelection();
const rangeBefore = range;
editor.executeEdits('', [{ range, text: `${wrapper[0]}${text}${wrapper[1]} ` }], (inverseOp) => {
range = inverseOp[0].range;
if (pos.endLineNumber !== pos.startLineNumber || pos.endLineNumber !== range.endLineNumber) return null;
const delta = rangeBefore.endColumn - range.endColumn;
editor.setPosition(new monaco.Position(pos.endLineNumber, pos.endColumn - delta));
return null;
});
}
let progress = 0;
request.postFile(isProblemEdit ? './files' : '/file', data, {
xhr() {
const xhr = new XMLHttpRequest();
xhr.upload.addEventListener('loadstart', () => updateText(i18n('Uploading...')));
xhr.upload.addEventListener('progress', (e) => {
if (!e.lengthComputable) return;
const percentComplete = Math.round((e.loaded / e.total) * 100);
if (percentComplete === progress) return;
progress = percentComplete;
updateText(`${i18n('Uploading...')} ${percentComplete}%`);
}, false);
return xhr;
},
})
.then(() => updateText(`${isProblemPage ? 'file://' : `/file/${UserContext._id}/`}${filename}`))
.catch((e) => {
console.error(e);
updateText(`${i18n('Upload Failed')}: ${e.message}`);
});
});
}
export function registerAction(
editor: monaco.editor.IStandaloneCodeEditor,
model: monaco.editor.IModel,
element?,
) {
if (element) {
editor.addAction({
id: 'hydro.submitForm',
label: 'Submit',
keybindings: [
monaco.KeyMod.CtrlCmd | monaco.KeyCode.Enter,
monaco.KeyMod.WinCtrl | monaco.KeyCode.Enter,
],
run: () => {
const form = $(element).closest('form');
if (form.find('[data-default-submit]').length) form.find('[data-default-submit]').click();
else form.submit();
},
});
}
editor.addCommand(monaco.KeyMod.CtrlCmd | monaco.KeyMod.Shift | monaco.KeyCode.KeyP, () => {
editor.getAction('editor.action.quickCommand').run();
});
editor.addCommand(monaco.KeyMod.Alt | monaco.KeyMod.Shift | monaco.KeyCode.KeyF, () => {
const action = editor.getAction('editor.action.format') || editor.getAction('editor.action.formatDocument');
if (action) action.run();
});
editor.onDidChangeConfiguration(() => {
const current = editor.getOption(monaco.editor.EditorOptions.fontSize.id);
customOptions.fontSize = current;
saveCustomOptions();
});
if (model.getLanguageId() === 'markdown') {
const suggestWidget = (editor.getContribution('editor.contrib.suggestController') as any).widget?.value;
if (suggestWidget?._setDetailsVisible) suggestWidget._setDetailsVisible(true);
handlePasteEvent(editor);
}
if (!customOptions.theme) return null;
return loadThemePromise.then(() => editor.updateOptions({ theme: customOptions.theme }));
}