UNPKG

@bhsd/codemirror-mediawiki

Version:

Modified CodeMirror mode based on wikimedia/mediawiki-extensions-CodeMirror

108 lines (107 loc) 3.92 kB
import { keymap } from '@codemirror/view'; import { EditorSelection } from '@codemirror/state'; import { indentMore, indentLess } from '@codemirror/commands'; import { getLSP } from '@bhsd/browser'; import { CodeMirror6, menuRegistry } from './codemirror'; const entity = { '"': 'quot', "'": 'apos', '<': 'lt', '>': 'gt', '&': 'amp', ' ': 'nbsp' }; /** * 根据函数转换选中文本 * @param func 转换函数 * @param cmd 原命令 */ const convert = (func, cmd) => (view) => { if (view.state.selection.ranges.some(({ empty }) => !empty)) { CodeMirror6.replaceSelections(view, func); return true; } return cmd(view); }; // eslint-disable-next-line @typescript-eslint/no-misused-spread export const escapeHTML = (str) => [...str].map(c => { if (c in entity) { return `&${entity[c]};`; } const code = c.codePointAt(0); return code < 256 ? `&#${code};` : `&#x${code.toString(16)};`; }).join(''), escapeURI = (str) => { if (str.includes('%')) { try { return decodeURIComponent(str); } catch { } } return encodeURIComponent(str); }; const escapeWiki = (cm) => { const view = cm.view, { state } = view, { ranges } = state.selection, lsp = getLSP(view, false, cm.getWikiConfig); if (lsp && 'provideRefactoringAction' in lsp && ranges.some(({ empty }) => !empty)) { (async () => { const replacements = new WeakMap(); for (const range of ranges) { // eslint-disable-next-line no-await-in-loop const [action] = await lsp.provideRefactoringAction(state.sliceDoc(range.from, range.to)); replacements.set(range, action?.edit.changes[''][0].newText); } view.dispatch(state.changeByRange(range => { const insert = replacements.get(range); if (insert === undefined) { return { range }; } return { range: EditorSelection.range(range.from, range.from + insert.length), changes: { from: range.from, to: range.to, insert }, }; })); })(); return true; } return false; }; const handlerBase = (view, e) => { e.stopPropagation(); view.focus(); }; let items; menuRegistry.push({ name: 'escape', isActionable({ lang, view }) { return lang === 'mediawiki' && view.state.selection.ranges.some(({ empty }) => !empty); }, getItems(cm) { if (!items) { const view = cm.view, btnHTML = document.createElement('div'), btnURI = document.createElement('div'); btnHTML.textContent = 'HTML escape'; btnHTML.addEventListener('click', e => { CodeMirror6.replaceSelections(view, escapeHTML); handlerBase(view, e); }); btnURI.textContent = 'URI encode/decode'; btnURI.addEventListener('click', e => { CodeMirror6.replaceSelections(view, escapeURI); handlerBase(view, e); }); items = [btnHTML, btnURI]; const lsp = getLSP(view, false, cm.getWikiConfig); if (lsp && 'provideRefactoringAction' in lsp) { const btnWiki = document.createElement('div'); btnWiki.textContent = 'Escape with magic words'; btnWiki.addEventListener('click', e => { escapeWiki(cm); handlerBase(view, e); }); items.unshift(btnWiki); } } return items; }, }); export default (cm) => keymap.of([ { key: 'Mod-[', run: convert(escapeHTML, indentLess) }, { key: 'Mod-]', run: convert(escapeURI, indentMore) }, { key: 'Mod-\\', run() { return escapeWiki(cm); }, }, ]);