feat(render): autolink https:// URLs in BlockNote inline text
Plain-text nodes inside paragraphs, headings, lists and table cells now auto-wrap any https:// URL in <a href="..." rel="noopener">URL</a>. - Bare domains, www. and http:// URLs are intentionally not linked - Suppressed inside explicit `link` inline nodes (no nested anchors) - Suppressed inside `code`-styled spans (URL stays literal) - Trailing sentence punctuation (.,;:!?'") is excluded from the linked URL - Closing parens/brackets/braces are kept inside the URL only when balanced with an opener (so Wikipedia-style _(bar) is preserved but `(see https://x.com)` doesn't eat the trailing paren) - Bold/italic/color style wrappers compose around the anchor `renderInlineContent` gains an `insideLink bool` parameter; all existing call sites pass `false`, and the `link` branch recurses with `true`. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
7eb3e27053
commit
245e38dc95
@ -40,7 +40,7 @@ func renderBlocks(ctx context.Context, blocks []map[string]any) string {
|
||||
content := inlineContentFromRaw(item["content"])
|
||||
childrenHTML := renderChildren(ctx, item["children"])
|
||||
sb.WriteString("<li>")
|
||||
sb.WriteString(renderInlineContent(content))
|
||||
sb.WriteString(renderInlineContent(content, false))
|
||||
if childrenHTML != "" {
|
||||
sb.WriteString(childrenHTML)
|
||||
}
|
||||
@ -194,7 +194,7 @@ func renderBlock(ctx context.Context, block map[string]any) string {
|
||||
classNames += " " + alignClass
|
||||
}
|
||||
fmt.Fprintf(&sb, "<p class=\"%s\">", classNames)
|
||||
sb.WriteString(renderInlineContent(content))
|
||||
sb.WriteString(renderInlineContent(content, false))
|
||||
if childrenHTML != "" {
|
||||
sb.WriteString(childrenHTML)
|
||||
}
|
||||
@ -218,7 +218,7 @@ func renderBlock(ctx context.Context, block map[string]any) string {
|
||||
classNames += " " + alignClass
|
||||
}
|
||||
fmt.Fprintf(&sb, "<h%d class=\"%s\">", level, classNames)
|
||||
sb.WriteString(renderInlineContent(content))
|
||||
sb.WriteString(renderInlineContent(content, false))
|
||||
if childrenHTML != "" {
|
||||
sb.WriteString(childrenHTML)
|
||||
}
|
||||
@ -230,7 +230,7 @@ func renderBlock(ctx context.Context, block map[string]any) string {
|
||||
classNames += " " + alignClass
|
||||
}
|
||||
fmt.Fprintf(&sb, "<blockquote class=\"%s\">", classNames)
|
||||
sb.WriteString(renderInlineContent(content))
|
||||
sb.WriteString(renderInlineContent(content, false))
|
||||
if childrenHTML != "" {
|
||||
sb.WriteString(childrenHTML)
|
||||
}
|
||||
@ -246,7 +246,7 @@ func renderBlock(ctx context.Context, block map[string]any) string {
|
||||
checkedAttr = " checked"
|
||||
}
|
||||
fmt.Fprintf(&sb, `<div class="check-list-item my-2 flex items-start gap-2"><input type="checkbox" disabled%s><span>`, checkedAttr)
|
||||
sb.WriteString(renderInlineContent(content))
|
||||
sb.WriteString(renderInlineContent(content, false))
|
||||
sb.WriteString("</span>")
|
||||
if childrenHTML != "" {
|
||||
fmt.Fprintf(&sb, `<div class="pl-6">%s</div>`, childrenHTML)
|
||||
@ -260,7 +260,7 @@ func renderBlock(ctx context.Context, block map[string]any) string {
|
||||
}
|
||||
fmt.Fprintf(&sb, `<details class="my-4"%s>`, openAttr)
|
||||
sb.WriteString(`<summary class="cursor-pointer font-medium">`)
|
||||
sb.WriteString(renderInlineContent(content))
|
||||
sb.WriteString(renderInlineContent(content, false))
|
||||
sb.WriteString("</summary>")
|
||||
if childrenHTML != "" {
|
||||
fmt.Fprintf(&sb, `<div class="pl-6 mt-2">%s</div>`, childrenHTML)
|
||||
@ -273,7 +273,7 @@ func renderBlock(ctx context.Context, block map[string]any) string {
|
||||
lang = l
|
||||
}
|
||||
fmt.Fprintf(&sb, `<pre class="my-4"><code class="language-%s">`, html.EscapeString(lang))
|
||||
sb.WriteString(renderInlineContent(content))
|
||||
sb.WriteString(renderInlineContent(content, false))
|
||||
sb.WriteString("</code></pre>\n")
|
||||
|
||||
case "image":
|
||||
@ -375,7 +375,7 @@ func renderBlock(ctx context.Context, block map[string]any) string {
|
||||
cellClass = "px-4 py-3 text-left text-sm font-semibold"
|
||||
}
|
||||
fmt.Fprintf(&sb, "<%s class=\"%s\">", cellTag, cellClass)
|
||||
sb.WriteString(renderInlineContent(inlineContentFromRaw(cell)))
|
||||
sb.WriteString(renderInlineContent(inlineContentFromRaw(cell), false))
|
||||
fmt.Fprintf(&sb, "</%s>", cellTag)
|
||||
}
|
||||
}
|
||||
@ -422,7 +422,7 @@ func renderBlock(ctx context.Context, block map[string]any) string {
|
||||
sb.WriteString("<div class=\"bn-statement\">\n")
|
||||
if len(content) > 0 {
|
||||
sb.WriteString("<p>")
|
||||
sb.WriteString(renderInlineContent(content))
|
||||
sb.WriteString(renderInlineContent(content, false))
|
||||
sb.WriteString("</p>\n")
|
||||
}
|
||||
if childrenHTML != "" {
|
||||
@ -462,7 +462,7 @@ func renderBlock(ctx context.Context, block map[string]any) string {
|
||||
default:
|
||||
if len(content) > 0 || childrenHTML != "" {
|
||||
sb.WriteString("<div>")
|
||||
sb.WriteString(renderInlineContent(content))
|
||||
sb.WriteString(renderInlineContent(content, false))
|
||||
if childrenHTML != "" {
|
||||
sb.WriteString(childrenHTML)
|
||||
}
|
||||
@ -481,7 +481,7 @@ func renderChildren(ctx context.Context, children any) string {
|
||||
return renderBlocks(ctx, blocks)
|
||||
}
|
||||
|
||||
func renderInlineContent(content []map[string]any) string {
|
||||
func renderInlineContent(content []map[string]any, insideLink bool) string {
|
||||
var sb strings.Builder
|
||||
for _, itemMap := range content {
|
||||
itemType, _ := itemMap["type"].(string)
|
||||
@ -490,7 +490,18 @@ func renderInlineContent(content []map[string]any) string {
|
||||
|
||||
switch itemType {
|
||||
case "text":
|
||||
rendered := html.EscapeString(text)
|
||||
isCode := false
|
||||
if styles != nil {
|
||||
if c, ok := styles["code"].(bool); ok && c {
|
||||
isCode = true
|
||||
}
|
||||
}
|
||||
var rendered string
|
||||
if insideLink || isCode {
|
||||
rendered = html.EscapeString(text)
|
||||
} else {
|
||||
rendered = autolinkText(text)
|
||||
}
|
||||
if styles != nil {
|
||||
if bold, ok := styles["bold"].(bool); ok && bold {
|
||||
rendered = "<strong>" + rendered + "</strong>"
|
||||
@ -507,7 +518,7 @@ func renderInlineContent(content []map[string]any) string {
|
||||
if strike, ok := styles["strikethrough"].(bool); ok && strike {
|
||||
rendered = "<s>" + rendered + "</s>"
|
||||
}
|
||||
if code, ok := styles["code"].(bool); ok && code {
|
||||
if isCode {
|
||||
rendered = "<code>" + rendered + "</code>"
|
||||
}
|
||||
|
||||
@ -533,11 +544,11 @@ func renderInlineContent(content []map[string]any) string {
|
||||
href, _ := itemMap["href"].(string)
|
||||
linkContent := inlineContentFromRaw(itemMap["content"])
|
||||
if href == "" {
|
||||
sb.WriteString(renderInlineContent(linkContent))
|
||||
sb.WriteString(renderInlineContent(linkContent, insideLink))
|
||||
break
|
||||
}
|
||||
fmt.Fprintf(&sb, `<a href="%s">`, html.EscapeString(href))
|
||||
sb.WriteString(renderInlineContent(linkContent))
|
||||
sb.WriteString(renderInlineContent(linkContent, true))
|
||||
sb.WriteString("</a>")
|
||||
|
||||
case "hardBreak":
|
||||
@ -545,13 +556,92 @@ func renderInlineContent(content []map[string]any) string {
|
||||
|
||||
default:
|
||||
if text != "" {
|
||||
if insideLink {
|
||||
sb.WriteString(html.EscapeString(text))
|
||||
} else {
|
||||
sb.WriteString(autolinkText(text))
|
||||
}
|
||||
break
|
||||
}
|
||||
if rawContent, ok := itemMap["content"]; ok {
|
||||
sb.WriteString(renderInlineContent(inlineContentFromRaw(rawContent)))
|
||||
sb.WriteString(renderInlineContent(inlineContentFromRaw(rawContent), insideLink))
|
||||
}
|
||||
}
|
||||
}
|
||||
return sb.String()
|
||||
}
|
||||
|
||||
// autolinkText escapes plain text for HTML output and wraps any https:// URL
|
||||
// substring in <a href="..." rel="noopener">URL</a>. Bare domains, www. and
|
||||
// http:// URLs are intentionally not auto-linked.
|
||||
//
|
||||
// Trailing sentence punctuation (.,;:!?'") is excluded from the linked URL.
|
||||
// Closing parens, brackets and braces are kept inside the URL only when
|
||||
// balanced with an opener inside the URL itself — so
|
||||
// "(see https://example.com)" links only "https://example.com"
|
||||
// "https://en.wikipedia.org/wiki/Foo_(bar)" keeps the trailing paren.
|
||||
func autolinkText(text string) string {
|
||||
const scheme = "https://"
|
||||
var sb strings.Builder
|
||||
rest := text
|
||||
for {
|
||||
idx := strings.Index(rest, scheme)
|
||||
if idx < 0 {
|
||||
sb.WriteString(html.EscapeString(rest))
|
||||
return sb.String()
|
||||
}
|
||||
sb.WriteString(html.EscapeString(rest[:idx]))
|
||||
|
||||
// Scan forward until whitespace or a URL-terminating delimiter.
|
||||
end := idx + len(scheme)
|
||||
for end < len(rest) {
|
||||
c := rest[end]
|
||||
if c == ' ' || c == '\t' || c == '\n' || c == '\r' || c == '<' || c == '>' || c == '"' || c == '\'' {
|
||||
break
|
||||
}
|
||||
end++
|
||||
}
|
||||
// Walk back over trailing characters that should sit outside the link,
|
||||
// preserving paren/bracket/brace balance.
|
||||
urlEnd := end
|
||||
for urlEnd > idx+len(scheme) {
|
||||
last := rest[urlEnd-1]
|
||||
if last == '.' || last == ',' || last == ';' || last == ':' || last == '!' || last == '?' || last == '"' || last == '\'' {
|
||||
urlEnd--
|
||||
continue
|
||||
}
|
||||
candidate := rest[idx:urlEnd]
|
||||
if last == ')' && strings.Count(candidate, ")") > strings.Count(candidate, "(") {
|
||||
urlEnd--
|
||||
continue
|
||||
}
|
||||
if last == ']' && strings.Count(candidate, "]") > strings.Count(candidate, "[") {
|
||||
urlEnd--
|
||||
continue
|
||||
}
|
||||
if last == '}' && strings.Count(candidate, "}") > strings.Count(candidate, "{") {
|
||||
urlEnd--
|
||||
continue
|
||||
}
|
||||
break
|
||||
}
|
||||
url := rest[idx:urlEnd]
|
||||
tail := rest[urlEnd:end]
|
||||
|
||||
if url == scheme {
|
||||
sb.WriteString(html.EscapeString(rest[idx:end]))
|
||||
} else {
|
||||
escaped := html.EscapeString(url)
|
||||
sb.WriteString(`<a href="`)
|
||||
sb.WriteString(escaped)
|
||||
sb.WriteString(`" rel="noopener">`)
|
||||
sb.WriteString(escaped)
|
||||
sb.WriteString(`</a>`)
|
||||
if tail != "" {
|
||||
sb.WriteString(html.EscapeString(tail))
|
||||
}
|
||||
}
|
||||
|
||||
rest = rest[end:]
|
||||
}
|
||||
}
|
||||
|
||||
229
render/blocknote_autolink_test.go
Normal file
229
render/blocknote_autolink_test.go
Normal file
@ -0,0 +1,229 @@
|
||||
package render
|
||||
|
||||
import (
|
||||
"context"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestAutolinkText_PlainTextPassthrough(t *testing.T) {
|
||||
got := autolinkText("just some prose with no link")
|
||||
want := "just some prose with no link"
|
||||
if got != want {
|
||||
t.Errorf("got %q, want %q", got, want)
|
||||
}
|
||||
}
|
||||
|
||||
func TestAutolinkText_HTMLMetacharactersEscaped(t *testing.T) {
|
||||
got := autolinkText("a < b && c > d")
|
||||
if !strings.Contains(got, "<") || !strings.Contains(got, ">") || !strings.Contains(got, "&") {
|
||||
t.Errorf("expected escaped metacharacters, got %q", got)
|
||||
}
|
||||
if strings.Contains(got, "<a ") {
|
||||
t.Errorf("did not expect <a> tag in plain prose: %q", got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestAutolinkText_SingleHTTPSURL(t *testing.T) {
|
||||
got := autolinkText("see https://example.com for more")
|
||||
want := `see <a href="https://example.com" rel="noopener">https://example.com</a> for more`
|
||||
if got != want {
|
||||
t.Errorf("got %q, want %q", got, want)
|
||||
}
|
||||
}
|
||||
|
||||
func TestAutolinkText_HTTPNotLinked(t *testing.T) {
|
||||
got := autolinkText("see http://example.com for more")
|
||||
if strings.Contains(got, "<a ") {
|
||||
t.Errorf("http:// should not be auto-linked, got %q", got)
|
||||
}
|
||||
if !strings.Contains(got, "http://example.com") {
|
||||
t.Errorf("expected URL to appear as plain escaped text, got %q", got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestAutolinkText_BareDomainNotLinked(t *testing.T) {
|
||||
got := autolinkText("see example.com for more")
|
||||
if strings.Contains(got, "<a ") {
|
||||
t.Errorf("bare domain should not be auto-linked, got %q", got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestAutolinkText_TrailingPeriodOutsideAnchor(t *testing.T) {
|
||||
got := autolinkText("visit https://example.com.")
|
||||
want := `visit <a href="https://example.com" rel="noopener">https://example.com</a>.`
|
||||
if got != want {
|
||||
t.Errorf("got %q, want %q", got, want)
|
||||
}
|
||||
}
|
||||
|
||||
func TestAutolinkText_TrailingPunctuationStripped(t *testing.T) {
|
||||
cases := []struct {
|
||||
input string
|
||||
wantTail string
|
||||
}{
|
||||
{"check https://example.com,", ","},
|
||||
{"is it https://example.com?", "?"},
|
||||
{"wow https://example.com!", "!"},
|
||||
{"so https://example.com;", ";"},
|
||||
{"foo https://example.com:", ":"},
|
||||
{`he said "https://example.com"`, `"`},
|
||||
{"foo https://example.com'", "'"},
|
||||
}
|
||||
for _, tc := range cases {
|
||||
got := autolinkText(tc.input)
|
||||
if !strings.Contains(got, `<a href="https://example.com" rel="noopener">https://example.com</a>`) {
|
||||
t.Errorf("input %q: expected URL link without trailing punctuation, got %q", tc.input, got)
|
||||
}
|
||||
if !strings.HasSuffix(got, tc.wantTail) {
|
||||
t.Errorf("input %q: expected suffix %q, got %q", tc.input, tc.wantTail, got)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestAutolinkText_ClosingParenOutsideAnchorWhenUnbalanced(t *testing.T) {
|
||||
got := autolinkText("(see https://example.com)")
|
||||
want := `(see <a href="https://example.com" rel="noopener">https://example.com</a>)`
|
||||
if got != want {
|
||||
t.Errorf("got %q, want %q", got, want)
|
||||
}
|
||||
}
|
||||
|
||||
func TestAutolinkText_ClosingParenKeptWhenBalanced(t *testing.T) {
|
||||
got := autolinkText("see https://en.wikipedia.org/wiki/Foo_(bar) page")
|
||||
if !strings.Contains(got, `<a href="https://en.wikipedia.org/wiki/Foo_(bar)" rel="noopener">https://en.wikipedia.org/wiki/Foo_(bar)</a>`) {
|
||||
t.Errorf("expected paren kept inside anchor when balanced, got %q", got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestAutolinkText_MultipleURLs(t *testing.T) {
|
||||
got := autolinkText("first https://a.com then https://b.com end")
|
||||
if strings.Count(got, "<a ") != 2 {
|
||||
t.Errorf("expected 2 anchors, got %q", got)
|
||||
}
|
||||
if !strings.Contains(got, `href="https://a.com"`) || !strings.Contains(got, `href="https://b.com"`) {
|
||||
t.Errorf("expected both URLs linked, got %q", got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestAutolinkText_URLWithQueryStringContainingAmpersand(t *testing.T) {
|
||||
got := autolinkText("see https://example.com/?a=1&b=2 ok")
|
||||
// `&` should be escaped to `&` in both href and text
|
||||
if !strings.Contains(got, `href="https://example.com/?a=1&b=2"`) {
|
||||
t.Errorf("expected escaped ampersand in href, got %q", got)
|
||||
}
|
||||
if !strings.Contains(got, "rel=\"noopener\"") {
|
||||
t.Errorf("expected rel=noopener, got %q", got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestAutolinkText_URLAtStringStart(t *testing.T) {
|
||||
got := autolinkText("https://example.com is great")
|
||||
if !strings.HasPrefix(got, `<a href="https://example.com" rel="noopener">https://example.com</a>`) {
|
||||
t.Errorf("expected anchor at start, got %q", got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestAutolinkText_URLAtStringEnd(t *testing.T) {
|
||||
got := autolinkText("checkout https://example.com")
|
||||
want := `checkout <a href="https://example.com" rel="noopener">https://example.com</a>`
|
||||
if got != want {
|
||||
t.Errorf("got %q, want %q", got, want)
|
||||
}
|
||||
}
|
||||
|
||||
// Integration tests: confirm autolinking flows through the public renderer
|
||||
|
||||
func TestBlockNoteToHTML_AutolinksURLInParagraph(t *testing.T) {
|
||||
doc := map[string]any{
|
||||
"blocks": []any{
|
||||
map[string]any{
|
||||
"type": "paragraph",
|
||||
"content": []any{
|
||||
map[string]any{"type": "text", "text": "visit https://example.com today"},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
html := BlockNoteToHTML(context.Background(), doc)
|
||||
if !strings.Contains(html, `<a href="https://example.com" rel="noopener">https://example.com</a>`) {
|
||||
t.Errorf("expected autolinked URL in paragraph, got %s", html)
|
||||
}
|
||||
}
|
||||
|
||||
func TestBlockNoteToHTML_NoNestedAnchorInsideExplicitLink(t *testing.T) {
|
||||
doc := map[string]any{
|
||||
"blocks": []any{
|
||||
map[string]any{
|
||||
"type": "paragraph",
|
||||
"content": []any{
|
||||
map[string]any{
|
||||
"type": "link",
|
||||
"href": "https://short.url/",
|
||||
"content": []any{
|
||||
map[string]any{"type": "text", "text": "see https://full-url-text.com"},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
html := BlockNoteToHTML(context.Background(), doc)
|
||||
// Outer link should exist
|
||||
if !strings.Contains(html, `<a href="https://short.url/">`) {
|
||||
t.Errorf("expected outer explicit link, got %s", html)
|
||||
}
|
||||
// Inner text must NOT become a nested anchor
|
||||
if strings.Contains(html, `<a href="https://full-url-text.com"`) {
|
||||
t.Errorf("did not expect nested anchor inside link, got %s", html)
|
||||
}
|
||||
// Inner URL should appear as escaped plain text
|
||||
if !strings.Contains(html, "https://full-url-text.com") {
|
||||
t.Errorf("expected inner URL as plain text, got %s", html)
|
||||
}
|
||||
}
|
||||
|
||||
func TestBlockNoteToHTML_NoAutolinkInsideCodeStyle(t *testing.T) {
|
||||
doc := map[string]any{
|
||||
"blocks": []any{
|
||||
map[string]any{
|
||||
"type": "paragraph",
|
||||
"content": []any{
|
||||
map[string]any{
|
||||
"type": "text",
|
||||
"text": "https://example.com",
|
||||
"styles": map[string]any{"code": true},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
html := BlockNoteToHTML(context.Background(), doc)
|
||||
if strings.Contains(html, "<a ") {
|
||||
t.Errorf("did not expect anchor inside code-styled text, got %s", html)
|
||||
}
|
||||
if !strings.Contains(html, "<code>") || !strings.Contains(html, "https://example.com") {
|
||||
t.Errorf("expected code-wrapped literal URL, got %s", html)
|
||||
}
|
||||
}
|
||||
|
||||
func TestBlockNoteToHTML_BoldWrapsAutolink(t *testing.T) {
|
||||
doc := map[string]any{
|
||||
"blocks": []any{
|
||||
map[string]any{
|
||||
"type": "paragraph",
|
||||
"content": []any{
|
||||
map[string]any{
|
||||
"type": "text",
|
||||
"text": "https://example.com",
|
||||
"styles": map[string]any{"bold": true},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
html := BlockNoteToHTML(context.Background(), doc)
|
||||
if !strings.Contains(html, `<strong><a href="https://example.com" rel="noopener">https://example.com</a></strong>`) {
|
||||
t.Errorf("expected bold-wrapped autolink, got %s", html)
|
||||
}
|
||||
}
|
||||
@ -346,7 +346,7 @@ func TestRenderInlineContentWithTextColor(t *testing.T) {
|
||||
},
|
||||
},
|
||||
}
|
||||
html := renderInlineContent(content)
|
||||
html := renderInlineContent(content, false)
|
||||
|
||||
if !strings.Contains(html, `class="text-primary"`) {
|
||||
t.Errorf("expected text color class: %s", html)
|
||||
@ -363,7 +363,7 @@ func TestRenderInlineContentWithBackgroundColor(t *testing.T) {
|
||||
},
|
||||
},
|
||||
}
|
||||
html := renderInlineContent(content)
|
||||
html := renderInlineContent(content, false)
|
||||
|
||||
if !strings.Contains(html, `bg-[#ffcc00]`) {
|
||||
t.Errorf("expected background color class: %s", html)
|
||||
@ -381,7 +381,7 @@ func TestRenderInlineContentWithBothColors(t *testing.T) {
|
||||
},
|
||||
},
|
||||
}
|
||||
html := renderInlineContent(content)
|
||||
html := renderInlineContent(content, false)
|
||||
|
||||
if !strings.Contains(html, `text-foreground`) {
|
||||
t.Errorf("expected text color class: %s", html)
|
||||
@ -401,7 +401,7 @@ func TestRenderInlineContentWithDefaultColor(t *testing.T) {
|
||||
},
|
||||
},
|
||||
}
|
||||
html := renderInlineContent(content)
|
||||
html := renderInlineContent(content, false)
|
||||
|
||||
if strings.Contains(html, "class=") {
|
||||
t.Errorf("default color should not add class: %s", html)
|
||||
@ -419,7 +419,7 @@ func TestRenderInlineContentWithColorsAndOtherStyles(t *testing.T) {
|
||||
},
|
||||
},
|
||||
}
|
||||
html := renderInlineContent(content)
|
||||
html := renderInlineContent(content, false)
|
||||
|
||||
if !strings.Contains(html, "<strong>") {
|
||||
t.Errorf("expected bold tag: %s", html)
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user