From b00e8ccf423c2c3a31b21643977134d152f64289 Mon Sep 17 00:00:00 2001 From: Gustav Westling Date: Tue, 19 Mar 2024 13:14:08 +0100 Subject: [PATCH] Multiline footnotes --- index.compiler.spec.tsx | 143 ++++++++++++++++++++++++++++++++++++++++ index.tsx | 36 +++++++++- 2 files changed, 178 insertions(+), 1 deletion(-) diff --git a/index.compiler.spec.tsx b/index.compiler.spec.tsx index 05453f2c..fef10ea8 100644 --- a/index.compiler.spec.tsx +++ b/index.compiler.spec.tsx @@ -3529,6 +3529,149 @@ describe('footnotes', () => { `) }) + + it('should handle multiline footnotes', () => { + render( + compiler(theredoc` + foo[^abc] bar + + [^abc]: Baz + line2 + line3 + + After footnotes content + `) + ) + + expect(root.innerHTML).toMatchInlineSnapshot(` +
+
+

+ foo + + + abc + + + bar +

+

+ After footnotes content +

+
+
+
+ abc: Baz + line2 + line3 +
+
+
+ `) + }) + + it('should handle mixed multiline and singleline footnotes', () => { + render( + compiler(theredoc` + a[^a] b[^b] c[^c] + + [^a]: single + [^b]: bbbb + bbbb + bbbb + [^c]: single-c + `) + ) + + expect(root.innerHTML).toMatchInlineSnapshot(` +
+

+ a + + + a + + + b + + + b + + + c + + + c + + +

+
+
+ a: single +
+
+ b: bbbb + bbbb + bbbb +
+
+ c: single-c +
+
+
+ `) + }) + + it('should handle indented multiline footnote', () => { + render( + compiler(theredoc` + Here's a simple footnote,[^1] and here's a longer one.[^bignote] + + [^1]: This is the first footnote. + + [^bignote]: Here's one with multiple paragraphs and code. + + Indent paragraphs to include them in the footnote. + + \`{ my code }\` + + Add as many paragraphs as you like. + `) + ) + + expect(root.innerHTML).toMatchInlineSnapshot(` +
+

+ Here's a simple footnote, + + + 1 + + + and here's a longer one. + + + bignote + + +

+ +
+ `) + }) }) describe('options.namedCodesToUnicode', () => { diff --git a/index.tsx b/index.tsx index cb68d27c..80157acd 100644 --- a/index.tsx +++ b/index.tsx @@ -187,7 +187,41 @@ const CODE_BLOCK_R = /^(?: {4}[^\n]+\n*)+(?:\n *)+\n?/ const CODE_INLINE_R = /^(`+)\s*([\s\S]*?[^`])\s*\1(?!`)/ const CONSECUTIVE_NEWLINE_R = /^(?:\n *)*\n/ const CR_NEWLINE_R = /\r\n?/g -const FOOTNOTE_R = /^\[\^([^\]]+)](:.*)\n/ + +/** + * Matches footnotes on the format: + * + * [^key]: value + * + * Matches multiline footnotes + * + * [^key]: row + * row + * row + * + * And empty lines in indented multiline footnotes + * + * [^key]: indented with + * row + * + * row + * + * Explanation: + * + * 1. Look for the starting tag, eg: [^key] + * ^\[\^([^\]]+)] + * + * 2. The first line starts with a colon, and continues for the rest of the line + * :(.*) + * + * 3. Parse as many additional lines as possible. Matches new non-empty lines that doesn't begin with a new footnote definition. + * (\n(?!\[\^).+) + * + * 4. ...or allows for repeated newlines if the next line begins with at least four whitespaces. + * (\n+ {4,}.*) + */ +const FOOTNOTE_R = /^\[\^([^\]]+)](:(.*)((\n+ {4,}.*)|(\n(?!\[\^).+))*)/ + const FOOTNOTE_REFERENCE_R = /^\[\^([^\]]+)]/ const FORMFEED_R = /\f/g const FRONT_MATTER_R = /^---[ \t]*\n(.|\n)*\n---[ \t]*\n/