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()]
	};
}