mirror of
https://github.com/gapmiss/substack-clipper.git
synced 2026-07-22 07:48:24 +00:00
fix: convert Substack footnotes to Obsidian [^N] syntax via Turndown rules
This commit is contained in:
parent
83402ad77d
commit
8dacb96863
3 changed files with 35 additions and 13 deletions
|
|
@ -95,6 +95,39 @@ export function createConverter(downloadedUrls: Set<string>): TurndownService {
|
|||
},
|
||||
});
|
||||
|
||||
turndown.addRule('footnoteRef', {
|
||||
filter: (node) => {
|
||||
if (node.nodeName !== 'A') return false;
|
||||
return /^#footnote-\d+$/.test(node.getAttribute('href') ?? '');
|
||||
},
|
||||
replacement: (content) => `[^${content.trim()}]`,
|
||||
});
|
||||
|
||||
turndown.addRule('footnoteBackLink', {
|
||||
filter: (node) => {
|
||||
if (node.nodeName !== 'A') return false;
|
||||
return /^#footnote-anchor-\d+$/.test(node.getAttribute('href') ?? '');
|
||||
},
|
||||
replacement: () => '',
|
||||
});
|
||||
|
||||
turndown.addRule('footnoteDef', {
|
||||
filter: (node) => {
|
||||
if (node.nodeName !== 'DIV') return false;
|
||||
const backLink = node.querySelector('a[href^="#footnote-anchor-"]');
|
||||
return backLink !== null && backLink.parentElement === node;
|
||||
},
|
||||
replacement: (content, node) => {
|
||||
const anchor = node.querySelector('a[href^="#footnote-anchor-"]');
|
||||
const num = (anchor?.textContent ?? '').trim();
|
||||
const text = content.trim();
|
||||
const indented = text.split('\n').map((line, i) =>
|
||||
i === 0 || line === '' ? line : ' ' + line
|
||||
).join('\n');
|
||||
return `\n\n[^${num}]: ${indented}\n\n`;
|
||||
},
|
||||
});
|
||||
|
||||
return turndown;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -98,7 +98,7 @@ export default class SubstackClipperPlugin extends Plugin {
|
|||
}
|
||||
}
|
||||
|
||||
let processedMd = postprocessMarkdown(markdown, domain, downloadResult.downloaded);
|
||||
let processedMd = postprocessMarkdown(markdown, downloadResult.downloaded);
|
||||
|
||||
// Build podcast/video/transcript sections
|
||||
let podcastVideoPlayer = '';
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
export function postprocessMarkdown(md: string, domain: string, downloadedUrls: Set<string>): string {
|
||||
export function postprocessMarkdown(md: string, downloadedUrls: Set<string>): string {
|
||||
let out = md;
|
||||
|
||||
// iframe spacing
|
||||
|
|
@ -11,17 +11,6 @@ export function postprocessMarkdown(md: string, domain: string, downloadedUrls:
|
|||
// fix headings glued to links
|
||||
out = out.replace(/(\))(#{1,6})/g, '$1\n\n$2');
|
||||
|
||||
// footnotes
|
||||
const escapedDomain = domain.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
||||
out = out.replace(
|
||||
new RegExp(`\\[(\\d)\\]\\(${escapedDomain}/p/([a-zA-Z0-9-]+)#footnote-\\d-\\d+\\)`, 'g'),
|
||||
'[^$1]',
|
||||
);
|
||||
out = out.replace(
|
||||
new RegExp(`\\[(\\d)\\]\\(${escapedDomain}/p/([a-zA-Z0-9-]+)#footnote-anchor-\\d-\\d+\\)`, 'g'),
|
||||
'[^$1]: ',
|
||||
);
|
||||
|
||||
// truncated post previews
|
||||
out = out.replace(
|
||||
/\[(.*)\n(\n)?-+\]\([a-z0-9-:/.]+\)(.*)(\n\n)?\[Read full story\]\(([a-z0-9-]+)\)/g,
|
||||
|
|
|
|||
Loading…
Reference in a new issue