@bhsd/codemirror-mediawiki
Version:
Modified CodeMirror mode based on wikimedia/mediawiki-extensions-CodeMirror
109 lines (108 loc) • 4.01 kB
JavaScript
import { EditorView } from '@codemirror/view';
import { ensureSyntaxTree } from '@codemirror/language';
import { tokens } from './config';
import { hasTag } from './mediawiki';
const { vendor, userAgent, maxTouchPoints, platform } = navigator;
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
export const isMac = vendor?.includes('Apple Computer')
&& (userAgent.includes('Mobile/') || maxTouchPoints > 2)
|| platform.includes('Mac');
const modKey = isMac ? 'metaKey' : 'ctrlKey', key = isMac ? 'Meta' : 'Control', tags = ['extLinkProtocol', 'extLink', 'freeExtLinkProtocol', 'freeExtLink', 'magicLink', 'pageName'], links = ['extlink-protocol', 'extlink', 'free-extlink-protocol', 'free-extlink', 'magic-link'], wikiLinks = [
'template-name',
'link-pagename',
'parserfunction.cm-mw-pagename',
'exttag-attribute-value.cm-mw-pagename',
'file-text.cm-mw-pagename',
];
const toggleOpenLinks = (toggle) => {
for (const ele of document.querySelectorAll('.cm-content')) {
if (toggle) {
ele.style.setProperty('--codemirror-cursor', 'pointer');
}
else {
ele.style.removeProperty('--codemirror-cursor');
}
}
};
/* eslint-disable @typescript-eslint/no-unnecessary-condition */
globalThis.document?.addEventListener('keydown', e => {
if (e.key === key) {
toggleOpenLinks(true);
}
});
globalThis.document?.addEventListener('keyup', e => {
if (e.key === key) {
toggleOpenLinks();
}
});
globalThis.document?.addEventListener('visibilitychange', () => {
if (document.hidden) {
toggleOpenLinks();
}
});
/* eslint-enable @typescript-eslint/no-unnecessary-condition */
const wrapURL = (url) => url.startsWith('//') ? location.protocol + url : url;
export const mouseEventListener = (e, view, langConfig) => {
if (!e[modKey]
|| !(e.target instanceof Element && getComputedStyle(e.target).textDecorationLine === 'underline')) {
return undefined;
}
const position = view.posAtCoords(e);
if (!position) {
return undefined;
}
const { state } = view, tree = ensureSyntaxTree(state, position);
if (!tree) {
return undefined;
}
let node = tree.resolve(position, -1);
if (node.name.includes(tokens.linkToSection)) {
node = node.prevSibling;
}
else if (!hasTag(new Set(node.name.split('_')), tags)) {
node = tree.resolve(position, 1);
}
const { name, from, to } = node;
if (name.includes(tokens.pageName) && typeof langConfig?.titleParser === 'function') {
return langConfig.titleParser(state, node);
}
else if (name.includes('-extlink-protocol')) {
return wrapURL(state.sliceDoc(from, node.nextSibling.to));
}
else if (/-extlink(?:_|$)/u.test(name)) {
return wrapURL(state.sliceDoc(node.prevSibling.from, to));
}
else if (name.includes(tokens.magicLink)) {
const link = state.sliceDoc(from, to);
if (link.startsWith('RFC')) {
return `https://datatracker.ietf.org/doc/html/rfc${link.slice(3).trim()}`;
}
else if (link.startsWith('PMID')) {
return `https://pubmed.ncbi.nlm.nih.gov/${link.slice(4).trim()}`;
}
else if (typeof langConfig?.isbnParser === 'function') {
return langConfig.isbnParser(link);
}
}
return undefined;
};
export default ({ langConfig }) => [
EditorView.domEventHandlers({
mousedown(e, view) {
if (e.button !== 0) {
return undefined;
}
const url = mouseEventListener(e, view, langConfig);
if (url) {
open(url, '_blank');
return true;
}
return undefined;
},
}),
EditorView.theme({
[[...links, ...langConfig?.titleParser ? wikiLinks : []].map(type => `.cm-mw-${type}`).join()]: {
cursor: 'var(--codemirror-cursor)',
},
}),
];