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:
parent
c1785d79a4
commit
6b21291798
Binary file not shown.
|
@ -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,
|
||||
},
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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]
|
||||
`
|
||||
|
|
|
@ -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) {}
|
|
@ -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
|
||||
|
|
|
@ -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();
|
||||
});
|
||||
|
|
|
@ -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,
|
||||
|
|
Loading…
Reference in New Issue