dashboard / erock/pico / chore(pgs): add projects.blocked col #15 rss

accepted · opened on 2024-08-19T20:48:55Z by erock
Help
checkout latest patchset:
ssh pr.pico.sh print pr-15 | git am -3
checkout any patchset in a patch request:
ssh pr.pico.sh print ps-X | git am -3
add changes to patch request:
git format-patch main --stdout | ssh pr.pico.sh pr add 15
add review to patch request:
git format-patch main --stdout | ssh pr.pico.sh pr add --review 15
accept PR:
ssh pr.pico.sh pr accept 15
close PR:
ssh pr.pico.sh pr close 15

Logs

erock created pr with ps-26 on 2024-08-01T14:25:45Z
erock added ps-27 on 2024-08-01T15:29:59Z
erock changed status on 2024-08-01T15:29:59Z {"status":"open"}
erock changed status on 2024-08-19T20:50:31Z {"status":"closed"}

Patchsets

ps-26 by erock on 2024-08-01T14:25:45Z
Range Diff ↕ rd-27
1: 866976a = 1: 866976a feat(prose): live journal
-: ------- > 2: b82b2f7 chore: diff in post
ps-27 by erock on 2024-08-01T15:29:59Z

Patchset ps-27

feat(prose): live journal

Eric Bower
2024-08-01T14:24:47Z
db/db.go
+1 -0
go.mod
+1 -0
go.sum
+8 -0
prose/api.go
+24 -24
Back to top

feat(prose): live journal

The goal is to be able to use posts as live journals where reader can
receive updates to docs via rss.
db/db.go link
+1 -0
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
diff --git a/db/db.go b/db/db.go
index 8cee29c..2c82dc4 100644
--- a/db/db.go
+++ b/db/db.go
@@ -32,6 +32,7 @@ type User struct {
 type PostData struct {
 	ImgPath    string     `json:"img_path"`
 	LastDigest *time.Time `json:"last_digest"`
+	Diff       string     `json:"diff"`
 }
 
 // Make the Attrs struct implement the driver.Valuer interface. This method
go.mod link
+1 -0
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
diff --git a/go.mod b/go.mod
index b7084ba..549e3ad 100644
--- a/go.mod
+++ b/go.mod
@@ -37,6 +37,7 @@ require (
 	github.com/picosh/tunkit v0.0.0-20240709033345-8315d4f3cd0e
 	github.com/sabhiram/go-gitignore v0.0.0-20210923224102-525f6e181f06
 	github.com/sendgrid/sendgrid-go v3.14.0+incompatible
+	github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3
 	github.com/simplesurance/go-ip-anonymizer v0.0.0-20200429124537-35a880f8e87d
 	github.com/x-way/crawlerdetect v0.2.21
 	github.com/yuin/goldmark v1.7.1
go.sum link
+8 -0
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
diff --git a/go.sum b/go.sum
index 1980154..2577975 100644
--- a/go.sum
+++ b/go.sum
@@ -158,8 +158,11 @@ github.com/klauspost/cpuid/v2 v2.2.7 h1:ZWSB3igEs+d0qvnxR/ZBzXVmxkgt8DdzP6m9pfuV
 github.com/klauspost/cpuid/v2 v2.2.7/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws=
 github.com/kr/fs v0.1.0 h1:Jskdu9ieNAYnjxsi0LbQp1ulIKZV1LAFgK1tWhpZgl8=
 github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg=
+github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
 github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
 github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
+github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
+github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
 github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
 github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
 github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw=
@@ -265,6 +268,8 @@ github.com/sendgrid/rest v2.6.9+incompatible h1:1EyIcsNdn9KIisLW50MKwmSRSK+ekuei
 github.com/sendgrid/rest v2.6.9+incompatible/go.mod h1:kXX7q3jZtJXK5c5qK83bSGMdV6tsOE70KbHoqJls4lE=
 github.com/sendgrid/sendgrid-go v3.14.0+incompatible h1:KDSasSTktAqMJCYClHVE94Fcif2i7P7wzISv1sU6DUA=
 github.com/sendgrid/sendgrid-go v3.14.0+incompatible/go.mod h1:QRQt+LX/NmgVEvmdRw0VT/QgUn499+iza2FnDca9fg8=
+github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3 h1:n661drycOFuPLCN3Uc8sB6B/s6Z4t2xvBgU1htSHuq8=
+github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3/go.mod h1:A0bzQcvG0E7Rwjx0REVgAGH58e96+X0MeOfepqsbeW4=
 github.com/shirou/gopsutil/v3 v3.24.5 h1:i0t8kL+kQTvpAYToeuiVk3TgDeKOFioZO3Ztz/iZ9pI=
 github.com/shirou/gopsutil/v3 v3.24.5/go.mod h1:bsoOS1aStSs9ErQ1WWfxllSeS1K5D+U30r2NfcubMVk=
 github.com/shoenig/go-m1cpu v0.1.6 h1:nxdKQNcEB6vzgA2E2bvzKIYRuNj7XNJ4S/aRSwKzFtM=
@@ -276,6 +281,7 @@ github.com/simplesurance/go-ip-anonymizer v0.0.0-20200429124537-35a880f8e87d/go.
 github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
 github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
 github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
+github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
 github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
 github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
 github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
@@ -393,10 +399,12 @@ golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8T
 google.golang.org/protobuf v1.34.1 h1:9ddQBjfCyZPOHPUiPxpYESBLc+T8P3E+Vo4IbKZgFWg=
 google.golang.org/protobuf v1.34.1/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos=
 gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
+gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
 gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
 gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
 gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA=
 gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
+gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
 gopkg.in/yaml.v2 v2.2.7/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
 gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
 gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
prose/api.go link
+24 -24
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
diff --git a/prose/api.go b/prose/api.go
index 1d703d8..7643e0e 100644
--- a/prose/api.go
+++ b/prose/api.go
@@ -160,7 +160,7 @@ func blogHandler(w http.ResponseWriter, r *http.Request) {
 	}
 
 	tag := r.URL.Query().Get("tag")
-	pager := &db.Pager{Num: 1000, Page: 0}
+	pager := &db.Pager{Num: 250, Page: 0}
 	var posts []*db.Post
 	var p *db.Paginate[*db.Post]
 	if tag == "" {
@@ -170,6 +170,13 @@ func blogHandler(w http.ResponseWriter, r *http.Request) {
 	}
 	posts = p.Data
 
+	byUpdated := strings.Contains(r.URL.Path, "live")
+	if byUpdated {
+		slices.SortFunc(posts, func(a *db.Post, b *db.Post) int {
+			return b.UpdatedAt.Compare(*a.UpdatedAt)
+		})
+	}
+
 	if err != nil {
 		logger.Error(err.Error())
 		http.Error(w, "could not fetch posts for blog", http.StatusInternalServerError)
@@ -653,6 +660,13 @@ func rssBlogHandler(w http.ResponseWriter, r *http.Request) {
 	curl := shared.CreateURLFromRequest(cfg, r)
 	blogUrl := cfg.FullBlogURL(curl, username)
 
+	byUpdated := strings.Contains(r.URL.Path, "live")
+	if byUpdated {
+		slices.SortFunc(posts, func(a *db.Post, b *db.Post) int {
+			return b.UpdatedAt.Compare(*a.UpdatedAt)
+		})
+	}
+
 	feed := &feeds.Feed{
 		Id:          blogUrl,
 		Title:       headerTxt.Title,
@@ -691,12 +705,18 @@ func rssBlogHandler(w http.ResponseWriter, r *http.Request) {
 		}
 
 		realUrl := cfg.FullPostURL(curl, post.Username, post.Slug)
+		feedId := realUrl
+
+		if byUpdated {
+			feedId = fmt.Sprintf("%s:%s", realUrl, post.UpdatedAt.Format(time.RFC3339))
+		}
 
 		item := &feeds.Item{
-			Id:          realUrl,
+			Id:          feedId,
 			Title:       shared.FilenameToTitle(post.Filename, post.Title),
 			Link:        &feeds.Link{Href: realUrl},
 			Content:     tpl.String(),
+			Updated:     *post.UpdatedAt,
 			Created:     *post.CreatedAt,
 			Description: post.Description,
 		}
@@ -849,36 +869,16 @@ func createMainRoutes(staticRoutes []shared.Route) []shared.Route {
 		staticRoutes...,
 	)
 
-	routes = append(
-		routes,
-		shared.NewRoute("GET", "/rss", rssHandler),
-		shared.NewRoute("GET", "/rss.xml", rssHandler),
-		shared.NewRoute("GET", "/atom.xml", rssHandler),
-		shared.NewRoute("GET", "/feed.xml", rssHandler),
-
-		shared.NewRoute("GET", "/([^/]+)", blogHandler),
-		shared.NewRoute("GET", "/([^/]+)/rss", rssBlogHandler),
-		shared.NewRoute("GET", "/([^/]+)/rss.xml", rssBlogHandler),
-		shared.NewRoute("GET", "/([^/]+)/atom.xml", rssBlogHandler),
-		shared.NewRoute("GET", "/([^/]+)/atom", rssBlogHandler),
-		shared.NewRoute("GET", "/([^/]+)/blog/index.xml", rssBlogHandler),
-		shared.NewRoute("GET", "/([^/]+)/feed.xml", rssBlogHandler),
-		shared.NewRoute("GET", "/([^/]+)/_styles.css", blogStyleHandler),
-		shared.NewRoute("GET", "/raw/([^/]+)/(.+)", postRawHandler),
-		shared.NewRoute("GET", "/([^/]+)/(.+)/(.+)", imgs.ImgRequest),
-		shared.NewRoute("GET", "/([^/]+)/(.+.(?:jpg|jpeg|png|gif|webp|svg))$", imgs.ImgRequest),
-		shared.NewRoute("GET", "/([^/]+)/i", imgs.ImgsListHandler),
-		shared.NewRoute("GET", "/([^/]+)/(.+)", postHandler),
-	)
-
 	return routes
 }
 
 func createSubdomainRoutes(staticRoutes []shared.Route) []shared.Route {
 	routes := []shared.Route{
 		shared.NewRoute("GET", "/", blogHandler),
+		shared.NewRoute("GET", "/live", blogHandler),
 		shared.NewRoute("GET", "/_styles.css", blogStyleHandler),
 		shared.NewRoute("GET", "/rss", rssBlogHandler),
+		shared.NewRoute("GET", "/live/rss", rssBlogHandler),
 		shared.NewRoute("GET", "/rss.xml", rssBlogHandler),
 		shared.NewRoute("GET", "/atom.xml", rssBlogHandler),
 		shared.NewRoute("GET", "/feed.xml", rssBlogHandler),
prose/scp_hooks.go link
+5 -0
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
diff --git a/prose/scp_hooks.go b/prose/scp_hooks.go
index 577790c..3cb64e6 100644
--- a/prose/scp_hooks.go
+++ b/prose/scp_hooks.go
@@ -10,6 +10,7 @@ import (
 	"github.com/picosh/pico/db"
 	"github.com/picosh/pico/filehandlers"
 	"github.com/picosh/pico/shared"
+	"github.com/sergi/go-diff/diffmatchpatch"
 )
 
 type MarkdownHooks struct {
@@ -62,6 +63,10 @@ func (p *MarkdownHooks) FileMeta(s ssh.Session, data *filehandlers.PostMetaData)
 	data.Tags = parsedText.Tags
 	data.Description = parsedText.Description
 
+	dmp := diffmatchpatch.New()
+	diffs := dmp.DiffMain(data.Cur.Text, data.Text, false)
+	data.Data.Diff = dmp.DiffPrettyText(diffs)
+
 	if parsedText.PublishAt != nil && !parsedText.PublishAt.IsZero() {
 		data.PublishAt = parsedText.MetaData.PublishAt
 	}

chore: diff in post

filehandlers/post_handler.go link
+1 -0
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
diff --git a/filehandlers/post_handler.go b/filehandlers/post_handler.go
index 4e10b66..30e00cc 100644
--- a/filehandlers/post_handler.go
+++ b/filehandlers/post_handler.go
@@ -139,6 +139,7 @@ func (h *ScpUploadHandler) Write(s ssh.Session, entry *utils.FileEntry) (string,
 
 	if post != nil {
 		metadata.Cur = post
+		metadata.Data = post.Data
 		metadata.Post.PublishAt = post.PublishAt
 	}
 
prose/api.go link
+11 -0
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
diff --git a/prose/api.go b/prose/api.go
index 7643e0e..60aa52a 100644
--- a/prose/api.go
+++ b/prose/api.go
@@ -83,6 +83,7 @@ type PostPageData struct {
 	Footer       template.HTML
 	Favicon      template.URL
 	Unlisted     bool
+	Diff         template.HTML
 }
 
 type TransparencyPageData struct {
@@ -399,6 +400,7 @@ func postHandler(w http.ResponseWriter, r *http.Request) {
 		favicon = readmeParsed.Favicon
 	}
 
+	diff := ""
 	post, err := dbpool.FindPostWithSlug(slug, user.ID, cfg.Space)
 	if err == nil {
 		parsedText, err := shared.ParseText(post.Text)
@@ -414,6 +416,14 @@ func postHandler(w http.ResponseWriter, r *http.Request) {
 			ogImageCard = parsedText.ImageCard
 		}
 
+		// if parsedText.Live {
+		if post.Data.Diff != "" {
+			str := "```diff\n" + post.Data.Diff + "\n```"
+			diffParsed, _ := shared.ParseText(str)
+			diff = diffParsed.Html
+		}
+		// }
+
 		// track visit
 		view, err := shared.AnalyticsVisitFromRequest(r, user.ID, cfg.Secret)
 		if err == nil {
@@ -451,6 +461,7 @@ func postHandler(w http.ResponseWriter, r *http.Request) {
 			Favicon:      template.URL(favicon),
 			Footer:       footerHTML,
 			Unlisted:     unlisted,
+			Diff:         template.HTML(diff),
 		}
 	} else {
 		// TODO: HACK to support imgs slugs inside prose
prose/html/post.page.tmpl link
+8 -1
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
diff --git a/prose/html/post.page.tmpl b/prose/html/post.page.tmpl
index 3811a4c..e667978 100644
--- a/prose/html/post.page.tmpl
+++ b/prose/html/post.page.tmpl
@@ -62,9 +62,16 @@
     </div>
 </header>
 <main>
+    {{if .Diff}}
+    <details>
+      <summary>diff</summary>
+      <div class="md">{{.Diff}}</div>
+    </details>
+    {{end}}
+
     <article class="md">
         {{.Contents}}
-        <div id="post-footer">{{.Footer}}</div>
+        <div id="post-footer" class="box">{{.Footer}}</div>
     </article>
 </main>
 {{template "footer" .}}
prose/scp_hooks.go link
+28 -3
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
diff --git a/prose/scp_hooks.go b/prose/scp_hooks.go
index 3cb64e6..707bb61 100644
--- a/prose/scp_hooks.go
+++ b/prose/scp_hooks.go
@@ -1,6 +1,7 @@
 package prose
 
 import (
+	"bytes"
 	"fmt"
 	"strings"
 
@@ -47,6 +48,26 @@ func (p *MarkdownHooks) FileValidate(s ssh.Session, data *filehandlers.PostMetaD
 	return true, nil
 }
 
+func diffText(diffs []diffmatchpatch.Diff) string {
+	var buff bytes.Buffer
+	for _, diff := range diffs {
+		text := diff.Text
+
+		switch diff.Type {
+		case diffmatchpatch.DiffInsert:
+			_, _ = buff.WriteString("+")
+			_, _ = buff.WriteString(text)
+		case diffmatchpatch.DiffDelete:
+			_, _ = buff.WriteString("-")
+			_, _ = buff.WriteString(text)
+		case diffmatchpatch.DiffEqual:
+			_, _ = buff.WriteString(text)
+		}
+	}
+
+	return buff.String()
+}
+
 func (p *MarkdownHooks) FileMeta(s ssh.Session, data *filehandlers.PostMetaData) error {
 	parsedText, err := shared.ParseText(data.Text)
 	if err != nil {
@@ -63,9 +84,13 @@ func (p *MarkdownHooks) FileMeta(s ssh.Session, data *filehandlers.PostMetaData)
 	data.Tags = parsedText.Tags
 	data.Description = parsedText.Description
 
-	dmp := diffmatchpatch.New()
-	diffs := dmp.DiffMain(data.Cur.Text, data.Text, false)
-	data.Data.Diff = dmp.DiffPrettyText(diffs)
+	if data.Cur.Text == data.Text {
+	} else {
+		dmp := diffmatchpatch.New()
+		diffs := dmp.DiffMain(data.Cur.Text, data.Text, false)
+		fmt.Println(diffs)
+		data.Data.Diff = diffText(diffs)
+	}
 
 	if parsedText.PublishAt != nil && !parsedText.PublishAt.IsZero() {
 		data.PublishAt = parsedText.MetaData.PublishAt
shared/mdparser.go link
+7 -0
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
diff --git a/shared/mdparser.go b/shared/mdparser.go
index 54f234b..1ddf0b1 100644
--- a/shared/mdparser.go
+++ b/shared/mdparser.go
@@ -40,6 +40,7 @@ type MetaData struct {
 	ImageCard   string
 	Favicon     string
 	Hidden      bool
+	Live        bool
 }
 
 type ParsedText struct {
@@ -303,6 +304,12 @@ func ParseText(text string) (*ParsedText, error) {
 	}
 	parsed.MetaData.Favicon = favicon
 
+	live, err := toBool(metaData["live"])
+	if err != nil {
+		return &parsed, fmt.Errorf("front-matter field (%s): %w", "live", err)
+	}
+	parsed.MetaData.Live = live
+
 	var publishAt *time.Time = nil
 	date, err := toString(metaData["date"])
 	if err != nil {