Add plain-text post previews

I opted to do this by making a new markdown renderer that only outputs
plain text, no HTML. This feels a lot more sane to me than trying to
strip HTML out of already-parsed stuff. The tradeoff right now is that
some content just doesn't show up at all, notably bbcode content. I
doubt anyone will care.
This commit is contained in:
Ben Visness 2021-07-05 13:34:51 -05:00
parent c1785d79a4
commit 6b21291798
8 changed files with 111 additions and 44 deletions

Binary file not shown.

View File

@ -1,16 +1,23 @@
package config
import (
"github.com/jackc/pgx/v4"
"github.com/rs/zerolog"
)
var Config = HMNConfig{
Env: Dev,
Addr: ":9001",
BaseUrl: "http://handmade.local:9001",
LogLevel: zerolog.TraceLevel,
Env: Dev,
Addr: ":9001",
PrivateAddr: ":9002",
BaseUrl: "http://handmade.local:9001",
LogLevel: zerolog.TraceLevel,
Postgres: PostgresConfig{
User: "hmn",
Password: "password",
Hostname: "handmade.local",
Port: 5454,
DbName: "hmn",
LogLevel: pgx.LogLevelTrace,
MinConn: 2, // Keep these low for dev, high for production
MaxConn: 2,
},

View File

@ -9,37 +9,23 @@ import (
"github.com/yuin/goldmark/util"
)
var previewMarkdown = goldmark.New(
goldmark.WithExtensions(
extension.GFM,
highlightExtension,
SpoilerExtension{},
EmbedExtension{
Preview: true,
},
MathjaxExtension{},
BBCodeExtension{
Preview: true,
},
),
)
var realMarkdown = goldmark.New(
goldmark.WithExtensions(
extension.GFM,
highlightExtension,
SpoilerExtension{},
EmbedExtension{},
MathjaxExtension{},
BBCodeExtension{},
),
// Used for rendering real-time previews of post content.
var PreviewMarkdown = goldmark.New(
goldmark.WithExtensions(makeGoldmarkExtensions(true)...),
)
func ParsePostInput(source string, preview bool) string {
md := realMarkdown
if preview {
md = previewMarkdown
}
// Used for generating the final HTML for a post.
var RealMarkdown = goldmark.New(
goldmark.WithExtensions(makeGoldmarkExtensions(false)...),
)
// Used for generating plain-text previews of posts.
var PlaintextMarkdown = goldmark.New(
goldmark.WithExtensions(makeGoldmarkExtensions(false)...),
goldmark.WithRenderer(plaintextRenderer{}),
)
func ParsePostInput(source string, md goldmark.Markdown) string {
var buf bytes.Buffer
if err := md.Convert([]byte(source), &buf); err != nil {
panic(err)
@ -48,6 +34,21 @@ func ParsePostInput(source string, preview bool) string {
return buf.String()
}
func makeGoldmarkExtensions(preview bool) []goldmark.Extender {
return []goldmark.Extender{
extension.GFM,
highlightExtension,
SpoilerExtension{},
EmbedExtension{
Preview: preview,
},
MathjaxExtension{},
BBCodeExtension{
Preview: preview,
},
}
}
var highlightExtension = highlighting.NewHighlighting(
highlighting.WithFormatOptions(HMNChromaOptions...),
highlighting.WithWrapperRenderer(func(w util.BufWriter, context highlighting.CodeBlockContext, entering bool) {

View File

@ -10,14 +10,14 @@ import (
func TestMarkdown(t *testing.T) {
t.Run("fenced code blocks", func(t *testing.T) {
t.Run("multiple lines", func(t *testing.T) {
html := ParsePostInput("```\nmultiple lines\n\tof code\n```", false)
html := ParsePostInput("```\nmultiple lines\n\tof code\n```", RealMarkdown)
t.Log(html)
assert.Equal(t, 1, strings.Count(html, "<pre"))
assert.Contains(t, html, `class="hmn-code"`)
assert.Contains(t, html, "multiple lines\n\tof code")
})
t.Run("multiple lines with language", func(t *testing.T) {
html := ParsePostInput("```go\nfunc main() {\n\tfmt.Println(\"Hello, world!\")\n}\n```", false)
html := ParsePostInput("```go\nfunc main() {\n\tfmt.Println(\"Hello, world!\")\n}\n```", RealMarkdown)
t.Log(html)
assert.Equal(t, 1, strings.Count(html, "<pre"))
assert.Contains(t, html, `class="hmn-code"`)
@ -30,7 +30,7 @@ func TestMarkdown(t *testing.T) {
func TestBBCode(t *testing.T) {
t.Run("[code]", func(t *testing.T) {
t.Run("one line", func(t *testing.T) {
html := ParsePostInput("[code]Just some code, you know?[/code]", false)
html := ParsePostInput("[code]Just some code, you know?[/code]", RealMarkdown)
t.Log(html)
assert.Equal(t, 1, strings.Count(html, "<pre"))
assert.Contains(t, html, `class="hmn-code"`)
@ -41,7 +41,7 @@ func TestBBCode(t *testing.T) {
Multiline code
with an indent
[/code]`
html := ParsePostInput(bbcode, false)
html := ParsePostInput(bbcode, RealMarkdown)
t.Log(html)
assert.Equal(t, 1, strings.Count(html, "<pre"))
assert.Contains(t, html, `class="hmn-code"`)
@ -54,7 +54,7 @@ func main() {
fmt.Println("Hello, world!")
}
[/code]`
html := ParsePostInput(bbcode, false)
html := ParsePostInput(bbcode, RealMarkdown)
t.Log(html)
assert.Equal(t, 1, strings.Count(html, "<pre"))
assert.Contains(t, html, "Println")
@ -114,4 +114,7 @@ func main() {
[td]Body 1[/td] [td]Body 2[/td]
[/tr]
[/table]
[youtube]https://www.youtube.com/watch?v=0J8G9qNT7gQ[/youtube]
[youtube]https://youtu.be/0J8G9qNT7gQ[/youtube]
`

46
src/parsing/renderer.go Normal file
View File

@ -0,0 +1,46 @@
package parsing
import (
"io"
"github.com/yuin/goldmark/ast"
"github.com/yuin/goldmark/renderer"
)
type plaintextRenderer struct{}
var _ renderer.Renderer = plaintextRenderer{}
func (r plaintextRenderer) Render(w io.Writer, source []byte, n ast.Node) error {
return ast.Walk(n, func(n ast.Node, entering bool) (ast.WalkStatus, error) {
if !entering {
return ast.WalkContinue, nil
}
switch n.Kind() {
case ast.KindText:
n := n.(*ast.Text)
_, err := w.Write(n.Text(source))
if err != nil {
return ast.WalkContinue, err
}
if n.SoftLineBreak() {
_, err := w.Write([]byte(" "))
if err != nil {
return ast.WalkContinue, err
}
}
case ast.KindParagraph:
_, err := w.Write([]byte(" "))
if err != nil {
return ast.WalkContinue, err
}
}
return ast.WalkContinue, nil
})
}
func (r plaintextRenderer) AddOptions(...renderer.Option) {}

View File

@ -8,7 +8,7 @@ import (
func main() {
js.Global().Set("parseMarkdown", js.FuncOf(func(this js.Value, args []js.Value) interface{} {
return parsing.ParsePostInput(args[0].String(), true)
return parsing.ParsePostInput(args[0].String(), parsing.PreviewMarkdown)
}))
var done chan bool

View File

@ -8,9 +8,10 @@
<script src="{{ static "go_wasm_exec.js" }}"></script>
<script>
const go = new Go();
WebAssembly.instantiateStreaming(fetch("{{ static "parsing.wasm" }}"), go.importObject).then(result => {
go.run(result.instance);
});
const goLoaded = WebAssembly.instantiateStreaming(fetch("{{ static "parsing.wasm" }}"), go.importObject)
.then(result => {
go.run(result.instance);
});
</script>
{{ end }}
@ -110,6 +111,9 @@
MathJax.typeset();
}
goLoaded.then(() => {
updatePreview();
});
tf.addEventListener('input', () => {
updatePreview();
});

View File

@ -581,11 +581,17 @@ func ForumNewThreadSubmit(c *RequestContext) ResponseData {
sticky = true
}
parsed := parsing.ParsePostInput(unparsed, false)
parsed := parsing.ParsePostInput(unparsed, parsing.RealMarkdown)
now := time.Now()
ip := net.ParseIP(c.Req.RemoteAddr)
const previewMaxLength = 100
parsedPlaintext := parsing.ParsePostInput(unparsed, parsing.PlaintextMarkdown)
preview := parsedPlaintext
if len(preview) > previewMaxLength-1 {
preview = preview[:previewMaxLength-1] + "…"
}
// Create thread
var threadId int
err = tx.QueryRow(c.Context(),
@ -615,7 +621,7 @@ func ForumNewThreadSubmit(c *RequestContext) ResponseData {
now,
currentCatId,
threadId,
"lol", // TODO: Actual previews
preview,
-1,
c.CurrentUser.ID,
models.CatKindForum,