Skip to content

Commit

Permalink
feat(renderer): support caret in links (#996)
Browse files Browse the repository at this point in the history
Fixes #391
Fixes #682

Signed-off-by: Xavier Coulon <[email protected]>
  • Loading branch information
xcoulon authored Apr 20, 2022
1 parent 8d0abec commit 394e4e4
Show file tree
Hide file tree
Showing 5 changed files with 330 additions and 98 deletions.
262 changes: 228 additions & 34 deletions pkg/parser/link_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -270,7 +270,7 @@ a link to <{example}>.`
})

It("with text only", func() {
source := "a link to mailto:foo@bar[the foo@bar email]."
source := "a link to mailto:[email protected][the [email protected] email]."
expected := &types.Document{
Elements: []interface{}{
&types.Paragraph{
Expand All @@ -279,10 +279,10 @@ a link to <{example}>.`
&types.InlineLink{
Location: &types.Location{
Scheme: "mailto:",
Path: "foo@bar",
Path: "[email protected]",
},
Attributes: types.Attributes{
types.AttrInlineLinkText: "the foo@bar email",
types.AttrInlineLinkText: "the [email protected] email",
},
},
&types.StringElement{
Expand All @@ -296,7 +296,7 @@ a link to <{example}>.`
})

It("with text and extra attributes", func() {
source := "a link to mailto:foo@bar[the foo@bar email, foo=bar]"
source := "a link to mailto:[email protected][the [email protected] email, foo=bar]"
expected := &types.Document{
Elements: []interface{}{
&types.Paragraph{
Expand All @@ -305,10 +305,10 @@ a link to <{example}>.`
&types.InlineLink{
Location: &types.Location{
Scheme: "mailto:",
Path: "foo@bar",
Path: "[email protected]",
},
Attributes: types.Attributes{
types.AttrInlineLinkText: "the foo@bar email",
types.AttrInlineLinkText: "the [email protected] email",
"foo": "bar",
},
},
Expand Down Expand Up @@ -396,6 +396,103 @@ next lines`
Expect(ParseDocument(source)).To(MatchDocument(expected))
})

It("with text and custom target", func() {
source := `a link to https://example.com[the doc,window=read-later]`
expected := &types.Document{
Elements: []interface{}{
&types.Paragraph{
Elements: []interface{}{
&types.StringElement{Content: "a link to "},
&types.InlineLink{
Attributes: types.Attributes{
types.AttrInlineLinkText: "the doc",
types.AttrInlineLinkTarget: "read-later",
},
Location: &types.Location{
Scheme: "https://",
Path: "example.com",
},
},
},
},
},
}
Expect(ParseDocument(source)).To(MatchDocument(expected))
})

It("with text and custom target with noopener", func() {
source := `a link to https://example.com[the doc,window=read-later,opts=noopener]`
expected := &types.Document{
Elements: []interface{}{
&types.Paragraph{
Elements: []interface{}{
&types.StringElement{Content: "a link to "},
&types.InlineLink{
Attributes: types.Attributes{
types.AttrInlineLinkText: "the doc",
types.AttrInlineLinkTarget: "read-later",
types.AttrOptions: types.Options{"noopener"},
},
Location: &types.Location{
Scheme: "https://",
Path: "example.com",
},
},
},
},
},
}
Expect(ParseDocument(source)).To(MatchDocument(expected))
})

It("with text and explicit blank target", func() {
source := `a link to https://example.com[the doc,window=_blank]`
expected := &types.Document{
Elements: []interface{}{
&types.Paragraph{
Elements: []interface{}{
&types.StringElement{Content: "a link to "},
&types.InlineLink{
Attributes: types.Attributes{
types.AttrInlineLinkText: "the doc",
types.AttrInlineLinkTarget: "_blank",
},
Location: &types.Location{
Scheme: "https://",
Path: "example.com",
},
},
},
},
},
}
Expect(ParseDocument(source)).To(MatchDocument(expected))
})

It("with text and blank target short-hand", func() {
source := `a link to https://example.com[the doc^]` // the ^ character is used to define a `blank` target
expected := &types.Document{
Elements: []interface{}{
&types.Paragraph{
Elements: []interface{}{
&types.StringElement{Content: "a link to "},
&types.InlineLink{
Attributes: types.Attributes{
types.AttrInlineLinkText: "the doc",
types.AttrInlineLinkTarget: "_blank",
},
Location: &types.Location{
Scheme: "https://",
Path: "example.com",
},
},
},
},
},
}
Expect(ParseDocument(source)).To(MatchDocument(expected))
})

Context("text attribute with comma", func() {

It("only with text having comma", func() {
Expand Down Expand Up @@ -1059,16 +1156,16 @@ a link to {scheme}://{path} and https://foo.com`
Context("relative links", func() {

It("to doc without text", func() {
source := "a link to link:foo.adoc[]"
source := "a link to link:https://example.com[]"
expected := &types.Document{
Elements: []interface{}{
&types.Paragraph{
Elements: []interface{}{
&types.StringElement{Content: "a link to "},
&types.InlineLink{
Location: &types.Location{
Scheme: "",
Path: "foo.adoc",
Scheme: "https://",
Path: "example.com",
},
},
},
Expand All @@ -1079,19 +1176,19 @@ a link to {scheme}://{path} and https://foo.com`
})

It("to doc with text", func() {
source := "a link to link:foo.adoc[foo doc]"
source := "a link to link:https://example.com[the doc]"
expected := &types.Document{
Elements: []interface{}{
&types.Paragraph{
Elements: []interface{}{
&types.StringElement{Content: "a link to "},
&types.InlineLink{
Location: &types.Location{
Scheme: "",
Path: "foo.adoc",
Scheme: "https://",
Path: "example.com",
},
Attributes: types.Attributes{
types.AttrInlineLinkText: "foo doc",
types.AttrInlineLinkText: "the doc",
},
},
},
Expand All @@ -1102,7 +1199,7 @@ a link to {scheme}://{path} and https://foo.com`
})

It("to external URL with text only", func() {
source := "a link to link:https://example.com[foo doc]"
source := "a link to link:https://example.com[the doc]"
expected := &types.Document{
Elements: []interface{}{
&types.Paragraph{
Expand All @@ -1114,7 +1211,7 @@ a link to {scheme}://{path} and https://foo.com`
Path: "example.com",
},
Attributes: types.Attributes{
types.AttrInlineLinkText: "foo doc",
types.AttrInlineLinkText: "the doc",
},
},
},
Expand All @@ -1125,7 +1222,7 @@ a link to {scheme}://{path} and https://foo.com`
})

It("to external URL with text and extra attributes", func() {
source := "a link to link:https://example.com[foo doc, foo=bar]"
source := "a link to link:https://example.com[the doc, foo=bar]"
expected := &types.Document{
Elements: []interface{}{
&types.Paragraph{
Expand All @@ -1137,7 +1234,7 @@ a link to {scheme}://{path} and https://foo.com`
Path: "example.com",
},
Attributes: types.Attributes{
types.AttrInlineLinkText: "foo doc",
types.AttrInlineLinkText: "the doc",
"foo": "bar",
},
},
Expand Down Expand Up @@ -1171,21 +1268,22 @@ a link to {scheme}://{path} and https://foo.com`
Expect(ParseDocument(source)).To(MatchDocument(expected))
})

It("invalid syntax", func() {
source := "a link to link:foo.adoc"
expected := &types.Document{
Elements: []interface{}{
&types.Paragraph{
Elements: []interface{}{
&types.StringElement{
Content: "a link to link:foo.adoc",
},
},
},
},
}
Expect(ParseDocument(source)).To(MatchDocument(expected))
})
// skipped: should be consistent with other syntaxes, and allow for skipped/missing attributes?
// It("invalid syntax", func() {
// source := "a link to link:https://example.com"
// expected := &types.Document{
// Elements: []interface{}{
// &types.Paragraph{
// Elements: []interface{}{
// &types.StringElement{
// Content: "a link to link:https://example.com",
// },
// },
// },
// },
// }
// Expect(ParseDocument(source)).To(MatchDocument(expected))
// })

It("with quoted text attribute", func() {
source := "link:/[a _a_ b *b* c `c`]"
Expand Down Expand Up @@ -1400,7 +1498,7 @@ a link to {scheme}:{path}[] and https://foo.com`
})

It("with line breaks in attributes", func() {
source := `link:x[
source := `link:example.com[
title]`
expected := &types.Document{
Elements: []interface{}{
Expand All @@ -1411,7 +1509,80 @@ title]`
types.AttrInlineLinkText: "title",
},
Location: &types.Location{
Path: "x",
Path: "example.com",
},
},
},
},
},
}
Expect(ParseDocument(source)).To(MatchDocument(expected))
})

It("with text and custom target", func() {
source := `a link to link:https://example.com[the doc,window=read-later]`
expected := &types.Document{
Elements: []interface{}{
&types.Paragraph{
Elements: []interface{}{
&types.StringElement{Content: "a link to "},
&types.InlineLink{
Attributes: types.Attributes{
types.AttrInlineLinkText: "the doc",
types.AttrInlineLinkTarget: "read-later",
},
Location: &types.Location{
Scheme: "https://",
Path: "example.com",
},
},
},
},
},
}
Expect(ParseDocument(source)).To(MatchDocument(expected))
})

It("with text and custom target with noopener", func() {
source := `a link to link:https://example.com[the doc,window=read-later,opts=noopener]`
expected := &types.Document{
Elements: []interface{}{
&types.Paragraph{
Elements: []interface{}{
&types.StringElement{Content: "a link to "},
&types.InlineLink{
Attributes: types.Attributes{
types.AttrInlineLinkText: "the doc",
types.AttrInlineLinkTarget: "read-later",
types.AttrOptions: types.Options{"noopener"},
},
Location: &types.Location{
Scheme: "https://",
Path: "example.com",
},
},
},
},
},
}
Expect(ParseDocument(source)).To(MatchDocument(expected))
})

It("with text and explicit blank target", func() {
source := `a link to link:https://example.com[the doc,window=_blank]`
expected := &types.Document{
Elements: []interface{}{
&types.Paragraph{
Elements: []interface{}{
&types.StringElement{Content: "a link to "},
&types.InlineLink{
Attributes: types.Attributes{
types.AttrInlineLinkText: "the doc",
types.AttrInlineLinkTarget: "_blank",
},
Location: &types.Location{
Scheme: "https://",
Path: "example.com",
},
},
},
Expand All @@ -1421,6 +1592,29 @@ title]`
Expect(ParseDocument(source)).To(MatchDocument(expected))
})

It("with text and blank target short-hand", func() {
source := `a link to link:https://example.com[the doc^]` // the ^ character is used to define a `blank` target
expected := &types.Document{
Elements: []interface{}{
&types.Paragraph{
Elements: []interface{}{
&types.StringElement{Content: "a link to "},
&types.InlineLink{
Attributes: types.Attributes{
types.AttrInlineLinkText: "the doc",
types.AttrInlineLinkTarget: "_blank",
},
Location: &types.Location{
Scheme: "https://",
Path: "example.com",
},
},
},
},
},
}
Expect(ParseDocument(source)).To(MatchDocument(expected))
})
Context("text attribute with comma", func() {

It("with text having comma", func() {
Expand Down
2 changes: 1 addition & 1 deletion pkg/renderer/sgml/html5/link.go
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
package html5

const (
linkTmpl = `<a{{ if .ID }} id="{{ .ID }}"{{ end }}{{ if .URL }} href="{{ escape .URL }}"{{ end }}{{if .Class}} class="{{ .Class }}"{{ end }}{{if .Target}} target="{{ .Target }}"{{ end }}>{{ .Text }}</a>`
linkTmpl = `<a{{ if .ID }} id="{{ .ID }}"{{ end }}{{ if .URL }} href="{{ escape .URL }}"{{ end }}{{if .Class}} class="{{ .Class }}"{{ end }}{{if .Target}} target="{{ .Target }}"{{ end }}{{ if .NoOpener}} rel="noopener"{{ end }}>{{ .Text }}</a>`
)
Loading

0 comments on commit 394e4e4

Please sign in to comment.