文件
mynode/packages/editor/CodeMirror/utils/findPositionMatchingLink.ts
chen 301194963b
一些检查失败了
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
feat: Initial clean release of CHM
2026-11-04 11:29:39 +08:00

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;