pico
created pr with
102.1
added 102.2
1: e265f5e ! 1: ff673f6 feat(prose): lists are back bby
added 102.3
1: ff673f6 ! 1: c86f5a4 feat(prose): lists are back bby
changed status to
accepted
cmds
checkout latest patchset:
ssh pr.pico.sh print 102 | git am -3checkout any patchset in a patch request:
ssh pr.pico.sh print 102.[rev] | git am -3add changes to patch request:
git format-patch main --stdout | ssh pr.pico.sh pr add 102set PR to open (enables RSS notifications):
ssh pr.pico.sh pr open 102set PR to draft (stops RSS notifications):
ssh pr.pico.sh pr draft 102
Patchset
102.2
feat(prose): lists are back bby
Eric Bower
2026-01-04T04:35:04ZSemantic diff summary
2 added,
7 modified,
0 signature changed,
1 removed
across 5 analyzed files
(2 files skipped: unsupported file type)
+43
-15
pkg/apps/prose/api.go
#
@@ -90,6 +90,7 @@ type PostPageData struct {
Diff template.HTML
UpdatedAtISO string
UpdatedAt string
+ List *shared.ListParsedText
}
type HeaderTxt struct {
@@ -427,22 +428,47 @@ func postHandler(w http.ResponseWriter, r *http.Request) {
post, err := dbpool.FindPostWithSlug(slug, user.ID, cfg.Space)
if err == nil {
logger.Info("post found", "id", post.ID, "filename", post.FileSize)
- parsedText, err := shared.ParseText(post.Text)
- if err != nil {
- logger.Error("find post with slug", "err", err.Error())
- }
+ ext := filepath.Ext(post.Filename)
+ contents := template.HTML("")
+ tags := []string{}
+ unlisted := false
+ var list *shared.ListParsedText
+
+ switch ext {
+ case ".lxt":
+ list = shared.ListParseText(post.Text)
+
+ // if parsedText.Image != "" {
+ // ogImage = parsedText.Image
+ // }
+ //
+ // if parsedText.ImageCard != "" {
+ // ogImageCard = parsedText.ImageCard
+ // }
+ tags = list.Tags
+
+ if post.Hidden || post.PublishAt.After(time.Now()) {
+ unlisted = true
+ }
+ case ".md":
+ parsedText, err := shared.ParseText(post.Text)
+ if err != nil {
+ logger.Error("could not parse md text", "err", err.Error())
+ }
- if parsedText.Image != "" {
- ogImage = parsedText.Image
- }
+ if parsedText.Image != "" {
+ ogImage = parsedText.Image
+ }
- if parsedText.ImageCard != "" {
- ogImageCard = parsedText.ImageCard
- }
+ if parsedText.ImageCard != "" {
+ ogImageCard = parsedText.ImageCard
+ }
+ tags = parsedText.Tags
- unlisted := false
- if post.Hidden || post.PublishAt.After(time.Now()) {
- unlisted = true
+ if post.Hidden || post.PublishAt.After(time.Now()) {
+ unlisted = true
+ }
+ contents = template.HTML(parsedText.Html)
}
data = PostPageData{
@@ -459,10 +485,10 @@ func postHandler(w http.ResponseWriter, r *http.Request) {
UpdatedAtISO: post.UpdatedAt.Format(time.RFC3339),
Username: username,
BlogName: blogName,
- Contents: template.HTML(parsedText.Html),
+ Contents: contents,
HasCSS: hasCSS,
CssURL: template.URL(cfg.CssURL(username)),
- Tags: parsedText.Tags,
+ Tags: tags,
Image: template.URL(ogImage),
ImageCard: ogImageCard,
Favicon: template.URL(favicon),
@@ -470,6 +496,7 @@ func postHandler(w http.ResponseWriter, r *http.Request) {
Unlisted: unlisted,
Diff: template.HTML(diff),
WithStyles: withStyles,
+ List: list,
}
} else {
logger.Info("post not found")
@@ -522,6 +549,7 @@ func postHandler(w http.ResponseWriter, r *http.Request) {
}
ts, err := shared.RenderTemplate(cfg, []string{
+ cfg.StaticPath("html/list.partial.tmpl"),
cfg.StaticPath("html/post.page.tmpl"),
})
+1
-0
pkg/apps/prose/config.go
#
@@ -28,6 +28,7 @@ func NewConfigSite(service string) *shared.ConfigSite {
Space: "prose",
AllowedExt: []string{
".md",
+ ".lxt",
".jpg",
".jpeg",
".png",
+51
-0
pkg/apps/prose/html/list.partial.tmpl
#
@@ -0,0 +1,51 @@
+{{define "list"}}
+{{$indent := 0}}
+{{$mod := 0}}
+<ul style="list-style-type: {{.ListType}};">
+ {{range .Items}}
+ {{if lt $indent .Indent}}
+ <ul>
+ {{else if gt $indent .Indent}}
+
+ {{$mod = minus $indent .Indent}}
+ {{range $y := intRange 1 $mod}}
+ </li></ul>
+ {{end}}
+
+ {{else}}
+ </li>
+ {{end}}
+ {{$indent = .Indent}}
+
+ {{if .IsText}}
+ {{if .Value}}
+ <li>{{.Value}}
+ {{end}}
+ {{end}}
+
+ {{if .IsURL}}
+ <li><a href="{{.URL}}">{{.Value}}</a>
+ {{end}}
+
+ {{if .IsImg}}
+ <li><img src="{{.URL}}" alt="{{.Value}}" />
+ {{end}}
+
+ {{if .IsBlock}}
+ <li><blockquote>{{.Value}}</blockquote>
+ {{end}}
+
+ {{if .IsHeaderOne}}
+ </ul><h2 class="text-xl font-bold">{{.Value}}</h2><ul style="list-style-type: {{$.ListType}};">
+ {{end}}
+
+ {{if .IsHeaderTwo}}
+ </ul><h3 class="text-lg font-bold">{{.Value}}</h3><ul style="list-style-type: {{$.ListType}};">
+ {{end}}
+
+ {{if .IsPre}}
+ <li><pre>{{.Value}}</pre>
+ {{end}}
+ {{end}}
+</ul>
+{{end}}
+5
-1
pkg/apps/prose/html/post.page.tmpl
#
@@ -65,7 +65,11 @@
</header>
<main>
<article class="md">
- {{.Contents}}
+ {{if .List}}
+ {{template "list" .List}}
+ {{else}}
+ {{.Contents}}
+ {{end}}
<div class="tags">
{{range .Tags}}
+43
-1
pkg/apps/prose/scp_hooks.go
#
@@ -2,6 +2,7 @@ package prose
import (
"fmt"
+ "path/filepath"
"strings"
"slices"
@@ -62,7 +63,29 @@ func (p *MarkdownHooks) FileValidate(s *pssh.SSHServerConnSession, data *filehan
return true, nil
}
-func (p *MarkdownHooks) FileMeta(s *pssh.SSHServerConnSession, data *filehandlers.PostMetaData) error {
+func (p *MarkdownHooks) metaLxt(data *filehandlers.PostMetaData) error {
+ parsedText := shared.ListParseText(data.Text)
+
+ if parsedText.Title == "" {
+ data.Title = utils.ToUpper(data.Slug)
+ } else {
+ data.Title = parsedText.Title
+ }
+
+ data.Aliases = parsedText.Aliases
+ data.Tags = parsedText.Tags
+ data.Description = parsedText.Description
+
+ if parsedText.PublishAt != nil && !parsedText.PublishAt.IsZero() {
+ data.PublishAt = parsedText.PublishAt
+ }
+
+ isHiddenFilename := slices.Contains(p.Cfg.HiddenPosts, data.Filename)
+ data.Hidden = parsedText.Hidden || isHiddenFilename
+ return nil
+}
+
+func (p *MarkdownHooks) metaMd(data *filehandlers.PostMetaData) error {
parsedText, err := shared.ParseText(data.Text)
if err != nil {
return fmt.Errorf("%s: %w", data.Filename, err)
@@ -84,6 +107,25 @@ func (p *MarkdownHooks) FileMeta(s *pssh.SSHServerConnSession, data *filehandler
isHiddenFilename := slices.Contains(p.Cfg.HiddenPosts, data.Filename)
data.Hidden = parsedText.Hidden || isHiddenFilename
+ return nil
+}
+
+func (p *MarkdownHooks) FileMeta(s *pssh.SSHServerConnSession, data *filehandlers.PostMetaData) error {
+ ext := filepath.Ext(data.Filename)
+ switch ext {
+ case ".lxt":
+ err := p.metaLxt(data)
+ if err != nil {
+ return fmt.Errorf("%s: %w", data.Filename, err)
+ }
+ case ".md":
+ err := p.metaMd(data)
+ if err != nil {
+ return fmt.Errorf("%s: %w", data.Filename, err)
+ }
+ default:
+ return fmt.Errorf("%s: invalid filetype", data.Filename)
+ }
return nil
}
+1
-0
pkg/apps/prose/ssh.go
#
@@ -55,6 +55,7 @@ func StartSshServer() {
".md": filehandlers.NewScpPostHandler(dbh, cfg, hooks),
".txt": filehandlers.NewScpPostHandler(dbh, cfg, hooks),
".css": filehandlers.NewScpPostHandler(dbh, cfg, hooks),
+ ".lxt": filehandlers.NewScpPostHandler(dbh, cfg, hooks),
"fallback": uploadimgs.NewUploadImgHandler(dbh, cfg, st),
}
handler := filehandlers.NewFileHandlerRouter(cfg, dbh, fileMap)