diff --git a/MacDown/Code/Document/MPRenderer.m b/MacDown/Code/Document/MPRenderer.m
index 9aec3617..c4021bef 100644
--- a/MacDown/Code/Document/MPRenderer.m
+++ b/MacDown/Code/Document/MPRenderer.m
@@ -423,6 +423,7 @@ NS_INLINE void add_to_languages(
flags, tocLevel);
htmlRenderer->blockcode = hoedown_patch_render_blockcode;
htmlRenderer->listitem = hoedown_patch_render_listitem;
+ htmlRenderer->header = hoedown_patch_render_header;
hoedown_html_renderer_state_extra *extra =
hoedown_malloc(sizeof(hoedown_html_renderer_state_extra));
diff --git a/MacDown/Code/Extension/hoedown_html_patch.c b/MacDown/Code/Extension/hoedown_html_patch.c
index 8cd5cec4..d2959b47 100644
--- a/MacDown/Code/Extension/hoedown_html_patch.c
+++ b/MacDown/Code/Extension/hoedown_html_patch.c
@@ -161,6 +161,85 @@ void hoedown_patch_render_listitem(
HOEDOWN_BUFPUTSL(ob, "\n");
}
+// Build a stable text-derived slug from a heading's HTML content.
+// Strips HTML tags, lowercases ASCII, converts spaces to hyphens,
+// drops ASCII punctuation, preserves UTF-8 multi-byte sequences so
+// accented characters survive (e.g. "Introducción" -> "introducción").
+static void slugify(hoedown_buffer *out, const hoedown_buffer *content)
+{
+ if (!content || !content->size)
+ return;
+
+ int in_tag = 0;
+ int last_was_dash = 1;
+
+ for (size_t i = 0; i < content->size; i++)
+ {
+ uint8_t c = content->data[i];
+
+ if (in_tag)
+ {
+ if (c == '>') in_tag = 0;
+ continue;
+ }
+ if (c == '<')
+ {
+ in_tag = 1;
+ continue;
+ }
+
+ if (c >= 0x80)
+ {
+ hoedown_buffer_putc(out, c);
+ last_was_dash = 0;
+ continue;
+ }
+
+ if (c >= 'A' && c <= 'Z')
+ c += 32;
+
+ if ((c >= 'a' && c <= 'z') || (c >= '0' && c <= '9') || c == '_')
+ {
+ hoedown_buffer_putc(out, c);
+ last_was_dash = 0;
+ }
+ else if (c == ' ' || c == '\t' || c == '-')
+ {
+ if (!last_was_dash)
+ {
+ hoedown_buffer_putc(out, '-');
+ last_was_dash = 1;
+ }
+ }
+ }
+
+ while (out->size > 0 && out->data[out->size - 1] == '-')
+ out->size--;
+}
+
+// rndr_header replacement that always emits a text-derived id, independent
+// of the TOC nesting level. Enables [link](#section-name) navigation.
+void hoedown_patch_render_header(
+ hoedown_buffer *ob, const hoedown_buffer *content, int level,
+ const hoedown_renderer_data *data)
+{
+ (void)data;
+ if (ob->size) hoedown_buffer_putc(ob, '\n');
+
+ hoedown_buffer *slug = hoedown_buffer_new(content ? content->size : 16);
+ slugify(slug, content);
+ if (slug->size == 0)
+ HOEDOWN_BUFPUTSL(slug, "section");
+
+ hoedown_buffer_printf(ob, "
https://example.com/path?query=param&other=value
-Visit https://example.com for more.
Check out http://www.example.org too.
-Email me at user@example.com or visit https://example.com.
-Multiple autolinks: https://first.com and https://second.com.
@@ -36,7 +36,7 @@End with autolink: https://end.com
-This is a paragraph under heading 1. It contains some text to test basic paragraph rendering.
-Another paragraph here. This one is under heading 2.
-A third paragraph for heading 3.
-Fourth level heading with text.
-Fifth level heading.
-The deepest heading level in Markdown.
diff --git a/MacDownTests/Fixtures/blockquotes.html b/MacDownTests/Fixtures/blockquotes.html index a01c9ebd..6907b662 100644 --- a/MacDownTests/Fixtures/blockquotes.html +++ b/MacDownTests/Fixtures/blockquotes.html @@ -1,19 +1,19 @@ --This is a blockquote. It can span multiple lines.
-Single line blockquote.
-First level quote
@@ -27,7 +27,7 @@Nested Blockquotes
-First paragraph in blockquote.
@@ -35,7 +35,7 @@Blockquote with Multiple Paragraphs
Second paragraph in blockquote.
-This has bold text.
@@ -45,7 +45,7 @@Blockquote with Formatting
This has
code.
-@@ -61,22 +61,22 @@
Blockquote with Lists
--Header in Blockquote
+Header in Blockquote
Some text under the header.
-This is lazy continuation without the > character.
Top level
diff --git a/MacDownTests/Fixtures/code-fenced.html b/MacDownTests/Fixtures/code-fenced.html index 56e8264c..4035f876 100644 --- a/MacDownTests/Fixtures/code-fenced.html +++ b/MacDownTests/Fixtures/code-fenced.html @@ -1,18 +1,18 @@ -Fenced Code Block Tests
+Fenced Code Block Tests
-Basic Fenced Block
+Basic Fenced Block
-This is a basic code block. No language specified. Multiple lines.Code Block with Content
+Code Block with Content
-function example() { return true; }Multiple Blocks
+Multiple Blocks
First block:
@@ -22,11 +22,11 @@Multiple Blocks
-Block twoEmpty Block
+Empty Block
-Block with Special Characters
+Block with Special Characters
-<html> **Not bold** diff --git a/MacDownTests/Fixtures/code-inline.html b/MacDownTests/Fixtures/code-inline.html index 3144def7..18dd9478 100644 --- a/MacDownTests/Fixtures/code-inline.html +++ b/MacDownTests/Fixtures/code-inline.html @@ -1,16 +1,16 @@ -Inline Code Tests
+Inline Code Tests
-Basic Inline Code
+Basic Inline Code
Use the
printf()function to print output.The variable
-userNamestores the user's name.Multiple Code Spans
+Multiple Code Spans
Use
-git addand thengit committo save changes.Code with Special Characters
+Code with Special Characters
This is
@@ -20,7 +20,7 @@code with spaces.Code with Special Characters
Use
-**not bold**inside code.Edge Cases
+Edge Cases
diff --git a/MacDownTests/Fixtures/code-languages.html b/MacDownTests/Fixtures/code-languages.html index e49d9bcf..0f3229c7 100644 --- a/MacDownTests/Fixtures/code-languages.html +++ b/MacDownTests/Fixtures/code-languages.html @@ -1,49 +1,49 @@ -
Code at startof line.Code Blocks with Language Tags
+Code Blocks with Language Tags
-JavaScript
+JavaScript
-function greet(name) { console.log("Hello, " + name); }Python
+Python
-def greet(name): print(f"Hello, {name}")Ruby
+Ruby
-def greet(name) puts "Hello, #{name}" endHTML
+HTML
-<div class="container"> <h1>Hello World</h1> </div>CSS
+CSS
-.container { max-width: 1200px; margin: 0 auto; }Objective-C
+Objective-C
-- (void)greetWithName:(NSString *)name { NSLog(@"Hello, %@", name); }Swift
+Swift
-func greet(name: String) { print("Hello, \(name)") }Bash
+Bash
diff --git a/MacDownTests/Fixtures/edge-cases.html b/MacDownTests/Fixtures/edge-cases.html index 22655924..514ea273 100644 --- a/MacDownTests/Fixtures/edge-cases.html +++ b/MacDownTests/Fixtures/edge-cases.html @@ -1,12 +1,12 @@ -#!/bin/bash echo "Hello, World"Edge Cases and Special Characters
+Edge Cases and Special Characters
-Empty Lines
+Empty Lines
This paragraph has an empty line after it.
This paragraph has two empty lines before it.
-Special Characters
+Special Characters
Ampersand: & &
@@ -16,7 +16,7 @@Special Characters
Quotes: "double" and 'single'
-Escaping
+Escaping
*Not italic*
@@ -28,7 +28,7 @@Escaping
~~Not strikethrough~~
-HTML Entities
+HTML Entities
© ® ™
@@ -36,7 +36,7 @@HTML Entities
(non-breaking space)
-Unicode
+Unicode
Emoji: 😀 🚀 ⭐
@@ -46,15 +46,15 @@Unicode
Arrows: → ← ↑ ↓
-Edge Case Headers
+Edge Case Headers
-No space after hash
+No space after hash
-Trailing hash
+Trailing hash
-Multiple##Hashes
+Multiple##Hashes
-Consecutive Formatting
+Consecutive Formatting
BoldMore bold
@@ -62,7 +62,7 @@Consecutive Formatting
All together
-Malformed Markdown
+Malformed Markdown
[Link without URL]
@@ -74,7 +74,7 @@Malformed Markdown
~~Unclosed strikethrough
-Line Breaks
+Line Breaks
Line with two spaces at end Should create line break.
@@ -82,19 +82,19 @@Line Breaks
Line without spaces Should not break.
-Tabs vs Spaces
+Tabs vs Spaces
-Tab-indented text Four-space indented textMixed Whitespace
+Mixed Whitespace
Text with multiple spaces.
Text with tabs.
-URL Edge Cases
+URL Edge Cases
https://example.com/path?query=1&other=2
@@ -102,7 +102,7 @@URL Edge Cases
ftp://files.example.com
-Numbers and Punctuation
+Numbers and Punctuation
-
- Not a list (no space).
@@ -110,9 +110,9 @@Numbers and Punctuation
100.5 decimal number
-1 hashtag number
+1 hashtag number
-Backslashes
+Backslashes
Backslash: \
@@ -120,7 +120,7 @@Backslashes
Network: \\server\share
-Parentheses and Brackets
+Parentheses and Brackets
(Text in parentheses)
@@ -130,7 +130,7 @@Parentheses and Brackets
-
Consecutive Punctuation
+Consecutive Punctuation
Multiple periods...
@@ -140,7 +140,7 @@Consecutive Punctuation
Mixed?!?!
-Empty Elements
+Empty Elements
[]
diff --git a/MacDownTests/Fixtures/emphasis.html b/MacDownTests/Fixtures/emphasis.html index d8c5dc3a..e6405865 100644 --- a/MacDownTests/Fixtures/emphasis.html +++ b/MacDownTests/Fixtures/emphasis.html @@ -1,18 +1,18 @@ -Emphasis Tests
+Emphasis Tests
-Italic
+Italic
This is italic text with asterisks.
This is italic text with underscores.
-Bold
+Bold
This is bold text with asterisks.
This is bold text with underscores.
-Combined Emphasis
+Combined Emphasis
This is bold and italic with asterisks.
@@ -22,7 +22,7 @@Combined Emphasis
This is *italic with bold inside*.
-Edge Cases
+Edge Cases
Notactuallyitalic because no spaces.
diff --git a/MacDownTests/Fixtures/horizontal-rules.html b/MacDownTests/Fixtures/horizontal-rules.html index 60eafad0..b97e4a5a 100644 --- a/MacDownTests/Fixtures/horizontal-rules.html +++ b/MacDownTests/Fixtures/horizontal-rules.html @@ -1,6 +1,6 @@ -Horizontal Rule Tests
+Horizontal Rule Tests
-Three Hyphens
+Three Hyphens
Text before rule.
@@ -8,7 +8,7 @@Three Hyphens
Text after rule.
-Three Asterisks
+Three Asterisks
Text before rule.
@@ -16,7 +16,7 @@Three Asterisks
Text after rule.
-Three Underscores
+Three Underscores
Text before rule.
@@ -24,7 +24,7 @@Three Underscores
Text after rule.
-More Than Three
+More Than Three
@@ -32,7 +32,7 @@More Than Three
-With Spaces
+With Spaces
@@ -40,7 +40,7 @@With Spaces
-Multiple Rules
+Multiple Rules
First section.
@@ -56,9 +56,9 @@Multiple Rules
Fourth section.
-Rules in Context
+Rules in Context
-Heading
+Heading
Some text here.
diff --git a/MacDownTests/Fixtures/images.html b/MacDownTests/Fixtures/images.html index 80d2faea..bb9aa1af 100644 --- a/MacDownTests/Fixtures/images.html +++ b/MacDownTests/Fixtures/images.html @@ -1,6 +1,6 @@ -Image Tests
+Image Tests
-Inline Images
+Inline Images
@@ -8,17 +8,17 @@
Inline Images
-
Reference Images
+Reference Images
-
Images in Links
+Images in Links
-Edge Cases
+Edge Cases
diff --git a/MacDownTests/Fixtures/links.html b/MacDownTests/Fixtures/links.html index 3874cbc8..8ec12520 100644 --- a/MacDownTests/Fixtures/links.html +++ b/MacDownTests/Fixtures/links.html @@ -1,6 +1,6 @@ -
Link Tests
+Link Tests
-Inline Links
+Inline Links
This is an inline link.
@@ -8,7 +8,7 @@Inline Links
This is a link to a path.
-Reference Links
+Reference Links
This is a reference link.
@@ -16,7 +16,7 @@Reference Links
This uses implicit reference.
-Edge Cases
+Edge Cases
Link at start of line.
diff --git a/MacDownTests/Fixtures/lists-nested.html b/MacDownTests/Fixtures/lists-nested.html index c126a442..b543fbf0 100644 --- a/MacDownTests/Fixtures/lists-nested.html +++ b/MacDownTests/Fixtures/lists-nested.html @@ -1,6 +1,6 @@ -Nested List Tests
+Nested List Tests
-Nested Unordered Lists
+Nested Unordered Lists
-
- First level @@ -13,7 +13,7 @@
Nested Unordered Lists
- Back to first level
Nested Ordered Lists
+Nested Ordered Lists
-
- First level @@ -30,7 +30,7 @@
Nested Ordered Lists
- Back to first level
Mixed Nested Lists
+Mixed Nested Lists
-
- Unordered first @@ -48,7 +48,7 @@
Mixed Nested Lists
- Unordered first again
Nested with Content
+Nested with Content
-
First item
@@ -66,7 +66,7 @@Nested with Content
Second item
Deep Nesting
+Deep Nesting
-
- Level 1 diff --git a/MacDownTests/Fixtures/lists-ordered.html b/MacDownTests/Fixtures/lists-ordered.html index 98953fc2..5c3d947b 100644 --- a/MacDownTests/Fixtures/lists-ordered.html +++ b/MacDownTests/Fixtures/lists-ordered.html @@ -1,6 +1,6 @@ -
Ordered List Tests
+Ordered List Tests
-Basic Ordered List
+Basic Ordered List
-
- First item
@@ -8,7 +8,7 @@Basic Ordered List
- Third item
List Starting with Different Number
+List Starting with Different Number
-
- Fifth item
@@ -16,7 +16,7 @@List Starting with Different Number
- Seventh item
List with All Ones
+List with All Ones
-
- First item
@@ -24,7 +24,7 @@List with All Ones
- Third item (still numbered 3)
List with Paragraphs
+List with Paragraphs
-
- @@ -32,7 +32,7 @@
First item with text
List with Paragraphs
Third item
List with Inline Formatting
+List with Inline Formatting
-
- Bold item
@@ -41,7 +41,7 @@List with Inline Formatting
- Link item
Tight vs Loose Lists
+Tight vs Loose Lists
Tight list:
diff --git a/MacDownTests/Fixtures/lists-unordered.html b/MacDownTests/Fixtures/lists-unordered.html index 03661cc5..8ecfc94d 100644 --- a/MacDownTests/Fixtures/lists-unordered.html +++ b/MacDownTests/Fixtures/lists-unordered.html @@ -1,6 +1,6 @@ -Unordered List Tests
+Unordered List Tests
-Basic List with Asterisks
+Basic List with Asterisks
-
- First item
@@ -8,7 +8,7 @@Basic List with Asterisks
- Third item
Basic List with Hyphens
+Basic List with Hyphens
-
- First item
@@ -16,7 +16,7 @@Basic List with Hyphens
- Third item
Basic List with Plus
+Basic List with Plus
-
- First item
@@ -24,7 +24,7 @@Basic List with Plus
- Third item
List with Paragraphs
+List with Paragraphs
-
- @@ -32,7 +32,7 @@
First item with text
List with Paragraphs
Third item
List with Inline Formatting
+List with Inline Formatting
-
- Bold item
@@ -41,7 +41,7 @@List with Inline Formatting
- Link item
Tight vs Loose Lists
+Tight vs Loose Lists
Tight list:
diff --git a/MacDownTests/Fixtures/mathjax-in-code.html b/MacDownTests/Fixtures/mathjax-in-code.html index 61df5c3f..59ae6366 100644 --- a/MacDownTests/Fixtures/mathjax-in-code.html +++ b/MacDownTests/Fixtures/mathjax-in-code.html @@ -1,19 +1,19 @@ -MathJax in Code Blocks
+MathJax in Code Blocks
-Regular Math
+Regular Math
This math should be processed: ( x^2 + y^2 = z^2 )
-Math in Code Block
+Math in Code Block
-This math should NOT be processed: \( x^2 + y^2 = z^2 \) And display math: $$ E = mc^2 $$Math in Inline Code
+Math in Inline Code
This math in inline code should be literal:
-\( x^2 \)and$$ E = mc^2 $$More Regular Math
+More Regular Math
Display math should be processed:
diff --git a/MacDownTests/Fixtures/mathjax-syntax.html b/MacDownTests/Fixtures/mathjax-syntax.html index a2e0169e..fe3188e3 100644 --- a/MacDownTests/Fixtures/mathjax-syntax.html +++ b/MacDownTests/Fixtures/mathjax-syntax.html @@ -1,12 +1,12 @@ -MathJax Syntax Tests
+MathJax Syntax Tests
-Inline Math
+Inline Math
The Pythagorean theorem states that ( a^2 + b^2 = c^2 ) for right triangles.
Einstein's famous equation is ( E = mc^2 ).
-Display Math
+Display Math
The quadratic formula is:
@@ -14,13 +14,13 @@Display Math
x = \frac{-b \pm \sqrt{b^2 - 4ac}}{2a} $$ -Integral
+Integral
$$ \int_0^\infty e^{-x^2} dx = \frac{\sqrt{\pi}}{2} $$
-Matrix
+Matrix
$$ \begin{bmatrix} @@ -30,11 +30,11 @@
Matrix
\end{bmatrix} $$ -Greek Letters
+Greek Letters
Symbols like ( \alpha ), ( \beta ), ( \gamma ), and ( \Delta ) are common in mathematics.
-Sum and Product
+Sum and Product
$$ \sum_{i=1}^{n} i = \frac{n(n+1)}{2} @@ -44,6 +44,6 @@
Sum and Product
\prod_{i=1}^{n} i = n! $$ -Fractions and Subscripts
+Fractions and Subscripts
The formula ( \frac{x1 + x2}{2} ) calculates the average.
diff --git a/MacDownTests/Fixtures/mixed-complex.html b/MacDownTests/Fixtures/mixed-complex.html index f8b66cca..009471ca 100644 --- a/MacDownTests/Fixtures/mixed-complex.html +++ b/MacDownTests/Fixtures/mixed-complex.html @@ -1,8 +1,8 @@ -MacDown Feature Guide
+MacDown Feature Guide
Welcome to MacDown, a powerful Markdown editor for macOS.
-Introduction
+Introduction
MacDown is an open source Markdown editor that supports:
@@ -15,7 +15,7 @@Introduction
-Code Examples
+Code Examples
Here's a simple JavaScript function:
@@ -30,7 +30,7 @@Code Examples
Inline code like
-const x = 42;is also supported.Feature Comparison
+Feature Comparison
@@ -65,7 +65,7 @@
-Feature Comparison
Task List
+Task List
Development progress:
@@ -89,9 +89,9 @@Task List
Advanced Features
+Advanced Features
-Nested Lists and Quotes
+Nested Lists and Quotes
Here's what users are saying:
@@ -108,13 +108,13 @@Nested Lists and Quotes
Images and Links
+Images and Links
Check out the logo:
Visit our website at https://macdown.example.com.
-Complex Nesting
+Complex Nesting
-
Installation Steps
@@ -153,7 +153,7 @@Complex Nesting
-Contact
+Contact
Email us at support@macdown.example.com or open an issue on GitHub.
diff --git a/MacDownTests/Fixtures/regression-issue25.html b/MacDownTests/Fixtures/regression-issue25.html index 0770c2d2..c6d8ca3a 100644 --- a/MacDownTests/Fixtures/regression-issue25.html +++ b/MacDownTests/Fixtures/regression-issue25.html @@ -1,4 +1,4 @@ -Adjacent Shortcut Links (Issue #25)
+Adjacent Shortcut Links (Issue #25)
Two shortcut-style links side by side should both render:
diff --git a/MacDownTests/Fixtures/regression-issue34.html b/MacDownTests/Fixtures/regression-issue34.html index 2a2f499a..c75555fb 100644 --- a/MacDownTests/Fixtures/regression-issue34.html +++ b/MacDownTests/Fixtures/regression-issue34.html @@ -1,6 +1,6 @@ -Lists After Colons (Issue #34)
+Lists After Colons (Issue #34)
-Test Case 1: Basic list after colon
+Test Case 1: Basic list after colon
Here is my list:
@@ -10,7 +10,7 @@Test Case 1: Basic list after colon
- Item 3
-Test Case 2: Ordered list after colon
+Test Case 2: Ordered list after colon
My grocery list:
@@ -20,7 +20,7 @@Test Case 2: Ordered list after colon
- Bread
Test Case 3: Nested list after colon
+Test Case 3: Nested list after colon
My tasks:
@@ -39,7 +39,7 @@Test Case 3: Nested list after colon
-Test Case 4: List with proper blank line (works correctly)
+Test Case 4: List with proper blank line (works correctly)
This should work:
diff --git a/MacDownTests/Fixtures/regression-issue36.html b/MacDownTests/Fixtures/regression-issue36.html index 01a69e15..6f06e48d 100644 --- a/MacDownTests/Fixtures/regression-issue36.html +++ b/MacDownTests/Fixtures/regression-issue36.html @@ -1,6 +1,6 @@ -Code Blocks Without Blank Lines (Issue #36)
+Code Blocks Without Blank Lines (Issue #36)
-Test Case 1: Fenced code block immediately after text
+Test Case 1: Fenced code block immediately after text
Here is some code:
@@ -8,7 +8,7 @@Test Case 1: Fenced code block immediately after text
return true; }Test Case 2: Multiple code blocks in sequence
+Test Case 2: Multiple code blocks in sequence
First block:
@@ -19,7 +19,7 @@Test Case 2: Multiple code blocks in sequence
-y = 100Test Case 3: Code block with proper blank line (works correctly)
+Test Case 3: Code block with proper blank line (works correctly)
This should work:
@@ -27,7 +27,7 @@Test Case 3: Code block with proper blank line (works correctly)
return 'yes'; } -Test Case 4: Code block in list without blank line
+Test Case 4: Code block in list without blank line
Steps to follow:
diff --git a/MacDownTests/Fixtures/regression-issue37.html b/MacDownTests/Fixtures/regression-issue37.html index 07e9e6f1..f3ae8cf1 100644 --- a/MacDownTests/Fixtures/regression-issue37.html +++ b/MacDownTests/Fixtures/regression-issue37.html @@ -1,19 +1,19 @@ -Square Brackets in Code (Issue #37)
+Square Brackets in Code (Issue #37)
-Test Case 1: TypeScript index signature
+Test Case 1: TypeScript index signature
-interface MyType { [key: string]: any; }Test Case 2: JavaScript array access
+Test Case 2: JavaScript array access
-const value = array[index]; const obj = { [computed]: 'value' };Test Case 3: Multiple bracket patterns
+Test Case 3: Multiple bracket patterns
-type Dict = { [id: number]: string; @@ -25,11 +25,11 @@Test Case 3: Multiple bracket patterns
}; }Test Case 4: Inline code with brackets
+Test Case 4: Inline code with brackets
Here's an example:
-array[0]andobj[key]should work.Test Case 5: Python dictionary syntax
+Test Case 5: Python dictionary syntax
my_dict = { "key": "value" diff --git a/MacDownTests/Fixtures/strikethrough.html b/MacDownTests/Fixtures/strikethrough.html index f1baac64..a6323467 100644 --- a/MacDownTests/Fixtures/strikethrough.html +++ b/MacDownTests/Fixtures/strikethrough.html @@ -1,12 +1,12 @@ -Strikethrough Tests
+Strikethrough Tests
-Basic Strikethrough
+Basic Strikethrough
This is
strikethrough text.-
Entire line strikethroughCombined with Other Formatting
+Combined with Other Formatting
This is
@@ -16,11 +16,11 @@strikethrough with bold.Combined with Other Formatting
This is italic with
-strikethrough.Multiple Strikethroughs
+Multiple Strikethroughs
-
Firstandsecondstrikethrough.Edge Cases
+Edge Cases
Not~strikethrough (single tilde).
@@ -30,7 +30,7 @@Edge Cases
End of line
-has strikethrough.In Lists
+In Lists
- diff --git a/MacDownTests/Fixtures/syntax-highlighting-aliases.html b/MacDownTests/Fixtures/syntax-highlighting-aliases.html index e1341c77..b724674c 100644 --- a/MacDownTests/Fixtures/syntax-highlighting-aliases.html +++ b/MacDownTests/Fixtures/syntax-highlighting-aliases.html @@ -1,16 +1,16 @@ -
Strikethrough list itemLanguage Aliases Test
+Language Aliases Test
-JavaScript (js alias)
+JavaScript (js alias)
-const greeting = "Hello, World!"; console.log(greeting);Objective-C (objc alias)
+Objective-C (objc alias)
-NSString *greeting = @"Hello, World!"; NSLog(@"%@", greeting);HTML (maps to markup)
+HTML (maps to markup)
-<!DOCTYPE html> <html> @@ -19,12 +19,12 @@HTML (maps to markup)
</body> </html>Shell (sh alias for bash)
+Shell (sh alias for bash)
-#!/bin/sh echo "Hello, World!"C++ (c++ and cpp aliases)
+C++ (c++ and cpp aliases)
#include <iostream> int main() { diff --git a/MacDownTests/Fixtures/syntax-highlighting-languages.html b/MacDownTests/Fixtures/syntax-highlighting-languages.html index e947631a..454bba11 100644 --- a/MacDownTests/Fixtures/syntax-highlighting-languages.html +++ b/MacDownTests/Fixtures/syntax-highlighting-languages.html @@ -1,41 +1,41 @@ -Common Programming Languages
+Common Programming Languages
-JavaScript
+JavaScript
-function fibonacci(n) { if (n <= 1) return n; return fibonacci(n - 1) + fibonacci(n - 2); }Python
+Python
-def fibonacci(n): if n <= 1: return n return fibonacci(n - 1) + fibonacci(n - 2)C
+C
-int fibonacci(int n) { if (n <= 1) return n; return fibonacci(n - 1) + fibonacci(n - 2); }C++
+C++
-int fibonacci(int n) { if (n <= 1) return n; return fibonacci(n - 1) + fibonacci(n - 2); }Java
+Java
-public int fibonacci(int n) { if (n <= 1) return n; return fibonacci(n - 1) + fibonacci(n - 2); }Go
+Go
-func fibonacci(n int) int { if n <= 1 { @@ -44,7 +44,7 @@Go
return fibonacci(n-1) + fibonacci(n-2) }Rust
+Rust
-fn fibonacci(n: u32) -> u32 { match n { @@ -54,7 +54,7 @@Rust
} }TypeScript
+TypeScript
function fibonacci(n: number): number { if (n <= 1) return n; diff --git a/MacDownTests/Fixtures/syntax-highlighting-mixed.html b/MacDownTests/Fixtures/syntax-highlighting-mixed.html index d6aafc0f..4b1dc860 100644 --- a/MacDownTests/Fixtures/syntax-highlighting-mixed.html +++ b/MacDownTests/Fixtures/syntax-highlighting-mixed.html @@ -1,24 +1,24 @@ -Mixed Code Blocks
+Mixed Code Blocks
-Labeled Code Block
+Labeled Code Block
-def hello(): print("Hello from Python")Unlabeled Code Block
+Unlabeled Code Block
-This code has no language specified. It should get the "language-none" class.Another Labeled Block
+Another Labeled Block
-console.log("Hello from JavaScript");Indented Code Block
+Indented Code Block
-This is indented code. It should not have language classes.Inline Code
+Inline Code
Here is some
diff --git a/MacDownTests/Fixtures/tables.html b/MacDownTests/Fixtures/tables.html index ffc2b0b5..c96e4978 100644 --- a/MacDownTests/Fixtures/tables.html +++ b/MacDownTests/Fixtures/tables.html @@ -1,6 +1,6 @@ -inline codethat should not have language classes.Table Tests
+Table Tests
-Basic Table
+Basic Table
@@ -25,7 +25,7 @@
-Basic Table
Table with Alignment
+Table with Alignment
@@ -55,14 +55,14 @@
-Table with Alignment
Minimal Table
+Minimal Table
| A | B | | - | - | | 1 | 2 | | 3 | 4 |
-Table with Inline Formatting
+Table with Inline Formatting
@@ -87,7 +87,7 @@
-Table with Inline Formatting
Table with Different Column Widths
+Table with Different Column Widths
@@ -112,7 +112,7 @@
-Table with Different Column Widths
Empty Cells
+Empty Cells
diff --git a/MacDownTests/Fixtures/task-lists.html b/MacDownTests/Fixtures/task-lists.html index e197a15d..b3b3f267 100644 --- a/MacDownTests/Fixtures/task-lists.html +++ b/MacDownTests/Fixtures/task-lists.html @@ -1,6 +1,6 @@ -
Task List Tests
+Task List Tests
-Basic Task Lists
+Basic Task Lists
-
- Unchecked task @@ -12,7 +12,7 @@
Basic Task Lists
- [X] Checked with capital X
Nested Task Lists
+Nested Task Lists
-
- Parent task @@ -30,7 +30,7 @@
Nested Task Lists
Mixed with Regular Lists
+Mixed with Regular Lists
-
- Regular list item
@@ -42,7 +42,7 @@Mixed with Regular Lists
- Final regular item
Tasks with Inline Formatting
+Tasks with Inline Formatting
-
- Task with bold text @@ -55,7 +55,7 @@
Tasks with Inline Formatting
Numbered Task Lists
+Numbered Task Lists
-
- First task @@ -66,7 +66,7 @@
Numbered Task Lists
Edge Cases
+Edge Cases
- [] Invalid (no space)
diff --git a/MacDownTests/Fixtures/unicode.html b/MacDownTests/Fixtures/unicode.html index 70cb12a7..81672610 100644 --- a/MacDownTests/Fixtures/unicode.html +++ b/MacDownTests/Fixtures/unicode.html @@ -1,82 +1,82 @@ -Unicode Test
+Unicode Test
-Basic Latin and Symbols
+Basic Latin and Symbols
Regular text with special characters: © ® ™ § ¶
-Accented Characters
+Accented Characters
Café, naïve, résumé, piñata, Zürich
-Currency Symbols
+Currency Symbols
Dollar: $ Euro: € Pound: £ Yen: ¥ Bitcoin: ₿
-Mathematical Symbols
+Mathematical Symbols
∀ ∃ ∅ ∞ ∫ ∑ ∏ ≈ ≠ ≤ ≥ ± × ÷
-Arrows
+Arrows
← → ↑ ↓ ↔ ⇐ ⇒ ⇔
-Chinese Characters
+Chinese Characters
你好世界 (Hello World)
中文字符测试
-Japanese Characters
+Japanese Characters
こんにちは世界 (Hello World)
ひらがな、カタカナ、漢字
-Korean Characters
+Korean Characters
안녕하세요 세계 (Hello World)
한글 테스트
-Emoji
+Emoji
😀 😃 😄 😁 🚀 🌟 ⭐ 💻 📱 🎉 ✨
-Arabic
+Arabic
مرحبا بالعالم (Hello World)
-Hebrew
+Hebrew
שלום עולם (Hello World)
-Cyrillic
+Cyrillic
Привет мир (Hello World)
-Greek
+Greek
Γεια σου κόσμε (Hello World)
-Special Punctuation
+Special Punctuation
Em dash: — En dash: – Ellipsis: … Quotes: " " ' '
-Combining Characters
+Combining Characters
Café (with combining accent: Café)
-Zero-Width Characters
+Zero-Width Characters
Zero-width space: (invisible)
-Code Block with Unicode
+Code Block with Unicode
-def greet(): print("Hello 世界 🌍") # Comment with unicode: ñ á é í ó úBullet List with Unicode
+Bullet List with Unicode
-
- Item with emoji 🎯
@@ -85,10 +85,10 @@Bullet List with Unicode
- Item with special char ©
Links with Unicode
+Links with Unicode
-Emphasis with Unicode
+Emphasis with Unicode
Bold 粗体 and italic 斜体
diff --git a/MacDownTests/MPHTMLExportTests.m b/MacDownTests/MPHTMLExportTests.m index 7344b518..9ab68f9b 100644 --- a/MacDownTests/MPHTMLExportTests.m +++ b/MacDownTests/MPHTMLExportTests.m @@ -401,7 +401,7 @@ - (void)testHTMLExportWritesToFile error:&error]; XCTAssertNil(error, @"Should read file without error"); XCTAssertEqualObjects(readBack, html, @"Content should match"); - XCTAssertTrue([readBack containsString:@"Test Document
"], @"Should contain heading"); + XCTAssertTrue([readBack containsString:@"Test Document
"], @"Should contain heading"); XCTAssertTrue([readBack containsString:@"paragraph"], @"Should contain bold text"); } @@ -431,7 +431,7 @@ - (void)testHTMLExportOverwritesExistingFile NSString *readBack = [NSString stringWithContentsOfURL:fileURL encoding:NSUTF8StringEncoding error:&error]; - XCTAssertTrue([readBack containsString:@"New Content
"], @"Should have new content"); + XCTAssertTrue([readBack containsString:@"New Content
"], @"Should have new content"); XCTAssertFalse([readBack containsString:@"Initial"], @"Should not have old content"); } @@ -460,8 +460,8 @@ - (void)testHTMLExportCreatesValidStructure @"Should be complete HTML document"); XCTAssertTrue([content containsString:@""], @"Should have head"); XCTAssertTrue([content containsString:@""], @"Should have h1"); - XCTAssertTrue([content containsString:@""], @"Should have h2"); + XCTAssertTrue([content containsString:@"
"], @"Should have list"); XCTAssertTrue([content containsString:@"
- "], @"Should have list items"); XCTAssertTrue([content containsString:@"
"] || [content containsString:@""], diff --git a/MacDownTests/MPImageExportTests.m b/MacDownTests/MPImageExportTests.m index 64a48244..9c297443 100644 --- a/MacDownTests/MPImageExportTests.m +++ b/MacDownTests/MPImageExportTests.m @@ -405,7 +405,7 @@ - (void)testExportWithImagesAndText [self.renderer parseMarkdown:self.dataSource.markdown]; NSString *html = [self.renderer HTMLForExportWithStyles:NO highlighting:NO]; - XCTAssertTrue([html containsString:@""], @"Should have heading"); + XCTAssertTrue([html containsString:@"
"], - @"CRLF heading should render as
"); + XCTAssertTrue([crlfHtml containsString:@"
element"); XCTAssertTrue([crlfHtml containsString:@"Heading"], @"CRLF heading text should appear in output"); XCTAssertTrue([crlfHtml containsString:@"
"], @@ -753,4 +753,50 @@ - (void)testFencedCodeAfterTextWithCRLFLineEndings @"CRLF and LF fenced-code-after-text should produce identical HTML"); } + +#pragma mark - Heading Anchor ID Tests + +// Headings should always receive a text-derived id attribute so that +// CommonMark/GFM-style anchor links like [text](#section) navigate to +// the corresponding heading in the preview. + +- (void)testHeadingHasSlugBasedAnchorId +{ + NSString *html = [self renderMarkdown:@"## Foo Bar" + withExtensions:0 + rendererFlags:0]; + XCTAssertTrue([html containsString:@"id=\"foo-bar\""], + @"Heading should have a slug-based id derived from its text. Got: %@", html); +} + +- (void)testHeadingAnchorIdPreservesUTF8 +{ + NSString *html = [self renderMarkdown:@"## Introducción" + withExtensions:0 + rendererFlags:0]; + XCTAssertTrue([html containsString:@"id=\"introducción\""], + @"Heading id should preserve UTF-8 multi-byte characters. Got: %@", html); +} + +- (void)testHeadingAnchorIdStripsPunctuation +{ + NSString *html = [self renderMarkdown:@"# Hello, World!" + withExtensions:0 + rendererFlags:0]; + XCTAssertTrue([html containsString:@"id=\"hello-world\""], + @"Heading id should drop ASCII punctuation. Got: %@", html); +} + +- (void)testHeadingAnchorIdEmittedWithoutTOCPreference +{ + // Regression guard: ids must be emitted regardless of the + // "Detect TOC token" preference state. + self.delegate.renderTOC = NO; + NSString *html = [self renderMarkdown:@"### Some Section" + withExtensions:0 + rendererFlags:0]; + XCTAssertTrue([html containsString:@"id=\"some-section\""], + @"Heading id must be emitted independent of TOC preference. Got: %@", html); +} + @end diff --git a/MacDownTests/MPQuickLookRendererTests.m b/MacDownTests/MPQuickLookRendererTests.m index 90146a02..075dee4e 100644 --- a/MacDownTests/MPQuickLookRendererTests.m +++ b/MacDownTests/MPQuickLookRendererTests.m @@ -124,8 +124,8 @@ - (void)testRenderSimpleMarkdown NSString *html = [self.renderer renderMarkdown:markdown]; XCTAssertNotNil(html, @"Should return non-nil HTML"); - XCTAssertTrue([html containsString:@"
"], - @"Should render heading as
"); + XCTAssertTrue([html containsString:@"
element"); XCTAssertTrue([html containsString:@"Hello World"], @"Should include heading text"); XCTAssertTrue([html containsString:@"
"], @@ -640,8 +640,8 @@ - (void)testRenderMarkdownFromURLWithCRLFLineEndings XCTAssertNil(error, @"Should render CRLF file without error"); XCTAssertNotNil(html, @"Should produce HTML from CRLF file"); - XCTAssertTrue([html containsString:@"
"], - @"CRLF heading should render as
"); + XCTAssertTrue([html containsString:@"
element"); XCTAssertTrue([html containsString:@"Heading"], @"Heading text should appear in output"); XCTAssertTrue([html containsString:@"
"], diff --git a/MacDownTests/MPRendererEdgeCaseTests.m b/MacDownTests/MPRendererEdgeCaseTests.m index 63a2909f..64039d9c 100644 --- a/MacDownTests/MPRendererEdgeCaseTests.m +++ b/MacDownTests/MPRendererEdgeCaseTests.m @@ -534,8 +534,8 @@ - (void)testRendererWithManyHeadings NSString *html = [self.renderer HTMLForExportWithStyles:NO highlighting:NO]; XCTAssertNotNil(html, @"Should handle many headings"); - XCTAssertTrue([html containsString:@"
"], @"Should have h1"); - XCTAssertTrue([html containsString:@"
"], @"Should have h6"); + XCTAssertTrue([html containsString:@"
"], + XCTAssertTrue([html containsString:@"
"], + XCTAssertTrue([html containsString:@"