File size: 1,840 Bytes
cfb0fa4 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 | export function citationExtension() {
return {
name: 'citation',
level: 'inline' as const,
start(src: string) {
// Trigger on any [number] or [number#suffix]
// We check for a digit immediately after [ to avoid matching arbitrary links
return src.search(/\[\d/);
},
tokenizer(src: string) {
// Avoid matching footnotes
if (/^\[\^/.test(src)) return;
// Match ONE OR MORE adjacent [1], [1,2], or [1#foo] blocks
// Example matched: "[1][2,3][4#bar]"
// We allow: digits, commas, spaces, and # followed by non-control chars (excluding ] and ,)
const rule = /^(\[(?:\d+(?:#[^,\]\s]+)?(?:,\s*\d+(?:#[^,\]\s]+)?)*)\])+/;
const match = rule.exec(src);
if (!match) return;
const raw = match[0];
// Extract ALL bracket groups inside the big match
const groupRegex = /\[([^\]]+)\]/g;
const ids: number[] = [];
const citationIdentifiers: string[] = [];
let m: RegExpExecArray | null;
while ((m = groupRegex.exec(raw))) {
// m[1] is the content inside brackets, e.g. "1, 2#foo"
const parts = m[1].split(',').map((p) => p.trim());
parts.forEach((part) => {
// Check if it starts with digit
const match = /^(\d+)(?:#(.+))?$/.exec(part);
if (match) {
const index = parseInt(match[1], 10);
if (!isNaN(index)) {
ids.push(index);
// Store the full identifier ("1#foo" or "1")
citationIdentifiers.push(part);
}
}
});
}
if (ids.length === 0) return;
return {
type: 'citation',
raw,
ids, // merged list of integers for legacy title lookup
citationIdentifiers // merged list of full identifiers for granular targeting
};
},
renderer(token: any) {
// fallback text
return token.raw;
}
};
}
export default function () {
return {
extensions: [citationExtension()]
};
}
|