diff --git a/src/browser/markdown.zig b/src/browser/markdown.zig index d7f833ff..5d7f3123 100644 --- a/src/browser/markdown.zig +++ b/src/browser/markdown.zig @@ -104,6 +104,10 @@ fn isVisibleElement(el: *Element) bool { }; } +fn getAnchorLabel(el: *Element) ?[]const u8 { + return el.getAttributeSafe(comptime .wrap("aria-label")) orelse el.getAttributeSafe(comptime .wrap("title")); +} + fn isAllWhitespace(text: []const u8) bool { return for (text) |c| { if (!std.ascii.isWhitespace(c)) break false; @@ -302,17 +306,20 @@ fn renderElement(el: *Element, state: *State, writer: *std.Io.Writer, page: *Pag return; }, .anchor => { - if (!hasVisibleContent(el.asNode())) return; + const has_content = hasVisibleContent(el.asNode()); + const label = getAnchorLabel(el); + const href_raw = el.getAttributeSafe(comptime .wrap("href")); + + if (!has_content and label == null and href_raw == null) return; const has_block = hasBlockDescendant(el.asNode()); - const href_raw = el.getAttributeSafe(comptime .wrap("href")); const href = if (href_raw) |h| URL.resolve(page.call_arena, page.base(), h, .{}) catch h else null; if (has_block) { try renderChildren(el.asNode(), state, writer, page); if (href) |h| { if (!state.last_char_was_newline) try writer.writeByte('\n'); - try writer.writeAll("([Link]("); + try writer.writeAll("([]("); try writer.writeAll(h); try writer.writeAll("))\n"); state.last_char_was_newline = true; @@ -323,7 +330,11 @@ fn renderElement(el: *Element, state: *State, writer: *std.Io.Writer, page: *Pag if (isStandaloneAnchor(el)) { if (!state.last_char_was_newline) try writer.writeByte('\n'); try writer.writeByte('['); - try renderChildren(el.asNode(), state, writer, page); + if (has_content) { + try renderChildren(el.asNode(), state, writer, page); + } else { + try writer.writeAll(label orelse ""); + } try writer.writeAll("]("); if (href) |h| { try writer.writeAll(h); @@ -334,7 +345,11 @@ fn renderElement(el: *Element, state: *State, writer: *std.Io.Writer, page: *Pag } try writer.writeByte('['); - try renderChildren(el.asNode(), state, writer, page); + if (has_content) { + try renderChildren(el.asNode(), state, writer, page); + } else { + try writer.writeAll(label orelse ""); + } try writer.writeAll("]("); if (href) |h| { try writer.writeAll(h); @@ -589,7 +604,7 @@ test "browser.markdown: block link" { \\### Title \\ \\Description - \\([Link](https://example.com)) + \\([](https://example.com)) \\ ); } @@ -634,7 +649,11 @@ test "browser.markdown: skip empty links" { try testMarkdownHTML( \\ \\ - , ""); + , + \\[](http://localhost/) + \\[](http://localhost/) + \\ + ); } test "browser.markdown: resolve links" { @@ -660,3 +679,17 @@ test "browser.markdown: resolve links" { \\ , aw.written()); } + +test "browser.markdown: anchor fallback label" { + try testMarkdownHTML( + \\ + , "[Discord Server](http://localhost/discord)\n"); + + try testMarkdownHTML( + \\ + , "[Search Site](http://localhost/search)\n"); + + try testMarkdownHTML( + \\ + , "[](http://localhost/no-label)\n"); +}