mirror of
https://github.com/osmarks/mycorrhiza.git
synced 2025-03-06 11:38:19 +00:00
Add inline links
This commit is contained in:
parent
1fbbf81794
commit
3c6e2aa16f
@ -127,7 +127,7 @@ func (img Img) ToHtml() (html string) {
|
|||||||
if i > 0 {
|
if i > 0 {
|
||||||
html += `<br>`
|
html += `<br>`
|
||||||
}
|
}
|
||||||
html += ParagraphToHtml(line)
|
html += ParagraphToHtml(img.hyphaName, line)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
html += `</figcaption>`
|
html += `</figcaption>`
|
||||||
|
@ -12,14 +12,14 @@ func TestParseStartOfEntry(t *testing.T) {
|
|||||||
entry imgEntry
|
entry imgEntry
|
||||||
followedByDesc bool
|
followedByDesc bool
|
||||||
}{
|
}{
|
||||||
{"apple", imgEntry{"apple", "", "", ""}, false},
|
{"apple", imgEntry{"/binary/apple", "", "", ""}, false},
|
||||||
{"pear:", imgEntry{"pear", "", "", ""}, false},
|
{"pear|", imgEntry{"/binary/pear", "", "", ""}, false},
|
||||||
{"яблоко: 30*60", imgEntry{"яблоко", "30", "60", ""}, false},
|
{"яблоко| 30*60", imgEntry{"/binary/яблоко", "30", "60", ""}, false},
|
||||||
{"груша : 65 ", imgEntry{"груша", "65", "", ""}, false},
|
{"груша | 65 ", imgEntry{"/binary/груша", "65", "", ""}, false},
|
||||||
{"жеронимо : 30 { full desc }", imgEntry{"жеронимо", "30", "", " full desc "}, false},
|
{"жеронимо | 30 { full desc }", imgEntry{"/binary/жеронимо", "30", "", " full desc "}, false},
|
||||||
{"жорно жованна : *5555 {partial description", imgEntry{"жорно_жованна", "", "5555", "partial description"}, true},
|
{"жорно жованна | *5555 {partial description", imgEntry{"/binary/жорно_жованна", "", "5555", "partial description"}, true},
|
||||||
{"иноске : {full}", imgEntry{"иноске", "", "", "full"}, false},
|
{"иноске | {full}", imgEntry{"/binary/иноске", "", "", "full"}, false},
|
||||||
{"j:{partial", imgEntry{"j", "", "", "partial"}, true},
|
{"j|{partial", imgEntry{"/binary/j", "", "", "partial"}, true},
|
||||||
}
|
}
|
||||||
for _, triplet := range tests {
|
for _, triplet := range tests {
|
||||||
entry, followedByDesc := img.parseStartOfEntry(triplet.line)
|
entry, followedByDesc := img.parseStartOfEntry(triplet.line)
|
||||||
|
@ -3,7 +3,6 @@ package markup
|
|||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"html"
|
"html"
|
||||||
"path"
|
|
||||||
"strings"
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -31,50 +30,6 @@ type Line struct {
|
|||||||
contents interface{}
|
contents interface{}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Parse markup line starting with "=>" according to wikilink rules.
|
|
||||||
// See http://localhost:1737/page/wikilink
|
|
||||||
func wikilink(src string, state *GemLexerState) (href, text, class string) {
|
|
||||||
src = strings.TrimSpace(remover("=>")(src))
|
|
||||||
if src == "" {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
// Href is text after => till first whitespace
|
|
||||||
href = strings.Fields(src)[0]
|
|
||||||
// Text is everything after whitespace.
|
|
||||||
// If there's no text, make it same as href
|
|
||||||
if text = strings.TrimPrefix(src, href); text == "" {
|
|
||||||
text = href
|
|
||||||
}
|
|
||||||
|
|
||||||
class = "wikilink_internal"
|
|
||||||
|
|
||||||
switch {
|
|
||||||
case strings.HasPrefix(href, "./"):
|
|
||||||
hyphaName := canonicalName(path.Join(
|
|
||||||
state.name, strings.TrimPrefix(href, "./")))
|
|
||||||
if !HyphaExists(hyphaName) {
|
|
||||||
class += " wikilink_new"
|
|
||||||
}
|
|
||||||
href = path.Join("/page", hyphaName)
|
|
||||||
case strings.HasPrefix(href, "../"):
|
|
||||||
hyphaName := canonicalName(path.Join(
|
|
||||||
path.Dir(state.name), strings.TrimPrefix(href, "../")))
|
|
||||||
if !HyphaExists(hyphaName) {
|
|
||||||
class += " wikilink_new"
|
|
||||||
}
|
|
||||||
href = path.Join("/page", hyphaName)
|
|
||||||
case strings.HasPrefix(href, "/"):
|
|
||||||
case strings.ContainsRune(href, ':'):
|
|
||||||
class = "wikilink_external"
|
|
||||||
default:
|
|
||||||
if !HyphaExists(canonicalName(href)) {
|
|
||||||
class += " wikilink_new"
|
|
||||||
}
|
|
||||||
href = path.Join("/page", href)
|
|
||||||
}
|
|
||||||
return href, strings.TrimSpace(text), class
|
|
||||||
}
|
|
||||||
|
|
||||||
func lex(name, content string) (ast []Line) {
|
func lex(name, content string) (ast []Line) {
|
||||||
var state = GemLexerState{name: name}
|
var state = GemLexerState{name: name}
|
||||||
|
|
||||||
@ -141,7 +96,7 @@ preformattedState:
|
|||||||
listState:
|
listState:
|
||||||
switch {
|
switch {
|
||||||
case startsWith("* "):
|
case startsWith("* "):
|
||||||
state.buf += fmt.Sprintf("\t<li>%s</li>\n", ParagraphToHtml(line[2:]))
|
state.buf += fmt.Sprintf("\t<li>%s</li>\n", ParagraphToHtml(state.name, line[2:]))
|
||||||
case startsWith("```"):
|
case startsWith("```"):
|
||||||
state.where = "pre"
|
state.where = "pre"
|
||||||
addLine(state.buf + "</ul>")
|
addLine(state.buf + "</ul>")
|
||||||
@ -157,7 +112,7 @@ listState:
|
|||||||
numberState:
|
numberState:
|
||||||
switch {
|
switch {
|
||||||
case startsWith("*. "):
|
case startsWith("*. "):
|
||||||
state.buf += fmt.Sprintf("\t<li>%s</li>\n", ParagraphToHtml(line[3:]))
|
state.buf += fmt.Sprintf("\t<li>%s</li>\n", ParagraphToHtml(state.name, line[3:]))
|
||||||
case startsWith("```"):
|
case startsWith("```"):
|
||||||
state.where = "pre"
|
state.where = "pre"
|
||||||
addLine(state.buf + "</ol>")
|
addLine(state.buf + "</ol>")
|
||||||
@ -209,9 +164,9 @@ normalState:
|
|||||||
addLine(fmt.Sprintf(
|
addLine(fmt.Sprintf(
|
||||||
"<blockquote id='%d'>%s</blockquote>", state.id, remover(">")(line)))
|
"<blockquote id='%d'>%s</blockquote>", state.id, remover(">")(line)))
|
||||||
case startsWith("=>"):
|
case startsWith("=>"):
|
||||||
source, content, class := wikilink(line, state)
|
href, text, class := Rocketlink(line, state.name)
|
||||||
addLine(fmt.Sprintf(
|
addLine(fmt.Sprintf(
|
||||||
`<p><a id='%d' class='%s' href="%s">%s</a></p>`, state.id, class, source, content))
|
`<p><a id='%d' class='rocketlink %s' href="%s">%s</a></p>`, state.id, class, href, text))
|
||||||
|
|
||||||
case startsWith("<="):
|
case startsWith("<="):
|
||||||
addLine(parseTransclusion(line, state.name))
|
addLine(parseTransclusion(line, state.name))
|
||||||
@ -221,6 +176,6 @@ normalState:
|
|||||||
state.where = "img"
|
state.where = "img"
|
||||||
state.img = ImgFromFirstLine(line, state.name)
|
state.img = ImgFromFirstLine(line, state.name)
|
||||||
default:
|
default:
|
||||||
addLine(fmt.Sprintf("<p id='%d'>%s</p>", state.id, ParagraphToHtml(line)))
|
addLine(fmt.Sprintf("<p id='%d'>%s</p>", state.id, ParagraphToHtml(state.name, line)))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -41,13 +41,13 @@ func TestLex(t *testing.T) {
|
|||||||
</ul>`},
|
</ul>`},
|
||||||
{6, "<p id='6'>text</p>"},
|
{6, "<p id='6'>text</p>"},
|
||||||
{7, "<p id='7'>more text</p>"},
|
{7, "<p id='7'>more text</p>"},
|
||||||
{8, `<p><a id='8' class='wikilink_internal' href="/page/Pear">some link</a></p>`},
|
{8, `<p><a id='8' class='rocketlink wikilink_internal' href="/page/Pear">some link</a></p>`},
|
||||||
{9, `<ul id='9'>
|
{9, `<ul id='9'>
|
||||||
<li>lin"+</li>
|
<li>lin"+</li>
|
||||||
</ul>`},
|
</ul>`},
|
||||||
{10, `<pre id='10' alt='alt text goes here' class='codeblock'><code>=> preformatted text
|
{10, `<pre id='10' alt='alt text goes here' class='codeblock'><code>=> preformatted text
|
||||||
where markup is not lexed</code></pre>`},
|
where markup is not lexed</code></pre>`},
|
||||||
{11, `<p><a id='11' class='wikilink_internal' href="/page/linking">linking</a></p>`},
|
{11, `<p><a id='11' class='rocketlink wikilink_internal' href="/page/linking">linking</a></p>`},
|
||||||
{12, "<p id='12'>text</p>"},
|
{12, "<p id='12'>text</p>"},
|
||||||
{13, `<pre id='13' alt='' class='codeblock'><code>()
|
{13, `<pre id='13' alt='' class='codeblock'><code>()
|
||||||
/\</code></pre>`},
|
/\</code></pre>`},
|
||||||
@ -56,11 +56,11 @@ where markup is not lexed</code></pre>`},
|
|||||||
hyphaName: "Apple",
|
hyphaName: "Apple",
|
||||||
inDesc: false,
|
inDesc: false,
|
||||||
entries: []imgEntry{
|
entries: []imgEntry{
|
||||||
{"hypha1", "", "", ""},
|
{"/binary/hypha1", "", "", ""},
|
||||||
{"hypha2", "", "", ""},
|
{"/binary/hypha2", "", "", ""},
|
||||||
{"hypha3", "60", "", ""},
|
{"/binary/hypha3", "60", "", ""},
|
||||||
{"hypha4", "", "", " line1\nline2\n"},
|
{"/binary/hypha4", "", "", " line1\nline2\n"},
|
||||||
{"hypha5", "", "", "\nstate of minnesota\n"},
|
{"/binary/hypha5", "", "", "\nstate of minnesota\n"},
|
||||||
},
|
},
|
||||||
}},
|
}},
|
||||||
})
|
})
|
||||||
|
49
markup/link.go
Normal file
49
markup/link.go
Normal file
@ -0,0 +1,49 @@
|
|||||||
|
package markup
|
||||||
|
|
||||||
|
import (
|
||||||
|
"path"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
// LinkParts determines what href, text and class should resulting <a> have based on mycomarkup's addr, display and hypha name.
|
||||||
|
//
|
||||||
|
// => addr display
|
||||||
|
// [[addr|display]]
|
||||||
|
func LinkParts(addr, display, hyphaName string) (href, text, class string) {
|
||||||
|
if display == "" {
|
||||||
|
text = addr
|
||||||
|
} else {
|
||||||
|
text = strings.TrimSpace(display)
|
||||||
|
}
|
||||||
|
class = "wikilink_internal"
|
||||||
|
|
||||||
|
switch {
|
||||||
|
case strings.ContainsRune(addr, ':'):
|
||||||
|
return addr, text, "wikilink_external"
|
||||||
|
case strings.HasPrefix(addr, "/"):
|
||||||
|
return addr, text, class
|
||||||
|
case strings.HasPrefix(addr, "./"):
|
||||||
|
hyphaName = canonicalName(path.Join(hyphaName, addr[2:]))
|
||||||
|
case strings.HasPrefix(addr, "../"):
|
||||||
|
hyphaName = canonicalName(path.Join(path.Dir(hyphaName), addr[3:]))
|
||||||
|
default:
|
||||||
|
hyphaName = canonicalName(addr)
|
||||||
|
}
|
||||||
|
if !HyphaExists(hyphaName) {
|
||||||
|
class += " wikilink_new"
|
||||||
|
}
|
||||||
|
return "/page/" + hyphaName, text, class
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse markup line starting with "=>" according to wikilink rules.
|
||||||
|
// See http://localhost:1737/page/wikilink
|
||||||
|
func Rocketlink(src, hyphaName string) (href, text, class string) {
|
||||||
|
src = strings.TrimSpace(src[2:]) // Drop =>
|
||||||
|
if src == "" {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// Href is text after => till first whitespace
|
||||||
|
addr := strings.Fields(src)[0]
|
||||||
|
display := strings.TrimPrefix(src, addr)
|
||||||
|
return LinkParts(addr, display, hyphaName)
|
||||||
|
}
|
@ -17,6 +17,7 @@ const (
|
|||||||
spanSuper
|
spanSuper
|
||||||
spanSub
|
spanSub
|
||||||
spanMark
|
spanMark
|
||||||
|
spanLink
|
||||||
)
|
)
|
||||||
|
|
||||||
func tagFromState(stt spanTokenType, tagState map[spanTokenType]bool, tagName, originalForm string) string {
|
func tagFromState(stt spanTokenType, tagState map[spanTokenType]bool, tagName, originalForm string) string {
|
||||||
@ -32,7 +33,33 @@ func tagFromState(stt spanTokenType, tagState map[spanTokenType]bool, tagName, o
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// getTextNode splits the `p` into two parts `textNode` and `rest` by the first encountered rune that resembles a span tag. If there is none, `textNode = p`, `rest = ""`. It handles escaping with backslash.
|
func getLinkNode(input *bytes.Buffer, hyphaName string) string {
|
||||||
|
input.Next(2)
|
||||||
|
var (
|
||||||
|
escaping = false
|
||||||
|
addrBuf = bytes.Buffer{}
|
||||||
|
displayBuf = bytes.Buffer{}
|
||||||
|
currBuf = &addrBuf
|
||||||
|
)
|
||||||
|
for input.Len() != 0 {
|
||||||
|
b, _ := input.ReadByte()
|
||||||
|
if escaping {
|
||||||
|
currBuf.WriteByte(b)
|
||||||
|
escaping = false
|
||||||
|
} else if b == '|' && currBuf == &addrBuf {
|
||||||
|
currBuf = &displayBuf
|
||||||
|
} else if b == ']' && bytes.HasPrefix(input.Bytes(), []byte{']'}) {
|
||||||
|
input.Next(1)
|
||||||
|
break
|
||||||
|
} else {
|
||||||
|
currBuf.WriteByte(b)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
href, text, class := LinkParts(addrBuf.String(), displayBuf.String(), hyphaName)
|
||||||
|
return fmt.Sprintf(`<a href="%s" class="%s">%s</a>`, href, class, html.EscapeString(text))
|
||||||
|
}
|
||||||
|
|
||||||
|
// getTextNode splits the `input` into two parts `textNode` and `rest` by the first encountered rune that resembles a span tag. If there is none, `textNode = input`, `rest = ""`. It handles escaping with backslash.
|
||||||
func getTextNode(input *bytes.Buffer) string {
|
func getTextNode(input *bytes.Buffer) string {
|
||||||
var (
|
var (
|
||||||
textNodeBuffer = bytes.Buffer{}
|
textNodeBuffer = bytes.Buffer{}
|
||||||
@ -51,7 +78,7 @@ func getTextNode(input *bytes.Buffer) string {
|
|||||||
escaping = false
|
escaping = false
|
||||||
} else if b == '\\' {
|
} else if b == '\\' {
|
||||||
escaping = true
|
escaping = true
|
||||||
} else if strings.IndexByte("/*`^,!", b) >= 0 {
|
} else if strings.IndexByte("/*`^,![", b) >= 0 {
|
||||||
input.UnreadByte()
|
input.UnreadByte()
|
||||||
break
|
break
|
||||||
} else {
|
} else {
|
||||||
@ -61,7 +88,7 @@ func getTextNode(input *bytes.Buffer) string {
|
|||||||
return textNodeBuffer.String()
|
return textNodeBuffer.String()
|
||||||
}
|
}
|
||||||
|
|
||||||
func ParagraphToHtml(input string) string {
|
func ParagraphToHtml(hyphaName, input string) string {
|
||||||
var (
|
var (
|
||||||
p = bytes.NewBufferString(input)
|
p = bytes.NewBufferString(input)
|
||||||
ret strings.Builder
|
ret strings.Builder
|
||||||
@ -73,6 +100,7 @@ func ParagraphToHtml(input string) string {
|
|||||||
spanSuper: false,
|
spanSuper: false,
|
||||||
spanSub: false,
|
spanSub: false,
|
||||||
spanMark: false,
|
spanMark: false,
|
||||||
|
spanLink: false,
|
||||||
}
|
}
|
||||||
startsWith = func(t string) bool {
|
startsWith = func(t string) bool {
|
||||||
return bytes.HasPrefix(p.Bytes(), []byte(t))
|
return bytes.HasPrefix(p.Bytes(), []byte(t))
|
||||||
@ -99,6 +127,8 @@ func ParagraphToHtml(input string) string {
|
|||||||
case startsWith("!!"):
|
case startsWith("!!"):
|
||||||
ret.WriteString(tagFromState(spanMark, tagState, "mark", "!!"))
|
ret.WriteString(tagFromState(spanMark, tagState, "mark", "!!"))
|
||||||
p.Next(2)
|
p.Next(2)
|
||||||
|
case startsWith("[["):
|
||||||
|
ret.WriteString(getLinkNode(p, hyphaName))
|
||||||
default:
|
default:
|
||||||
ret.WriteString(html.EscapeString(getTextNode(p)))
|
ret.WriteString(html.EscapeString(getTextNode(p)))
|
||||||
}
|
}
|
||||||
@ -119,6 +149,8 @@ func ParagraphToHtml(input string) string {
|
|||||||
ret.WriteString(tagFromState(spanSub, tagState, "sub", ",,"))
|
ret.WriteString(tagFromState(spanSub, tagState, "sub", ",,"))
|
||||||
case spanMark:
|
case spanMark:
|
||||||
ret.WriteString(tagFromState(spanMark, tagState, "mark", "!!"))
|
ret.WriteString(tagFromState(spanMark, tagState, "mark", "!!"))
|
||||||
|
case spanLink:
|
||||||
|
ret.WriteString(tagFromState(spanLink, tagState, "a", "[["))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -35,10 +35,15 @@ func TestParagraphToHtml(t *testing.T) {
|
|||||||
{"this is a left **bold", "this is a left <strong>bold</strong>"},
|
{"this is a left **bold", "this is a left <strong>bold</strong>"},
|
||||||
{"this line has a ,comma, two of them", "this line has a ,comma, two of them"},
|
{"this line has a ,comma, two of them", "this line has a ,comma, two of them"},
|
||||||
{"Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.", "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum."},
|
{"Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.", "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum."},
|
||||||
|
{"A [[simple]] link", `A <a href="/page/simple" class="wikilink_internal">simple</a> link`},
|
||||||
}
|
}
|
||||||
for _, test := range tests {
|
for _, test := range tests {
|
||||||
if ParagraphToHtml(test[0]) != test[1] {
|
if ParagraphToHtml("Apple", test[0]) != test[1] {
|
||||||
t.Error(fmt.Sprintf("%q: Wanted %q, got %q", test[0], test[1], ParagraphToHtml(test[0])))
|
t.Error(fmt.Sprintf("%q: Wanted %q, got %q", test[0], test[1], ParagraphToHtml("Apple", test[0])))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
HyphaExists = func(_ string) bool { return true }
|
||||||
|
}
|
||||||
|
8
markup/testdata/test.myco
vendored
8
markup/testdata/test.myco
vendored
@ -25,13 +25,13 @@ text
|
|||||||
|
|
||||||
img {
|
img {
|
||||||
hypha1
|
hypha1
|
||||||
hypha2:
|
hypha2|
|
||||||
hypha3: 60
|
hypha3| 60
|
||||||
hypha4: { line1
|
hypha4| { line1
|
||||||
line2
|
line2
|
||||||
} this is ignored
|
} this is ignored
|
||||||
|
|
||||||
hypha5: {
|
hypha5| {
|
||||||
state of minnesota
|
state of minnesota
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1 +1 @@
|
|||||||
Subproject commit 9fa5334ee958c02fbb327c002bf646e648a5a1be
|
Subproject commit d78a90718ae9c36f7a114901ddad84b0e23221b3
|
Loading…
x
Reference in New Issue
Block a user