refac: sources and citations
This commit is contained in:
+1
-25
@@ -32,7 +32,7 @@ function escapeRegExp(string: string): string {
|
||||
return string.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
||||
}
|
||||
|
||||
export const replaceTokens = (content, sourceIds, char, user) => {
|
||||
export const replaceTokens = (content, char, user) => {
|
||||
const tokens = [
|
||||
{ regex: /{{char}}/gi, replacement: char },
|
||||
{ regex: /{{user}}/gi, replacement: user },
|
||||
@@ -67,30 +67,6 @@ export const replaceTokens = (content, sourceIds, char, user) => {
|
||||
}
|
||||
});
|
||||
|
||||
if (Array.isArray(sourceIds)) {
|
||||
// Match both [1], [2], and [1,2,3] forms
|
||||
const multiRefRegex = /\[([\d,\s]+)\]/g;
|
||||
segment = segment.replace(multiRefRegex, (match, group) => {
|
||||
// Extract numbers like 1,2,3
|
||||
const indices = group
|
||||
.split(',')
|
||||
.map((n) => parseInt(n.trim(), 10))
|
||||
.filter((n) => !isNaN(n));
|
||||
|
||||
// Replace each index with a <source_id> tag
|
||||
const sources = indices
|
||||
.map((idx) => {
|
||||
const sourceId = sourceIds[idx - 1];
|
||||
return sourceId
|
||||
? `<source_id data="${idx}" title="${encodeURIComponent(sourceId)}" />`
|
||||
: `[${idx}]`;
|
||||
})
|
||||
.join('');
|
||||
|
||||
return sources;
|
||||
});
|
||||
}
|
||||
|
||||
return segment;
|
||||
});
|
||||
|
||||
|
||||
@@ -0,0 +1,55 @@
|
||||
export function citationExtension() {
|
||||
return {
|
||||
name: 'citation',
|
||||
level: 'inline' as const,
|
||||
|
||||
start(src: string) {
|
||||
// Trigger on any [number]
|
||||
return src.search(/\[(\d[\d,\s]*)\]/);
|
||||
},
|
||||
|
||||
tokenizer(src: string) {
|
||||
// Avoid matching footnotes
|
||||
if (/^\[\^/.test(src)) return;
|
||||
|
||||
// Match ONE OR MORE adjacent [1] or [1,2] blocks
|
||||
// Example matched: "[1][2,3][4]"
|
||||
const rule = /^(\[(?:\d[\d,\s]*)\])+/;
|
||||
const match = rule.exec(src);
|
||||
if (!match) return;
|
||||
|
||||
const raw = match[0];
|
||||
|
||||
// Extract ALL bracket groups inside the big match
|
||||
const groupRegex = /\[([\d,\s]+)\]/g;
|
||||
const ids: number[] = [];
|
||||
let m: RegExpExecArray | null;
|
||||
|
||||
while ((m = groupRegex.exec(raw))) {
|
||||
const parsed = m[1]
|
||||
.split(',')
|
||||
.map((n) => parseInt(n.trim(), 10))
|
||||
.filter((n) => !isNaN(n));
|
||||
|
||||
ids.push(...parsed);
|
||||
}
|
||||
|
||||
return {
|
||||
type: 'citation',
|
||||
raw,
|
||||
ids // merged list
|
||||
};
|
||||
},
|
||||
|
||||
renderer(token: any) {
|
||||
// e.g. "1,2,3"
|
||||
return token.ids.join(',');
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
export default function () {
|
||||
return {
|
||||
extensions: [citationExtension()]
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
// footnote-extension.ts
|
||||
// Simple extension for marked to support footnote references like [^1], [^note]
|
||||
|
||||
function escapeHtml(s: string) {
|
||||
return s.replace(
|
||||
/[&<>"']/g,
|
||||
(c) => ({ '&': '&', '<': '<', '>': '>', '"': '"', "'": ''' })[c]!
|
||||
);
|
||||
}
|
||||
|
||||
export function footnoteExtension() {
|
||||
return {
|
||||
name: 'footnote',
|
||||
level: 'inline' as const,
|
||||
start(src: string) {
|
||||
return src.search(/\[\^\s*[a-zA-Z0-9_-]+\s*\]/);
|
||||
},
|
||||
tokenizer(src: string) {
|
||||
const rule = /^\[\^\s*([a-zA-Z0-9_-]+)\s*\]/;
|
||||
const match = rule.exec(src);
|
||||
if (match) {
|
||||
const escapedText = escapeHtml(match[1]);
|
||||
return {
|
||||
type: 'footnote',
|
||||
raw: match[0],
|
||||
text: match[1],
|
||||
escapedText: escapedText
|
||||
};
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
export default function () {
|
||||
return {
|
||||
extensions: [footnoteExtension()]
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user