diff --git a/public/parsing.wasm b/public/parsing.wasm index 7b49fa1..e2ef7d2 100644 Binary files a/public/parsing.wasm and b/public/parsing.wasm differ diff --git a/src/parsing/bbcode.go b/src/parsing/bbcode.go new file mode 100644 index 0000000..203f1c5 --- /dev/null +++ b/src/parsing/bbcode.go @@ -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), + // )) +} diff --git a/src/parsing/parsing.go b/src/parsing/parsing.go index 5510e29..ee8e1ce 100644 --- a/src/parsing/parsing.go +++ b/src/parsing/parsing.go @@ -91,7 +91,7 @@ func tokenizeBBCode(input string) []token { } var md = goldmark.New( - goldmark.WithExtensions(extension.GFM), + goldmark.WithExtensions(extension.GFM, SpoilerExtension{}, bTag{}), ) func ParsePostInput(source string) string { diff --git a/src/parsing/parsing_test.go b/src/parsing/parsing_test.go index 2b79d75..c9d413e 100644 --- a/src/parsing/parsing_test.go +++ b/src/parsing/parsing_test.go @@ -26,22 +26,28 @@ Mix 'em: [u][/i]wow.[/i][/u] }) } -func TestParsePostInput(t *testing.T) { - testDoc := []byte(` -Hello, *world!* +// func TestParsePostInput(t *testing.T) { +// testDoc := []byte(` +// Hello, *world!* -I can do **bold**, *italic*, and _underlined_ text?? +// I can do **bold**, *italic*, and _underlined_ text?? -# Heading 1 -## Heading 2 -### Heading 3 +// # Heading 1 +// ## Heading 2 +// ### Heading 3 -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) -`) +// 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) +// `) - res := ParsePostInput(testDoc) - fmt.Println(string(res)) +// res := ParsePostInput(testDoc) +// fmt.Println(string(res)) +// t.Fail() +// } + +func TestBBCodeParsing(t *testing.T) { + res := ParsePostInput(`[b]ONE[/b] [i]TWO[/i]`) + fmt.Println(res) t.Fail() } diff --git a/src/parsing/spoilers.go b/src/parsing/spoilers.go new file mode 100644 index 0000000..2d44d33 --- /dev/null +++ b/src/parsing/spoilers.go @@ -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("") + } else { + _, _ = w.WriteString("") + } + 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), + )) +} diff --git a/src/templates/src/editor.html b/src/templates/src/editor.html index b62d7c5..1a73434 100644 --- a/src/templates/src/editor.html +++ b/src/templates/src/editor.html @@ -108,9 +108,13 @@ const tf = document.querySelector('#editor'); const preview = document.querySelector('#preview'); - tf.addEventListener('input', () => { + function updatePreview() { const previewHtml = parseMarkdown(tf.value); preview.innerHTML = previewHtml; - }) + } + + tf.addEventListener('input', () => { + updatePreview(); + }); {{ end }}