一些检查失败了
automerge / automerge (push) Has been cancelled
react-native-android-build-apk / AssembleRelease (push) Has been cancelled
Build macOS M1 / Main (push) Has been cancelled
Joplin Continuous Integration / Main (macos-15-intel) (push) Has been cancelled
Joplin Continuous Integration / Main (ubuntu-22.04) (push) Has been cancelled
Joplin Continuous Integration / Main (ubuntu-22.04-arm) (push) Has been cancelled
Joplin Continuous Integration / Main (windows-11-vs2026-arm) (push) Has been cancelled
Joplin Continuous Integration / Main (windows-2025) (push) Has been cancelled
Joplin Continuous Integration / ServerDockerImage (ubuntu-22.04) (push) Has been cancelled
Joplin Continuous Integration / ServerDockerImage (ubuntu-22.04-arm) (push) Has been cancelled
Joplin UI tests / Main (ubuntu-22.04) (push) Has been cancelled
Joplin UI tests / Main (windows-2025) (push) Has been cancelled
Joplin UI tests / Mobile (push) Has been cancelled
Close stale issues / ProcessStaleIssues (push) Has been cancelled
85 行
2.8 KiB
TypeScript
85 行
2.8 KiB
TypeScript
import { EditorState } from '@codemirror/state';
|
|
import uslug from '@joplin/fork-uslug/lib/uslug';
|
|
import { SyntaxNodeRef } from '@lezer/common';
|
|
import htmlNodeInfo from './htmlNodeInfo';
|
|
import { ensureSyntaxTree, syntaxTree } from '@codemirror/language';
|
|
|
|
// Searches the given `state` for a line that matches the target link.
|
|
const findPositionMatchingLink = (link: string, state: EditorState): number|null => {
|
|
const isAnchorLink = link.startsWith('#');
|
|
const isFootnote = link.startsWith('[^') && link.endsWith(']');
|
|
|
|
if (!isAnchorLink && !isFootnote) return null;
|
|
|
|
if (isFootnote) {
|
|
return findPositionMatchingFootnote(link, state);
|
|
} else if (isAnchorLink) {
|
|
return findPositionMatchingHash(link.substring(1), state);
|
|
}
|
|
|
|
return null;
|
|
};
|
|
|
|
const findPositionMatchingFootnote = (footnoteMarker: string, state: EditorState) => {
|
|
let iterator = state.doc.iterLines();
|
|
let lineNumber = 0;
|
|
while (!iterator.done && lineNumber <= state.doc.lines) {
|
|
lineNumber ++;
|
|
iterator = iterator.next();
|
|
const line = iterator.value;
|
|
|
|
if (line.trim().startsWith(`${footnoteMarker}:`)) {
|
|
return state.doc.line(lineNumber).to;
|
|
}
|
|
}
|
|
return null;
|
|
};
|
|
|
|
const findPositionMatchingHash = (hash: string, state: EditorState) => {
|
|
let targetLocation: number|null = null;
|
|
|
|
const makeEnterNode = (offset: number) => (node: SyntaxNodeRef) => {
|
|
const nodeToText = (node: SyntaxNodeRef) => {
|
|
return state.sliceDoc(node.from + offset, node.to + offset);
|
|
};
|
|
|
|
const found = targetLocation !== null;
|
|
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.
|
|
const timeout = 1_000; // Maximum time to spend parsing the syntax tree
|
|
const tree = ensureSyntaxTree(state, state.doc.length, timeout) ?? syntaxTree(state);
|
|
tree.iterate({
|
|
enter: makeEnterNode(0),
|
|
});
|
|
|
|
return targetLocation;
|
|
};
|
|
|
|
export default findPositionMatchingLink;
|