Add spoilers (and broken jank starter bbcode)
This commit is contained in:
parent
08f20f9fed
commit
987d379223
Binary file not shown.
|
@ -0,0 +1,70 @@
|
||||||
|
package parsing
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/yuin/goldmark"
|
||||||
|
"github.com/yuin/goldmark/ast"
|
||||||
|
gast "github.com/yuin/goldmark/ast"
|
||||||
|
"github.com/yuin/goldmark/parser"
|
||||||
|
"github.com/yuin/goldmark/text"
|
||||||
|
"github.com/yuin/goldmark/util"
|
||||||
|
)
|
||||||
|
|
||||||
|
const BBCodePriority = 1 // TODO: Pick something more reasonable?
|
||||||
|
|
||||||
|
type bParser struct{}
|
||||||
|
|
||||||
|
var _ parser.InlineParser = bParser{}
|
||||||
|
|
||||||
|
func (s bParser) Trigger() []byte {
|
||||||
|
return []byte{'['}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s bParser) Parse(parent gast.Node, block text.Reader, pc parser.Context) gast.Node {
|
||||||
|
// _, segment := block.PeekLine()
|
||||||
|
// start := segment.Start
|
||||||
|
|
||||||
|
// block.Advance(3)
|
||||||
|
// n := ast.NewTextSegment(text.NewSegment(start, start+4))
|
||||||
|
// bold := ast.NewText()
|
||||||
|
// bold.Segment
|
||||||
|
// link := ast.NewAutoLink(typ, n)
|
||||||
|
// link.Protocol = protocol
|
||||||
|
// return link
|
||||||
|
|
||||||
|
lineBytes, segment := block.PeekLine()
|
||||||
|
fmt.Printf("line: %s\n", string(lineBytes))
|
||||||
|
fmt.Printf("segment: %#v\n", segment)
|
||||||
|
|
||||||
|
line := string(lineBytes)
|
||||||
|
|
||||||
|
if !strings.HasPrefix(line, "[b]") {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
start := 0
|
||||||
|
|
||||||
|
closingIndex := strings.Index(line, "[/b]")
|
||||||
|
if closingIndex < 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
end := closingIndex + 4
|
||||||
|
|
||||||
|
n := ast.NewEmphasis(2)
|
||||||
|
n.AppendChild(n, ast.NewString([]byte("wow bold text")))
|
||||||
|
|
||||||
|
block.Advance(end - start)
|
||||||
|
return n
|
||||||
|
}
|
||||||
|
|
||||||
|
type bTag struct{}
|
||||||
|
|
||||||
|
func (e bTag) Extend(m goldmark.Markdown) {
|
||||||
|
m.Parser().AddOptions(parser.WithInlineParsers(
|
||||||
|
util.Prioritized(bParser{}, BBCodePriority),
|
||||||
|
))
|
||||||
|
// m.Renderer().AddOptions(renderer.WithNodeRenderers(
|
||||||
|
// util.Prioritized(NewStrikethroughHTMLRenderer(), 500),
|
||||||
|
// ))
|
||||||
|
}
|
|
@ -91,7 +91,7 @@ func tokenizeBBCode(input string) []token {
|
||||||
}
|
}
|
||||||
|
|
||||||
var md = goldmark.New(
|
var md = goldmark.New(
|
||||||
goldmark.WithExtensions(extension.GFM),
|
goldmark.WithExtensions(extension.GFM, SpoilerExtension{}, bTag{}),
|
||||||
)
|
)
|
||||||
|
|
||||||
func ParsePostInput(source string) string {
|
func ParsePostInput(source string) string {
|
||||||
|
|
|
@ -26,22 +26,28 @@ Mix 'em: [u][/i]wow.[/i][/u]
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestParsePostInput(t *testing.T) {
|
// func TestParsePostInput(t *testing.T) {
|
||||||
testDoc := []byte(`
|
// testDoc := []byte(`
|
||||||
Hello, *world!*
|
// Hello, *world!*
|
||||||
|
|
||||||
I can do **bold**, *italic*, and _underlined_ text??
|
// I can do **bold**, *italic*, and _underlined_ text??
|
||||||
|
|
||||||
# Heading 1
|
// # Heading 1
|
||||||
## Heading 2
|
// ## Heading 2
|
||||||
### Heading 3
|
// ### Heading 3
|
||||||
|
|
||||||
Links: [HMN](https://handmade.network)
|
// Links: [HMN](https://handmade.network)
|
||||||
Images: ![this is a picture of sanic](https://i.kym-cdn.com/photos/images/newsfeed/000/722/711/ef1.jpg)
|
// Images: ![this is a picture of sanic](https://i.kym-cdn.com/photos/images/newsfeed/000/722/711/ef1.jpg)
|
||||||
`)
|
// `)
|
||||||
|
|
||||||
res := ParsePostInput(testDoc)
|
// res := ParsePostInput(testDoc)
|
||||||
fmt.Println(string(res))
|
// fmt.Println(string(res))
|
||||||
|
|
||||||
|
// t.Fail()
|
||||||
|
// }
|
||||||
|
|
||||||
|
func TestBBCodeParsing(t *testing.T) {
|
||||||
|
res := ParsePostInput(`[b]ONE[/b] [i]TWO[/i]`)
|
||||||
|
fmt.Println(res)
|
||||||
t.Fail()
|
t.Fail()
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,123 @@
|
||||||
|
package parsing
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/yuin/goldmark"
|
||||||
|
gast "github.com/yuin/goldmark/ast"
|
||||||
|
"github.com/yuin/goldmark/parser"
|
||||||
|
"github.com/yuin/goldmark/renderer"
|
||||||
|
"github.com/yuin/goldmark/renderer/html"
|
||||||
|
"github.com/yuin/goldmark/text"
|
||||||
|
"github.com/yuin/goldmark/util"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ----------------------
|
||||||
|
// Parser and delimiters
|
||||||
|
// ----------------------
|
||||||
|
|
||||||
|
type spoilerParser struct{}
|
||||||
|
|
||||||
|
func NewSpoilerParser() parser.InlineParser {
|
||||||
|
return spoilerParser{}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s spoilerParser) Trigger() []byte {
|
||||||
|
return []byte{'|'}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s spoilerParser) Parse(parent gast.Node, block text.Reader, pc parser.Context) gast.Node {
|
||||||
|
before := block.PrecendingCharacter() // ScanDelimiter needs this for shady left vs. right delimiter reasons. Who delimits the delimiters?
|
||||||
|
restOfLine, segment := block.PeekLine() // Gets the rest of the line (starting at the current parser cursor index), and the segment representing the indices in the source text.
|
||||||
|
delimiter := parser.ScanDelimiter(restOfLine, before, 2, spoilerDelimiterParser{}) // Scans a consecutive run of the trigger character. Returns a delimiter node tracking which character that was, how many of it there were, etc. We do 2 here because we want ~~spoilers~~.
|
||||||
|
if delimiter == nil {
|
||||||
|
// I guess we only saw one ~ :)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
delimiter.Segment = segment.WithStop(segment.Start + delimiter.OriginalLength) // The delimiter needs to know exactly what source indices it corresponds to.
|
||||||
|
block.Advance(delimiter.OriginalLength) // Advance the parser past the delimiter.
|
||||||
|
pc.PushDelimiter(delimiter) // Push the delimiter onto the stack (either opening or closing; both are handled the same way as far as this method is concerned).
|
||||||
|
return delimiter
|
||||||
|
}
|
||||||
|
|
||||||
|
type spoilerDelimiterParser struct{}
|
||||||
|
|
||||||
|
func (p spoilerDelimiterParser) IsDelimiter(b byte) bool {
|
||||||
|
return b == '|'
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p spoilerDelimiterParser) CanOpenCloser(opener, closer *parser.Delimiter) bool {
|
||||||
|
return opener.Char == closer.Char
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p spoilerDelimiterParser) OnMatch(consumes int) gast.Node {
|
||||||
|
return NewSpoiler()
|
||||||
|
}
|
||||||
|
|
||||||
|
// ----------------------
|
||||||
|
// AST node
|
||||||
|
// ----------------------
|
||||||
|
|
||||||
|
type SpoilerNode struct {
|
||||||
|
gast.BaseInline
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ gast.Node = &SpoilerNode{}
|
||||||
|
|
||||||
|
func (n *SpoilerNode) Dump(source []byte, level int) {
|
||||||
|
gast.DumpHelper(n, source, level, nil, nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
var KindSpoiler = gast.NewNodeKind("Spoiler")
|
||||||
|
|
||||||
|
func (n *SpoilerNode) Kind() gast.NodeKind {
|
||||||
|
return KindSpoiler
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewSpoiler() *SpoilerNode {
|
||||||
|
return &SpoilerNode{}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ----------------------
|
||||||
|
// Renderer
|
||||||
|
// ----------------------
|
||||||
|
|
||||||
|
type SpoilerHTMLRenderer struct {
|
||||||
|
html.Config
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewSpoilerHTMLRenderer(opts ...html.Option) renderer.NodeRenderer {
|
||||||
|
r := &SpoilerHTMLRenderer{
|
||||||
|
Config: html.NewConfig(),
|
||||||
|
}
|
||||||
|
for _, opt := range opts {
|
||||||
|
opt.SetHTMLOption(&r.Config)
|
||||||
|
}
|
||||||
|
return r
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *SpoilerHTMLRenderer) RegisterFuncs(reg renderer.NodeRendererFuncRegisterer) {
|
||||||
|
reg.Register(KindSpoiler, r.renderSpoiler)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *SpoilerHTMLRenderer) renderSpoiler(w util.BufWriter, source []byte, n gast.Node, entering bool) (gast.WalkStatus, error) {
|
||||||
|
if entering {
|
||||||
|
_, _ = w.WriteString("<span class=\"spoiler\">")
|
||||||
|
} else {
|
||||||
|
_, _ = w.WriteString("</span>")
|
||||||
|
}
|
||||||
|
return gast.WalkContinue, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ----------------------
|
||||||
|
// Extension
|
||||||
|
// ----------------------
|
||||||
|
|
||||||
|
type SpoilerExtension struct{}
|
||||||
|
|
||||||
|
func (e SpoilerExtension) Extend(m goldmark.Markdown) {
|
||||||
|
m.Parser().AddOptions(parser.WithInlineParsers(
|
||||||
|
util.Prioritized(NewSpoilerParser(), 500),
|
||||||
|
))
|
||||||
|
m.Renderer().AddOptions(renderer.WithNodeRenderers(
|
||||||
|
util.Prioritized(NewSpoilerHTMLRenderer(), 500),
|
||||||
|
))
|
||||||
|
}
|
|
@ -108,9 +108,13 @@
|
||||||
const tf = document.querySelector('#editor');
|
const tf = document.querySelector('#editor');
|
||||||
const preview = document.querySelector('#preview');
|
const preview = document.querySelector('#preview');
|
||||||
|
|
||||||
tf.addEventListener('input', () => {
|
function updatePreview() {
|
||||||
const previewHtml = parseMarkdown(tf.value);
|
const previewHtml = parseMarkdown(tf.value);
|
||||||
preview.innerHTML = previewHtml;
|
preview.innerHTML = previewHtml;
|
||||||
})
|
}
|
||||||
|
|
||||||
|
tf.addEventListener('input', () => {
|
||||||
|
updatePreview();
|
||||||
|
});
|
||||||
</script>
|
</script>
|
||||||
{{ end }}
|
{{ end }}
|
||||||
|
|
Loading…
Reference in New Issue