mirror of
https://github.com/laurent22/joplin.git
synced 2026-06-06 20:10:22 +00:00
67 lines
2.3 KiB
TypeScript
67 lines
2.3 KiB
TypeScript
import { ensureSyntaxTree } from '@codemirror/language';
|
|
import { EditorSelection } from '@codemirror/state';
|
|
import { EditorView } from '@codemirror/view';
|
|
import uslug from '@joplin/fork-uslug/lib/uslug';
|
|
import { SyntaxNodeRef } from '@lezer/common';
|
|
import htmlNodeInfo from '../utils/htmlNodeInfo';
|
|
|
|
const jumpToHash = (view: EditorView, hash: string) => {
|
|
const state = view.state;
|
|
const timeout = 1_000; // Maximum time to spend parsing the syntax tree
|
|
let targetLocation: number|undefined = undefined;
|
|
|
|
const makeEnterNode = (offset: number) => (node: SyntaxNodeRef) => {
|
|
const nodeToText = (node: SyntaxNodeRef) => {
|
|
return state.sliceDoc(node.from + offset, node.to + offset);
|
|
};
|
|
|
|
const found = targetLocation !== undefined;
|
|
if (found) return false; // Skip this node
|
|
|
|
let matches = false;
|
|
if (node.name.startsWith('SetextHeading') || node.name.startsWith('ATXHeading')) {
|
|
const nodeText = nodeToText(node)
|
|
.replace(/^#+\s/, '') // Leading #s in headers
|
|
.replace(/\n-+$/, ''); // Trailing --s in headers
|
|
matches = hash === uslug(nodeText);
|
|
} else if (node.name === 'HTMLBlock') {
|
|
// CodeMirror adds HTML information to Markdown documents using overlays attached
|
|
// to HTMLTag and HTMLBlock nodes.
|
|
// Use .enter to enter the overlay and visit the HTML nodes:
|
|
node.node.enter(node.from, 1).toTree().iterate({ enter: makeEnterNode(node.from) });
|
|
} else if (node.name === 'OpenTag' || node.name === 'HTMLTag') {
|
|
const htmlNodeDetails = htmlNodeInfo(node, state);
|
|
matches = htmlNodeDetails.getAttr('id') === hash || htmlNodeDetails.getAttr('name') === hash;
|
|
}
|
|
|
|
if (matches) {
|
|
targetLocation = node.to + offset;
|
|
return false;
|
|
}
|
|
|
|
const keepIterating = !matches;
|
|
return keepIterating;
|
|
};
|
|
|
|
// Iterate over the entire syntax tree.
|
|
ensureSyntaxTree(state, state.doc.length, timeout).iterate({
|
|
enter: makeEnterNode(0),
|
|
});
|
|
|
|
if (targetLocation !== undefined) {
|
|
view.dispatch({
|
|
selection: EditorSelection.cursor(targetLocation),
|
|
effects: [
|
|
// Scrolls the target header/anchor to the top of the editor --
|
|
// users are usually interested in the content just below a header
|
|
// when clicking on a header link.
|
|
EditorView.scrollIntoView(targetLocation, { y: 'start' }),
|
|
],
|
|
});
|
|
return true;
|
|
}
|
|
return false;
|
|
};
|
|
|
|
export default jumpToHash;
|