feat: ✨ render footnote at bottom of page
This commit is contained in:
parent
5dbdf8d1d2
commit
05bd82df24
@ -1,5 +1,8 @@
|
|||||||
# Changelog
|
# Changelog
|
||||||
|
|
||||||
|
## [2022-08-23]
|
||||||
|
|
||||||
|
- feat: ✨ Footnotes are rendered as they are in the Obsidian client.
|
||||||
## [2022-08-16]
|
## [2022-08-16]
|
||||||
|
|
||||||
- fix: 🐛 Fix highlights not rendering correctly when mixed with other formatting. ([issue #19](https://github.com/mcndt/noteshare.space/issues/19))
|
- fix: 🐛 Fix highlights not rendering correctly when mixed with other formatting. ([issue #19](https://github.com/mcndt/noteshare.space/issues/19))
|
||||||
|
@ -15,15 +15,24 @@
|
|||||||
import MathBlock from '$lib/marked/renderers/MathBlock.svelte';
|
import MathBlock from '$lib/marked/renderers/MathBlock.svelte';
|
||||||
import ListItem from '$lib/marked/renderers/ListItem.svelte';
|
import ListItem from '$lib/marked/renderers/ListItem.svelte';
|
||||||
import Code from '$lib/marked/renderers/Code.svelte';
|
import Code from '$lib/marked/renderers/Code.svelte';
|
||||||
|
import FootnoteRef from '$lib/marked/renderers/FootnoteRef.svelte';
|
||||||
|
import Footnote from '$lib/marked/renderers/Footnote.svelte';
|
||||||
|
|
||||||
export let plaintext: string;
|
export let plaintext: string;
|
||||||
let ref: HTMLDivElement;
|
let ref: HTMLDivElement;
|
||||||
|
let footnotes: HTMLDivElement[];
|
||||||
|
let footnoteContainer: HTMLDivElement;
|
||||||
|
|
||||||
// @ts-ignore: typing mismatch
|
// @ts-ignore: typing mismatch
|
||||||
marked.use({ extensions: extensions });
|
marked.use({ extensions: extensions });
|
||||||
|
|
||||||
const options = { ...marked.defaults, breaks: true };
|
const options = { ...marked.defaults, breaks: true };
|
||||||
|
|
||||||
|
function onParsed() {
|
||||||
|
setTitle();
|
||||||
|
parseFootnotes();
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Searches for the first major header in the document to use as page title.
|
* Searches for the first major header in the document to use as page title.
|
||||||
*/
|
*/
|
||||||
@ -37,6 +46,19 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* find all elements inside "ref" that have the data-footnote attribute
|
||||||
|
*/
|
||||||
|
function parseFootnotes() {
|
||||||
|
footnotes = Array.from(ref.querySelectorAll('[data-footnote]'));
|
||||||
|
}
|
||||||
|
|
||||||
|
$: if (footnotes?.length > 0 && footnoteContainer) {
|
||||||
|
footnotes.forEach((footnote) => {
|
||||||
|
footnoteContainer.appendChild(footnote);
|
||||||
|
});
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div
|
<div
|
||||||
@ -47,7 +69,7 @@ prose-strong:font-bold prose-a:font-normal prose-blockquote:font-normal prose-bl
|
|||||||
prose-blockquote:first:before:content-[''] prose-hr:transition-colors"
|
prose-blockquote:first:before:content-[''] prose-hr:transition-colors"
|
||||||
>
|
>
|
||||||
<SvelteMarkdown
|
<SvelteMarkdown
|
||||||
on:parsed={setTitle}
|
on:parsed={onParsed}
|
||||||
renderers={{
|
renderers={{
|
||||||
heading: Heading,
|
heading: Heading,
|
||||||
list: List,
|
list: List,
|
||||||
@ -60,9 +82,17 @@ prose-blockquote:first:before:content-[''] prose-hr:transition-colors"
|
|||||||
blockquote: Blockquote,
|
blockquote: Blockquote,
|
||||||
'math-inline': MathInline,
|
'math-inline': MathInline,
|
||||||
'math-block': MathBlock,
|
'math-block': MathBlock,
|
||||||
code: Code
|
code: Code,
|
||||||
|
'footnote-ref': FootnoteRef,
|
||||||
|
footnote: Footnote
|
||||||
}}
|
}}
|
||||||
source={plaintext}
|
source={plaintext}
|
||||||
{options}
|
{options}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
<!-- footnote container -->
|
||||||
|
{#if footnotes?.length > 0}
|
||||||
|
<hr />
|
||||||
|
<div bind:this={footnoteContainer} />
|
||||||
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
|
@ -117,11 +117,68 @@ const MathBlock = {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const footnoteRef = {
|
||||||
|
name: 'footnote-ref',
|
||||||
|
level: 'inline',
|
||||||
|
start(src: string) {
|
||||||
|
return src.indexOf('[^');
|
||||||
|
},
|
||||||
|
|
||||||
|
tokenizer(src: string) {
|
||||||
|
const match = src.match(/^\[\^([^\]]+)\]/);
|
||||||
|
if (match) {
|
||||||
|
return {
|
||||||
|
type: 'footnote-ref',
|
||||||
|
raw: match[0],
|
||||||
|
id: match[1].trim()
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const footnote = {
|
||||||
|
name: 'footnote',
|
||||||
|
level: 'block',
|
||||||
|
start(src: string) {
|
||||||
|
return src.match(/^\[\^([^\]]+)\]:/)?.index;
|
||||||
|
},
|
||||||
|
|
||||||
|
tokenizer(src: string): any {
|
||||||
|
const matchFootnote = /^\[\^([^\]]+)\]: ?((?:[^\r\n]*))/;
|
||||||
|
|
||||||
|
const lines = src.split('\n');
|
||||||
|
const match = lines[0].match(matchFootnote);
|
||||||
|
if (match) {
|
||||||
|
// find all subsequent lines that are not a match with matchFootnote or blank
|
||||||
|
let i = 1;
|
||||||
|
while (i < lines.length && !lines[i].match(matchFootnote) && lines[i].trim() !== '') {
|
||||||
|
i++;
|
||||||
|
}
|
||||||
|
const raw = lines.slice(0, i).join('\n');
|
||||||
|
|
||||||
|
// const text equals raw without the [^id]: part
|
||||||
|
const text = raw.replace(/^\[\^([^\]]+)\]: ?/, '').trim();
|
||||||
|
|
||||||
|
return {
|
||||||
|
type: 'footnote',
|
||||||
|
raw: raw,
|
||||||
|
id: match[1].trim(),
|
||||||
|
// @ts-expect-error - marked types are wrong
|
||||||
|
tokens: this.lexer.blockTokens(text, [])
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
export default [
|
export default [
|
||||||
InternalLinkExtension,
|
InternalLinkExtension,
|
||||||
InternalEmbedExtension,
|
InternalEmbedExtension,
|
||||||
TagExtension,
|
TagExtension,
|
||||||
HighlightExtension,
|
HighlightExtension,
|
||||||
MathBlock,
|
MathBlock,
|
||||||
MathInline
|
MathInline,
|
||||||
|
footnoteRef,
|
||||||
|
footnote
|
||||||
];
|
];
|
||||||
|
35
webapp/src/lib/marked/renderers/Footnote.svelte
Normal file
35
webapp/src/lib/marked/renderers/Footnote.svelte
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import { scrollToId } from '$lib/util/scrollToId';
|
||||||
|
|
||||||
|
export let id: string;
|
||||||
|
|
||||||
|
let content: HTMLElement;
|
||||||
|
let returnRef: HTMLElement;
|
||||||
|
|
||||||
|
let refMoved = false;
|
||||||
|
|
||||||
|
$: if (content && returnRef) {
|
||||||
|
// Find the p element in content move the return link to the end of that p element
|
||||||
|
const p = content.querySelector('p');
|
||||||
|
if (p) {
|
||||||
|
p.appendChild(returnRef);
|
||||||
|
refMoved = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div data-footnote class="flex gap-2 prose-p:my-0">
|
||||||
|
<p>{id}.</p>
|
||||||
|
<span bind:this={content} class="">
|
||||||
|
<slot />
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<span bind:this={returnRef} class="ml-1 {refMoved ? 'inline' : 'hidden'}"
|
||||||
|
><a
|
||||||
|
on:click|preventDefault={() => scrollToId(`footnote-ref-${id}`)}
|
||||||
|
id="footnote-{id}"
|
||||||
|
href="#footnote-ref-{id}"
|
||||||
|
class="no-underline">⮥</a
|
||||||
|
></span
|
||||||
|
>
|
13
webapp/src/lib/marked/renderers/FootnoteRef.svelte
Normal file
13
webapp/src/lib/marked/renderers/FootnoteRef.svelte
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import { scrollToId } from '$lib/util/scrollToId';
|
||||||
|
|
||||||
|
export let id: string;
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<sup
|
||||||
|
><a
|
||||||
|
on:click|preventDefault={() => scrollToId(`footnote-${id}`)}
|
||||||
|
id="footnote-ref-{id}"
|
||||||
|
href="#footnote-{id}">[{id}]</a
|
||||||
|
></sup
|
||||||
|
>
|
6
webapp/src/lib/util/scrollToId.ts
Normal file
6
webapp/src/lib/util/scrollToId.ts
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
export function scrollToId(id: string) {
|
||||||
|
document.querySelector(`#${id}`)?.scrollIntoView();
|
||||||
|
|
||||||
|
// scroll 65px down to avoid the navbar
|
||||||
|
window.scrollBy(0, -65);
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user