Commit a8e9b6c

Eric Bower  ·  2026-10-01 21:05:05 -0400 EDT
parent fed4e39
chore: remove pastes service

We are going to keep pastes running but we want to remove it from our git tree
since it doesn't receive updates at this point and is mostly clutter.
30 files changed,  +10, -2527
+2, -4
......@@ -12,11 +12,9 @@ WRITE?=0
1212 smol:
1313 curl https://pico.sh/smol.css -o ./pkg/apps/prose/public/smol-v2.css
1414 cat ./pkg/apps/prose/artifacts/main.css >> ./pkg/apps/prose/public/smol-v2.css
15- curl https://pico.sh/smol.css -o ./pkg/apps/pastes/public/smol.css
1615 .PHONY: smol
1716
1817 css:
19- cp ./syntax.css ./pkg/apps/pastes/public/syntax.css
2018 cp ./syntax.css ./pkg/apps/prose/public/syntax.css
2119 .PHONY: css
2220
......@@ -78,7 +76,7 @@ bp-%: bp-setup
7876 $(DOCKER_BUILDX_BUILD) "ghcr.io/picosh/pico/$*-web:$(DOCKER_TAG)" --build-arg "APP=$*" --target release-web .
7977 .PHONY: bp-%
8078
81-bp-all: bp-prose bp-pastes bp-feeds bp-pgs bp-auth bp-bouncer bp-pipe bp-pgs-cdn
79+bp-all: bp-prose bp-feeds bp-pgs bp-auth bp-bouncer bp-pipe bp-pgs-cdn
8280 .PHONY: bp-all
8381
8482 LDFLAGS?=-s -w
......@@ -100,7 +98,7 @@ build-%:
10098 go build -ldflags="$(LDFLAGS)" -o "build/$*-ssh" "./cmd/$*/ssh"
10199 .PHONY: build-%
102100
103-build: build-prose build-pastes build-feeds build-pgs build-pgs-cdn build-auth build-pico build-pipe
101+build: build-prose build-feeds build-pgs build-pgs-cdn build-auth build-pico build-pipe
104102 .PHONY: build
105103
106104 scripts:
+1, -14
......@@ -12,8 +12,7 @@ without needing to install anything. We accomplish this with the `ssh` tools you
1212 already have installed on your system.
1313
1414 Want to publish a blog post? Use rsync, scp, or sftp. Want to publish a website?
15-Use rsync, scp, or sftp. Want to share a code snippet with a colleague? Use
16-rsync, scp, or sftp. Hopefully you see the trend.
15+Use rsync, scp, or sftp. Hopefully you see the trend.
1716
1817 - [pages](https://pico.sh/pgs): A static site hosting platform using `ssh` for
1918 site deployments. `ssh`.
......@@ -22,8 +21,6 @@ rsync, scp, or sftp. Hopefully you see the trend.
2221 - [prose](https://pico.sh/prose): A blog platform using `ssh` for content
2322 management.
2423 - [rss-to-email](https://pico.sh/feeds): An RSS email notification service using `ssh`.
25-- [pastes](https://pico.sh/pastes): Upload code snippets using rsync, scp, and
26- sftp.
2724
2825 ## Deploy a site with a single command
2926
......@@ -81,16 +78,6 @@ scp hello-world.md prose.sh:/
8178 Congrats! You just published a blog article, accessible here:
8279 https://{user}.prose.sh/hello-world
8380
84-## Easily share code snippets
85-
86-Pipe some stdout to us:
87-
88-```bash
89-git diff | ssh pastes.sh changes.patch
90-```
91-
92-And instantly share your code snippets: https://{user}.pastes.sh/changes.patch
93-
9481 ## Receive email notifications for your favorite rss feeds
9582
9683 Create a blogs.txt file:
+0, -7
......@@ -1,7 +0,0 @@
1-package main
2-
3-import "github.com/picosh/pico/pkg/apps/pastes"
4-
5-func main() {
6- pastes.StartSshServer()
7-}
+0, -7
......@@ -1,7 +0,0 @@
1-package main
2-
3-import "github.com/picosh/pico/pkg/apps/pastes"
4-
5-func main() {
6- pastes.StartApiServer()
7-}
+1, -1
......@@ -33,7 +33,7 @@ zmx run cdn docker buildx build --push --platform "$DOCKER_PLATFORM" -t
3333 zmx run standalone docker buildx build --push --platform "$DOCKER_PLATFORM" -t "ghcr.io/picosh/pgs:$DOCKER_TAG" --target release -f Dockerfile.standalone .
3434 zmx wait "*"
3535
36-apps=("prose" "pastes" "pgs" "feeds" "pipe")
36+apps=("prose" "pgs" "feeds" "pipe")
3737 for APP in "${apps[@]}"; do
3838 zmx run "$APP-ssh" -d docker buildx build --push --platform "$DOCKER_PLATFORM" -t "ghcr.io/picosh/pico/$APP-ssh:$DOCKER_TAG" --build-arg "APP=$APP" --target release-ssh .
3939 zmx run "$APP-web" -d docker buildx build --push --platform "$DOCKER_PLATFORM" -t "ghcr.io/picosh/pico/$APP-web:$DOCKER_TAG" --build-arg "APP=$APP" --target release-web .
+0, -412
......@@ -1,412 +0,0 @@
1-package pastes
2-
3-import (
4- "fmt"
5- "html/template"
6- "net/http"
7- "net/url"
8- "os"
9- "time"
10-
11- "github.com/picosh/pico/pkg/db"
12- "github.com/picosh/pico/pkg/db/postgres"
13- "github.com/picosh/pico/pkg/shared"
14- "github.com/picosh/pico/pkg/shared/router"
15- "github.com/prometheus/client_golang/prometheus/promhttp"
16-)
17-
18-type PageData struct {
19- Site shared.SitePageData
20-}
21-
22-type PostItemData struct {
23- URL template.URL
24- BlogURL template.URL
25- Username string
26- Title string
27- Description string
28- PublishAtISO string
29- PublishAt string
30- UpdatedAtISO string
31- UpdatedTimeAgo string
32- Padding string
33-}
34-
35-type BlogPageData struct {
36- Site shared.SitePageData
37- PageTitle string
38- URL template.URL
39- RSSURL template.URL
40- Username string
41- Header *HeaderTxt
42- Posts []PostItemData
43-}
44-
45-type PostPageData struct {
46- Site shared.SitePageData
47- PageTitle string
48- URL template.URL
49- RawURL template.URL
50- BlogURL template.URL
51- Title string
52- Description string
53- Username string
54- BlogName string
55- Contents template.HTML
56- Text string
57- PublishAtISO string
58- PublishAt string
59- ExpiresAt string
60- Unlisted bool
61-}
62-
63-type Link struct {
64- URL string
65- Text string
66-}
67-
68-type HeaderTxt struct {
69- Title string
70- Bio string
71- Nav []Link
72- HasLinks bool
73-}
74-
75-func blogHandler(w http.ResponseWriter, r *http.Request) {
76- username := router.GetUsernameFromRequest(r)
77- dbpool := router.GetDB(r)
78- blogger := router.GetLogger(r)
79- logger := blogger.With("user", username)
80- cfg := router.GetCfg(r)
81-
82- user, err := dbpool.FindUserByName(username)
83- if err != nil {
84- logger.Info("user not found")
85- http.Error(w, "user not found", http.StatusNotFound)
86- return
87- }
88- logger = shared.LoggerWithUser(blogger, user)
89-
90- pager, err := dbpool.FindPostsByUser(&db.Pager{Num: 1000, Page: 0}, user.ID, cfg.Space)
91- if err != nil {
92- logger.Error("could not find posts for user", "err", err.Error())
93- http.Error(w, "could not fetch posts for blog", http.StatusInternalServerError)
94- return
95- }
96-
97- posts := pager.Data
98-
99- ts, err := router.RenderTemplate(cfg, []string{
100- cfg.StaticPath("html/blog.page.tmpl"),
101- })
102-
103- if err != nil {
104- logger.Error("could not render template", "err", err)
105- http.Error(w, err.Error(), http.StatusInternalServerError)
106- return
107- }
108-
109- headerTxt := &HeaderTxt{
110- Title: GetBlogName(username),
111- Bio: "",
112- }
113-
114- curl := shared.CreateURLFromRequest(cfg, r)
115- postCollection := make([]PostItemData, 0, len(posts))
116- for _, post := range posts {
117- p := PostItemData{
118- URL: template.URL(cfg.FullPostURL(curl, post.Username, post.Slug)),
119- BlogURL: template.URL(cfg.FullBlogURL(curl, post.Username)),
120- Title: post.Filename,
121- PublishAt: post.PublishAt.Format(time.DateOnly),
122- PublishAtISO: post.PublishAt.Format(time.RFC3339),
123- UpdatedTimeAgo: shared.TimeAgo(post.UpdatedAt),
124- UpdatedAtISO: post.UpdatedAt.Format(time.RFC3339),
125- }
126- postCollection = append(postCollection, p)
127- }
128-
129- data := BlogPageData{
130- Site: *cfg.GetSiteData(),
131- PageTitle: headerTxt.Title,
132- URL: template.URL(cfg.FullBlogURL(curl, username)),
133- RSSURL: template.URL(cfg.RssBlogURL(curl, username, "")),
134- Header: headerTxt,
135- Username: username,
136- Posts: postCollection,
137- }
138-
139- err = ts.Execute(w, data)
140- if err != nil {
141- logger.Error("could not execute tempalte", "err", err)
142- http.Error(w, err.Error(), http.StatusInternalServerError)
143- }
144-}
145-
146-func GetPostTitle(post *db.Post) string {
147- if post.Description == "" {
148- return post.Title
149- }
150-
151- return fmt.Sprintf("%s: %s", post.Title, post.Description)
152-}
153-
154-func GetBlogName(username string) string {
155- return fmt.Sprintf("%s's pastes", username)
156-}
157-
158-func postHandler(w http.ResponseWriter, r *http.Request) {
159- username := router.GetUsernameFromRequest(r)
160- subdomain := router.GetSubdomain(r)
161- cfg := router.GetCfg(r)
162-
163- var slug string
164- if !cfg.IsSubdomains() || subdomain == "" {
165- slug, _ = url.PathUnescape(router.GetField(r, 1))
166- } else {
167- slug, _ = url.PathUnescape(router.GetField(r, 0))
168- }
169-
170- dbpool := router.GetDB(r)
171- blogger := router.GetLogger(r)
172- logger := blogger.With("slug", slug, "user", username)
173-
174- user, err := dbpool.FindUserByName(username)
175- if err != nil {
176- logger.Info("paste not found")
177- http.Error(w, "paste not found", http.StatusNotFound)
178- return
179- }
180- logger = shared.LoggerWithUser(logger, user)
181-
182- blogName := GetBlogName(username)
183-
184- var data PostPageData
185- post, err := dbpool.FindPostWithSlug(slug, user.ID, cfg.Space)
186- if err == nil {
187- logger = logger.With("filename", post.Filename)
188- logger.Info("paste found")
189- expiresAt := "never"
190- unlisted := false
191- parsedText := ""
192- // we dont want to syntax highlight huge files
193- if post.FileSize > 1*shared.MB {
194- logger.Warn("paste too large to parse and apply syntax highlighting")
195- parsedText = post.Text
196- } else {
197- parsedText, err = ParseText(post.Filename, post.Text)
198- if err != nil {
199- logger.Error("could not parse text", "err", err)
200- }
201- if post.ExpiresAt != nil {
202- expiresAt = post.ExpiresAt.Format(time.DateOnly)
203- }
204-
205- if post.Hidden {
206- unlisted = true
207- }
208- }
209-
210- data = PostPageData{
211- Site: *cfg.GetSiteData(),
212- PageTitle: post.Filename,
213- URL: template.URL(cfg.PostURL(post.Username, post.Slug)),
214- RawURL: template.URL(cfg.RawPostURL(post.Username, post.Slug)),
215- BlogURL: template.URL(cfg.BlogURL(username)),
216- Description: post.Description,
217- Title: post.Filename,
218- PublishAt: post.PublishAt.Format(time.DateOnly),
219- PublishAtISO: post.PublishAt.Format(time.RFC3339),
220- Username: username,
221- BlogName: blogName,
222- Contents: template.HTML(parsedText),
223- Text: post.Text,
224- ExpiresAt: expiresAt,
225- Unlisted: unlisted,
226- }
227- } else {
228- logger.Info("paste not found")
229- data = PostPageData{
230- Site: *cfg.GetSiteData(),
231- PageTitle: "Paste not found",
232- Description: "Paste not found",
233- Title: "Paste not found",
234- BlogURL: template.URL(cfg.BlogURL(username)),
235- PublishAt: time.Now().Format(time.DateOnly),
236- PublishAtISO: time.Now().Format(time.RFC3339),
237- Username: username,
238- BlogName: blogName,
239- Contents: "oops! we can't seem to find this post.",
240- Text: "oops! we can't seem to find this post.",
241- ExpiresAt: "",
242- }
243- }
244-
245- ts, err := router.RenderTemplate(cfg, []string{
246- cfg.StaticPath("html/post.page.tmpl"),
247- })
248-
249- if err != nil {
250- http.Error(w, err.Error(), http.StatusInternalServerError)
251- }
252-
253- logger.Info("serving paste")
254- err = ts.Execute(w, data)
255- if err != nil {
256- logger.Error("could not execute template", "err", err)
257- http.Error(w, err.Error(), http.StatusInternalServerError)
258- }
259-}
260-
261-func postHandlerRaw(w http.ResponseWriter, r *http.Request) {
262- username := router.GetUsernameFromRequest(r)
263- subdomain := router.GetSubdomain(r)
264- cfg := router.GetCfg(r)
265-
266- var slug string
267- if !cfg.IsSubdomains() || subdomain == "" {
268- slug, _ = url.PathUnescape(router.GetField(r, 1))
269- } else {
270- slug, _ = url.PathUnescape(router.GetField(r, 0))
271- }
272-
273- dbpool := router.GetDB(r)
274- blogger := router.GetLogger(r)
275- logger := blogger.With("user", username, "slug", slug)
276-
277- user, err := dbpool.FindUserByName(username)
278- if err != nil {
279- logger.Info("user not found")
280- http.Error(w, "user not found", http.StatusNotFound)
281- return
282- }
283- logger = shared.LoggerWithUser(blogger, user)
284-
285- post, err := dbpool.FindPostWithSlug(slug, user.ID, cfg.Space)
286- if err != nil {
287- logger.Info("paste not found")
288- http.Error(w, "paste not found", http.StatusNotFound)
289- return
290- }
291- logger = logger.With("filename", post.Filename)
292- logger.Info("raw paste found")
293-
294- w.Header().Set("Content-Type", "text/plain")
295- _, err = w.Write([]byte(post.Text))
296- if err != nil {
297- logger.Error("write error", "err", err)
298- }
299-}
300-
301-func serveFile(file string, contentType string) http.HandlerFunc {
302- return func(w http.ResponseWriter, r *http.Request) {
303- logger := router.GetLogger(r)
304- cfg := router.GetCfg(r)
305-
306- contents, err := os.ReadFile(cfg.StaticPath(fmt.Sprintf("public/%s", file)))
307- if err != nil {
308- logger.Error("could not read file", "err", err)
309- http.Error(w, "file not found", 404)
310- }
311- w.Header().Add("Content-Type", contentType)
312-
313- _, err = w.Write(contents)
314- if err != nil {
315- logger.Error("could not write contents", "err", err)
316- http.Error(w, "server error", 500)
317- }
318- }
319-}
320-
321-func createStaticRoutes() []router.Route {
322- return []router.Route{
323- router.NewRoute("GET", "/main.css", serveFile("main.css", "text/css")),
324- router.NewRoute("GET", "/smol.css", serveFile("smol.css", "text/css")),
325- router.NewRoute("GET", "/syntax.css", serveFile("syntax.css", "text/css")),
326- router.NewRoute("GET", "/card.png", serveFile("card.png", "image/png")),
327- router.NewRoute("GET", "/favicon-16x16.png", serveFile("favicon-16x16.png", "image/png")),
328- router.NewRoute("GET", "/favicon-32x32.png", serveFile("favicon-32x32.png", "image/png")),
329- router.NewRoute("GET", "/apple-touch-icon.png", serveFile("apple-touch-icon.png", "image/png")),
330- router.NewRoute("GET", "/favicon.ico", serveFile("favicon.ico", "image/x-icon")),
331- router.NewRoute("GET", "/robots.txt", serveFile("robots.txt", "text/plain")),
332- }
333-}
334-
335-func createMainRoutes(staticRoutes []router.Route) []router.Route {
336- routes := []router.Route{
337- router.NewRoute("GET", "/", router.CreatePageHandler("html/marketing.page.tmpl")),
338- router.NewRoute("GET", "/check", router.CheckHandler),
339- router.NewRoute("GET", "/_metrics", promhttp.Handler().ServeHTTP),
340- }
341-
342- routes = append(
343- routes,
344- staticRoutes...,
345- )
346-
347- routes = append(
348- routes,
349- router.NewRoute("GET", "/([^/]+)", blogHandler),
350- router.NewRoute("GET", "/([^/]+)/([^/]+)", postHandler),
351- router.NewRoute("GET", "/([^/]+)/([^/]+)/raw", postHandlerRaw),
352- router.NewRoute("GET", "/raw/([^/]+)/([^/]+)", postHandlerRaw),
353- )
354-
355- return routes
356-}
357-
358-func createSubdomainRoutes(staticRoutes []router.Route) []router.Route {
359- routes := []router.Route{
360- router.NewRoute("GET", "/", blogHandler),
361- }
362-
363- routes = append(
364- routes,
365- staticRoutes...,
366- )
367-
368- routes = append(
369- routes,
370- router.NewRoute("GET", "/([^/]+)", postHandler),
371- router.NewRoute("GET", "/([^/]+)/raw", postHandlerRaw),
372- router.NewRoute("GET", "/raw/([^/]+)", postHandlerRaw),
373- )
374-
375- return routes
376-}
377-
378-func StartApiServer() {
379- cfg := NewConfigSite("pastes-web")
380- db := postgres.NewDB(cfg.DbURL, cfg.Logger)
381- defer func() {
382- _ = db.Close()
383- }()
384- logger := cfg.Logger
385-
386- go CronDeleteExpiredPosts(cfg, db)
387-
388- staticRoutes := createStaticRoutes()
389-
390- if cfg.Debug {
391- staticRoutes = router.CreatePProfRoutes(staticRoutes)
392- }
393-
394- mainRoutes := createMainRoutes(staticRoutes)
395- subdomainRoutes := createSubdomainRoutes(staticRoutes)
396-
397- apiConfig := &router.ApiConfig{
398- Cfg: cfg,
399- Dbpool: db,
400- }
401- handler := router.CreateServe(mainRoutes, subdomainRoutes, apiConfig)
402- router := http.HandlerFunc(handler)
403-
404- portStr := fmt.Sprintf(":%s", cfg.Port)
405- logger.Info(
406- "Starting server on port",
407- "port", cfg.Port,
408- "domain", cfg.Domain,
409- )
410-
411- logger.Error(http.ListenAndServe(portStr, router).Error())
412-}
+0, -27
......@@ -1,27 +0,0 @@
1-package pastes
2-
3-import (
4- "strings"
5-
6- "github.com/picosh/pico/pkg/shared"
7-)
8-
9-func NewConfigSite(service string) *shared.ConfigSite {
10- debug := shared.GetEnv("PASTES_DEBUG", "0")
11- domain := shared.GetEnv("PASTES_DOMAIN", "pastes.sh")
12- port := shared.GetEnv("PASTES_WEB_PORT", "3000")
13- dbURL := shared.GetEnv("DATABASE_URL", "")
14- protocol := shared.GetEnv("PASTES_PROTOCOL", "https")
15- withPipe := strings.ToLower(shared.GetEnv("PICO_PIPE_ENABLED", "true")) == "true"
16-
17- return &shared.ConfigSite{
18- Debug: debug == "1",
19- Domain: domain,
20- Port: port,
21- Protocol: protocol,
22- DbURL: dbURL,
23- Space: "pastes",
24- Logger: shared.CreateLogger(service, withPipe),
25- MaxAssetSize: int64(3 * shared.MB),
26- }
27-}
+0, -39
......@@ -1,39 +0,0 @@
1-package pastes
2-
3-import (
4- "time"
5-
6- "github.com/picosh/pico/pkg/db"
7- "github.com/picosh/pico/pkg/shared"
8-)
9-
10-func deleteExpiredPosts(cfg *shared.ConfigSite, dbpool db.DB) error {
11- cfg.Logger.Info("checking for expired posts")
12- posts, err := dbpool.FindExpiredPosts(cfg.Space)
13- if err != nil {
14- return err
15- }
16-
17- postIds := []string{}
18- for _, post := range posts {
19- postIds = append(postIds, post.ID)
20- }
21-
22- cfg.Logger.Info("deleting expired posts", "len", len(postIds))
23- err = dbpool.RemovePosts(postIds)
24- if err != nil {
25- return err
26- }
27-
28- return nil
29-}
30-
31-func CronDeleteExpiredPosts(cfg *shared.ConfigSite, dbpool db.DB) {
32- for {
33- err := deleteExpiredPosts(cfg, dbpool)
34- if err != nil {
35- cfg.Logger.Error(err.Error())
36- }
37- time.Sleep(12 * time.Hour)
38- }
39-}
+0, -19
......@@ -1,19 +0,0 @@
1-{{define "base"}}
2-<!doctype html>
3-<html lang="en">
4- <head>
5- <meta charset='utf-8'>
6- <meta name="viewport" content="width=device-width, initial-scale=1" />
7- <title>{{template "title" .}}</title>
8-
9- <link rel="icon" type="image/png" sizes="16x16" href="/favicon-16x16.png">
10-
11- <meta name="keywords" content="pastebin, paste, copy, snippets" />
12- {{template "meta" .}}
13-
14- <link rel="stylesheet" href="/smol.css" />
15- <link rel="stylesheet" href="/main.css" />
16- </head>
17- <body {{template "attrs" .}}>{{template "body" .}}</body>
18-</html>
19-{{end}}
+0, -46
......@@ -1,46 +0,0 @@
1-{{template "base" .}}
2-
3-{{define "title"}}{{.PageTitle}}{{end}}
4-
5-{{define "meta"}}
6-<meta name="description" content="{{if .Header.Bio}}{{.Header.Bio}}{{else}}{{.Header.Title}}{{end}}" />
7-
8-<meta property="og:type" content="website">
9-<meta property="og:site_name" content="{{.Site.Domain}}">
10-<meta property="og:url" content="{{.URL}}">
11-<meta property="og:title" content="{{.Header.Title}}">
12-{{if .Header.Bio}}<meta property="og:description" content="{{.Header.Bio}}">{{end}}
13-<meta property="og:image:width" content="300" />
14-<meta property="og:image:height" content="300" />
15-<meta itemprop="image" content="https://{{.Site.Domain}}/card.png" />
16-<meta property="og:image" content="https://{{.Site.Domain}}/card.png" />
17-
18-<meta property="twitter:card" content="summary">
19-<meta property="twitter:url" content="{{.URL}}">
20-<meta property="twitter:title" content="{{.Header.Title}}">
21-{{if .Header.Bio}}<meta property="twitter:description" content="{{.Header.Bio}}">{{end}}
22-<meta name="twitter:image" content="https://{{.Site.Domain}}/card.png" />
23-<meta name="twitter:image:src" content="https://{{.Site.Domain}}/card.png" />
24-{{end}}
25-
26-{{define "attrs"}}{{end}}
27-
28-{{define "body"}}
29-<header class="text-center">
30- <h1 class="text-2xl">{{.Header.Title}}</h1>
31- <hr class="my-2" />
32-</header>
33-<main>
34- <section class="posts">
35- {{range .Posts}}
36- <article class="my">
37- <div class="flex items-center">
38- <time datetime="{{.PublishAtISO}}" class="font-italic text-sm post-date">{{.PublishAt}}</time>
39- <div class="text-md flex-1 transform-none m-0"><a href="{{.URL}}">{{.Title}}</a></div>
40- </div>
41- </article>
42- {{end}}
43- </section>
44-</main>
45-{{template "footer" .}}
46-{{end}}
+0, -6
......@@ -1,6 +0,0 @@
1-{{define "footer"}}
2-<footer>
3- <hr class="my-2" />
4- published with <a href={{.Site.HomeURL}}>{{.Site.Domain}}</a>
5-</footer>
6-{{end}}
+0, -6
......@@ -1,6 +0,0 @@
1-{{define "marketing-footer"}}
2-<footer>
3- <hr />
4- <p class="font-italic">Built and maintained by <a href="https://pico.sh">pico.sh</a>.</p>
5-</footer>
6-{{end}}
+0, -39
......@@ -1,39 +0,0 @@
1-{{template "base" .}}
2-
3-{{define "title"}}{{.Site.Domain}} -- a pastebin for hackers{{end}}
4-
5-{{define "meta"}}
6-<meta name="description" content="a pastebin for hackers" />
7-
8-<meta property="og:type" content="website">
9-<meta property="og:site_name" content="{{.Site.Domain}}">
10-<meta property="og:url" content="https://{{.Site.Domain}}">
11-<meta property="og:title" content="{{.Site.Domain}}">
12-<meta property="og:description" content="a pastebin for hackers">
13-
14-<meta name="twitter:card" content="summary" />
15-<meta property="twitter:url" content="https://{{.Site.Domain}}">
16-<meta property="twitter:title" content="{{.Site.Domain}}">
17-<meta property="twitter:description" content="a pastebin for hackers">
18-<meta name="twitter:image" content="https://{{.Site.Domain}}/card.png" />
19-<meta name="twitter:image:src" content="https://{{.Site.Domain}}/card.png" />
20-
21-<meta property="og:image:width" content="300" />
22-<meta property="og:image:height" content="300" />
23-<meta itemprop="image" content="https://{{.Site.Domain}}/card.png" />
24-<meta property="og:image" content="https://{{.Site.Domain}}/card.png" />
25-{{end}}
26-
27-{{define "attrs"}}{{end}}
28-
29-{{define "body"}}
30-<header class="text-center">
31- <h1 class="text-2xl font-bold">{{.Site.Domain}}</h1>
32- <p class="text-lg">a pastebin for hackers</p>
33- <div>
34- <a href="https://pico.sh/pastes" class="btn-link mt inline-block">LEARN MORE</a>
35- </div>
36-</header>
37-
38-{{template "marketing-footer" .}}
39-{{end}}
+0, -62
......@@ -1,62 +0,0 @@
1-{{template "base" .}}
2-
3-{{define "title"}}{{.PageTitle}}{{end}}
4-
5-{{define "meta"}}
6-<meta property="og:type" content="website">
7-<meta property="og:site_name" content="{{.Site.Domain}}">
8-<meta property="og:url" content="{{.URL}}">
9-<meta property="og:title" content="{{.Title}}">
10-<meta property="og:image:width" content="300" />
11-<meta property="og:image:height" content="300" />
12-<meta itemprop="image" content="https://{{.Site.Domain}}/card.png" />
13-<meta property="og:image" content="https://{{.Site.Domain}}/card.png" />
14-
15-<meta property="twitter:card" content="summary">
16-<meta property="twitter:url" content="{{.URL}}">
17-<meta property="twitter:title" content="{{.Title}}">
18-<meta name="twitter:image" content="https://{{.Site.Domain}}/card.png" />
19-<meta name="twitter:image:src" content="https://{{.Site.Domain}}/card.png" />
20-
21-<link rel="stylesheet" href="/syntax.css" />
22-{{end}}
23-
24-{{define "attrs"}}id="post"{{end}}
25-
26-{{define "body"}}
27-<header>
28- <h1 class="text-2xl font-bold transform-none">{{.Title}}</h1>
29- <p class="font-bold m-0">
30- {{if .Unlisted}} <code>unlisted</code>{{end}}
31- <time datetime="{{.PublishAtISO}}">{{.PublishAt}}</time>
32- <span> &middot; <span>
33- <a href="{{.BlogURL}}">{{.BlogName}}</a>
34- <span> &middot; <span>
35- <a href="{{.RawURL}}">raw</a>
36- </p>
37- <p class="font-bold m-0">expires: {{.ExpiresAt}}</p>
38- <script>
39- function init() {
40- const btn = document.querySelector("#copy-btn");
41- const res = document.querySelector("#copy-result");
42- const text = "{{.Text}}";
43- btn.addEventListener("click", () => {
44- res.innerHtml = "";
45- navigator.clipboard.writeText(text)
46- .then(() => res.innerHTML = "success!");
47- })
48- }
49- document.addEventListener("DOMContentLoaded", init);
50- </script>
51-</header>
52-<main>
53- <div class="flex gap items-center">
54- <button id="copy-btn" class="btn" style="background-color: transparent;">copy</button>
55- <div id="copy-result" class=""></div>
56- </div>
57- <article>
58- {{.Contents}}
59- </article>
60-</main>
61-{{template "footer" .}}
62-{{end}}
+0, -34
......@@ -1,34 +0,0 @@
1-package pastes
2-
3-import (
4- "bytes"
5-
6- "github.com/alecthomas/chroma/v2/formatters/html"
7- "github.com/alecthomas/chroma/v2/lexers"
8- "github.com/alecthomas/chroma/v2/styles"
9-)
10-
11-func ParseText(filename string, text string) (string, error) {
12- formatter := html.New(
13- html.WithLineNumbers(true),
14- html.WithLinkableLineNumbers(true, ""),
15- html.WithClasses(true),
16- )
17- lexer := lexers.Match(filename)
18- if lexer == nil {
19- lexer = lexers.Analyse(text)
20- }
21- if lexer == nil {
22- lexer = lexers.Get("plaintext")
23- }
24- iterator, err := lexer.Tokenise(nil, text)
25- if err != nil {
26- return text, err
27- }
28- var buf bytes.Buffer
29- err = formatter.Format(&buf, styles.Get("dracula"), iterator)
30- if err != nil {
31- return text, err
32- }
33- return buf.String(), nil
34-}
+0, -0
Binaries are not rendered as diffs.
+0, -0
Binaries are not rendered as diffs.
+0, -0
Binaries are not rendered as diffs.
+0, -0
Binaries are not rendered as diffs.
+0, -11
......@@ -1,11 +0,0 @@
1-body {
2- max-width: 1025px;
3-}
4-
5-.post-date {
6- width: 110px;
7-}
8-
9-.transform-none {
10- text-transform: none;
11-}
+0, -2
......@@ -1,2 +0,0 @@
1-User-agent: *
2-Allow: /
+0, -875
......@@ -1,875 +0,0 @@
1-*,
2-::before,
3-::after {
4- box-sizing: border-box;
5-}
6-
7-::-moz-focus-inner {
8- border-style: none;
9- padding: 0;
10-}
11-:-moz-focusring {
12- outline: 1px dotted ButtonText;
13-}
14-:-moz-ui-invalid {
15- box-shadow: none;
16-}
17-
18-:root {
19- --line-height: 1.3rem;
20- --grid-height: 0.65rem;
21- --font-mono: ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, "Liberation Mono", monospace;
22-}
23-
24-@media (prefers-color-scheme: light) {
25- :root {
26- --white: #2e3f53;
27- --white-light: #cfe0f4;
28- --white-dark: #6c6a6a;
29- --code: #52576f;
30- --pre: #e1e7ee;
31- --bg-color: #f4f4f4;
32- --text-color: #24292f;
33- --link-color: #005cc5;
34- --visited: #6f42c1;
35- --blockquote: #005cc5;
36- --blockquote-bg: #cfe0f4;
37- --hover: #c11e7a;
38- --grey: #ccc;
39- --grey-light: #6a708e;
40- --green: #1a7f37;
41- }
42-}
43-
44-@media (prefers-color-scheme: dark) {
45- :root {
46- --white: #f2f2f2;
47- --white-light: #f2f2f2;
48- --white-dark: #e8e8e8;
49- --code: #414558;
50- --pre: #252525;
51- --bg-color: #282a36;
52- --text-color: #f2f2f2;
53- --link-color: #8be9fd;
54- --visited: #bd93f9;
55- --blockquote: #bd93f9;
56- --blockquote-bg: #353548;
57- --hover: #ff80bf;
58- --grey: #414558;
59- --grey-light: #6a708e;
60- --green: #50fa7b;
61- }
62-}
63-
64-html {
65- background-color: var(--bg-color);
66- color: var(--text-color);
67- line-height: var(--line-height);
68- font-family:
69- -apple-system,
70- BlinkMacSystemFont,
71- "Segoe UI",
72- Roboto,
73- Oxygen,
74- Ubuntu,
75- Cantarell,
76- "Fira Sans",
77- "Droid Sans",
78- "Helvetica Neue",
79- Arial,
80- sans-serif,
81- "Apple Color Emoji",
82- "Segoe UI Emoji",
83- "Segoe UI Symbol";
84- -webkit-text-size-adjust: 100%;
85- -moz-tab-size: 4;
86- -o-tab-size: 4;
87- tab-size: 4;
88-}
89-
90-body {
91- margin: 0 auto;
92-}
93-
94-img {
95- max-width: 100%;
96- height: auto;
97-}
98-
99-b,
100-strong {
101- font-weight: bold;
102-}
103-
104-code,
105-kbd,
106-samp,
107-pre {
108- font-family: var(--font-mono);
109- font-variant-numeric: tabular-nums;
110-}
111-
112-code,
113-kbd,
114-samp {
115- border: 2px solid var(--code);
116-}
117-
118-pre > code {
119- background-color: inherit;
120- padding: 0;
121- border: none;
122- border-radius: 0;
123-}
124-
125-code {
126- border-radius: 0.3rem;
127- padding: 0.025rem 0.3rem;
128-}
129-
130-pre {
131- border-radius: 1px;
132- padding: var(--line-height);
133- overflow-x: auto;
134- background-color: var(--pre) !important;
135-}
136-
137-small {
138- font-size: 0.8rem;
139-}
140-
141-details {
142- border: 2px solid var(--grey-light);
143- padding: calc(var(--grid-height) - 2px) 1ch;
144- margin-bottom: var(--grid-height);
145-}
146-
147-details[open] summary {
148- margin-bottom: var(--grid-height);
149-}
150-
151-summary {
152- display: list-item;
153- cursor: pointer;
154-}
155-
156-h1,
157-h2,
158-h3,
159-h4,
160-h5,
161-h6 {
162- margin: 0;
163- padding: 0;
164- border: 0;
165- font-family: var(--font-mono);
166- font-weight: bold;
167-}
168-
169-h1 {
170- font-size: 1.6rem;
171- line-height: calc(var(--line-height) * 1.5);
172- margin-top: calc(var(--line-height) * 2.5);
173- margin-bottom: var(--grid-height);
174-}
175-
176-h2 {
177- font-size: 1.3rem;
178- line-height: var(--line-height);
179- margin-top: calc(var(--line-height) * 2);
180- margin-bottom: var(--grid-height);
181-}
182-
183-h3 {
184- font-size: 1.1rem;
185- line-height: var(--line-height);
186- margin-top: calc(var(--line-height) * 1.5);
187- margin-bottom: var(--grid-height);
188-}
189-
190-h4,
191-h5,
192-h6 {
193- font-size: 1rem;
194- line-height: var(--line-height);
195- margin-top: var(--line-height);
196- margin-bottom: var(--grid-height);
197- color: var(--white-dark);
198-}
199-
200-
201-path {
202- fill: var(--text-color);
203- stroke: var(--text-color);
204-}
205-
206-hr {
207- color: inherit;
208- border: 0;
209- height: 1px;
210- background: var(--grey-light);
211- margin: calc(var(--grid-height) - 1px) auto;
212-}
213-
214-hr.dashed {
215- border: none;
216- border-top: 1px dashed var(--grey-light);
217- background: none;
218- height: 0;
219- margin: calc(var(--grid-height) - 1px) auto;
220-}
221-
222-a {
223- color: var(--link-color);
224- text-decoration: underline;
225- text-decoration-thickness: 1px;
226- text-underline-offset: 2px;
227- padding: 0 2px;
228- border-radius: 1px;
229- transition: background-color 0.1s ease, color 0.1s ease;
230-}
231-
232-a:hover {
233- background-color: var(--link-color);
234- color: var(--bg-color);
235- text-decoration: none;
236-}
237-
238-a:visited {
239- color: var(--visited);
240-}
241-
242-a:visited:hover {
243- background-color: var(--visited);
244- color: var(--bg-color);
245- text-decoration: none;
246-}
247-
248-section {
249- margin-bottom: 1.4rem;
250-}
251-
252-section:last-child {
253- margin-bottom: 0;
254-}
255-
256-header {
257- margin: 1rem auto;
258-}
259-
260-p {
261- margin-top: var(--line-height);
262- margin-bottom: var(--line-height);
263-}
264-
265-article {
266- overflow-wrap: break-word;
267-}
268-
269-blockquote {
270- border-left: 5px solid var(--blockquote);
271- background-color: var(--blockquote-bg);
272- padding: var(--grid-height);
273- margin: var(--line-height) 0;
274-}
275-
276-blockquote > p {
277- margin: 0;
278-}
279-
280-blockquote code {
281- border: 1px solid var(--blockquote);
282-}
283-
284-ul,
285-ol {
286- padding: 0 15px;
287- list-style-position: inside;
288- list-style-type: square;
289- margin: var(--grid-height) 0;
290-}
291-
292-ul[style*="list-style-type: none;"] {
293- padding: 0;
294-}
295-
296-ol ul, ol ol, ul ol, ul ul {
297- padding: 0 0 0 var(--line-height);
298- margin: 0;
299-}
300-
301-li {
302- margin: var(--grid-height) 0;
303- padding: 0;
304-}
305-
306-li::marker {
307- line-height: 0;
308- color: var(--grey-light);
309-}
310-
311-li blockquote,
312-li pre {
313- display: inline-block;
314- margin: 0;
315- width: 100%;
316-}
317-
318-li pre {
319- padding: var(--grid-height);
320-}
321-
322-[role="list"] {
323- display: flex;
324- flex-direction: column;
325- gap: var(--grid-height);
326-}
327-
328-[role="list"] [role="list"] {
329- margin-left: 1.3rem;
330-}
331-
332-[role="listitem"] {
333- display: flex;
334- align-items: flex-start;
335- gap: 5px;
336-}
337-
338-[role="listitem"]:has(pre, img, blockquote) {
339- align-items: center;
340-}
341-
342-table {
343- border-collapse: collapse;
344- margin: var(--line-height) 0;
345-}
346-
347-th,
348-td {
349- border: 1px solid var(--white);
350- padding: var(--grid-height);
351-}
352-
353-footer {
354- text-align: center;
355- margin-bottom: calc(var(--line-height) * 3);
356-}
357-
358-dt {
359- font-weight: bold;
360-}
361-
362-dd {
363- margin-left: 0;
364-}
365-
366-dd:not(:last-child) {
367- margin-bottom: 0.5rem;
368-}
369-
370-figure {
371- margin: 0;
372-}
373-
374-sup {
375- line-height: 0;
376-}
377-
378-#toc {
379- margin-top: var(--line-height);
380-}
381-
382-.container {
383- max-width: 50em;
384- width: 100%;
385-}
386-
387-.container-sm {
388- max-width: 40em;
389- width: 100%;
390-}
391-
392-.mono,
393-time,
394-nav {
395- font-family: var(--font-mono);
396- font-variant-numeric: tabular-nums;
397-}
398-
399-.link-alt-hover,
400-.link-alt-hover:visited,
401-.link-alt-hover:visited:hover,
402-.link-alt-hover:hover {
403- color: var(--hover);
404- text-decoration: none;
405-}
406-
407-.link-alt-hover:visited:hover,
408-.link-alt-hover:hover {
409- background-color: transparent;
410- text-decoration: underline;
411-}
412-
413-.link-alt,
414-.link-alt:visited,
415-.link-alt:visited:hover,
416-.link-alt:hover {
417- color: var(--white);
418- text-decoration: none;
419-}
420-
421-.link-alt:visited:hover,
422-.link-alt:hover {
423- background-color: transparent;
424- text-decoration: underline;
425-}
426-
427-.link-grey,
428-.link-grey:visited {
429- color: var(--grey-light);
430- text-decoration: none;
431-}
432-
433-.link-grey:hover,
434-.link-grey:visited:hover {
435- background-color: transparent;
436- color: var(--text-color);
437- text-decoration: underline;
438-}
439-
440-.text-2xl code, .text-xl code, .text-lg code, .text-md code {
441- text-transform: none;
442-}
443-
444-.text-2xl {
445- font-size: 1.6rem;
446- font-weight: bold;
447- line-height: calc(var(--line-height) * 1.5);
448- margin-bottom: var(--grid-height);
449-}
450-
451-.text-xl {
452- font-size: 1.3rem;
453- font-weight: bold;
454- line-height: var(--line-height);
455- margin-bottom: var(--grid-height);
456-}
457-
458-.text-lg {
459- font-size: 1.1rem;
460- font-weight: bold;
461- line-height: var(--line-height);
462- margin-bottom: var(--grid-height);
463-}
464-
465-.text-md {
466- font-size: 1rem;
467- font-weight: bold;
468- line-height: var(--line-height);
469- margin-bottom: var(--grid-height);
470-}
471-
472-.text-right {
473- text-align: right;
474-}
475-
476-.text-sm {
477- font-size: 0.8rem;
478-}
479-
480-.w-full {
481- width: 100%;
482-}
483-
484-.border {
485- border: 2px solid var(--grey-light);
486-}
487-
488-.text-left {
489- text-align: left;
490-}
491-
492-.text-center {
493- text-align: center;
494-}
495-
496-.text-underline {
497- text-decoration: underline;
498- text-decoration-thickness: 2px;
499-}
500-
501-.text-hdr {
502- color: var(--hover);
503-}
504-
505-.font-bold {
506- font-weight: bold;
507-}
508-
509-.font-italic {
510- font-style: italic;
511-}
512-
513-.inline {
514- display: inline;
515-}
516-
517-.inline-block {
518- display: inline-block;
519-}
520-
521-.max-w-half {
522- max-width: 50%;
523-}
524-
525-.flex {
526- display: flex;
527-}
528-
529-.flex-col {
530- flex-direction: column;
531-}
532-
533-.flex-wrap {
534- flex-wrap: wrap;
535-}
536-
537-.items-center {
538- align-items: center;
539-}
540-
541-.m-0 {
542- margin: 0;
543-}
544-
545-.mt-0 {
546- margin-top: 0;
547-}
548-
549-.mt {
550- margin-top: var(--grid-height);
551-}
552-
553-.mt-2 {
554- margin-top: var(--line-height);
555-}
556-
557-.mt-4 {
558- margin-top: calc(var(--line-height) * 2);
559-}
560-
561-.mb {
562- margin-bottom: var(--grid-height);
563-}
564-
565-.mb-2 {
566- margin-bottom: var(--line-height);
567-}
568-
569-.mb-4 {
570- margin-bottom: calc(var(--line-height) * 2);
571-}
572-
573-.mr {
574- margin-right: 0.5rem;
575-}
576-
577-.ml {
578- margin-left: 0.5rem;
579-}
580-
581-.my {
582- margin-top: var(--grid-height);
583- margin-bottom: var(--grid-height);
584-}
585-
586-.my-2 {
587- margin-top: var(--line-height);
588- margin-bottom: var(--line-height);
589-}
590-
591-.my-4 {
592- margin-top: calc(var(--line-height) * 2);
593- margin-bottom: calc(var(--line-height) * 2);
594-}
595-
596-.p-0 {
597- padding: 0;
598-}
599-
600-.px {
601- padding-left: 0.5rem;
602- padding-right: 0.5rem;
603-}
604-
605-.px-4 {
606- padding-left: 2rem;
607- padding-right: 2rem;
608-}
609-
610-.py {
611- padding-top: var(--grid-height);
612- padding-bottom: var(--grid-height);
613-}
614-
615-.py-4 {
616- padding-top: calc(var(--line-height) * 2);
617- padding-bottom: calc(var(--line-height) * 2);
618-}
619-
620-.justify-between {
621- justify-content: space-between;
622-}
623-
624-.justify-center {
625- justify-content: center;
626-}
627-
628-.gap {
629- gap: var(--grid-height);
630-}
631-
632-.gap-2 {
633- gap: var(--line-height);
634-}
635-
636-.group {
637- display: flex;
638- flex-direction: column;
639- gap: var(--grid-height);
640-}
641-
642-.group-2 {
643- display: flex;
644- flex-direction: column;
645- gap: var(--line-height);
646-}
647-
648-.group-h {
649- display: flex;
650- gap: var(--grid-height);
651- align-items: center;
652-}
653-
654-.flex-1 {
655- flex: 1;
656-}
657-
658-.items-end {
659- align-items: end;
660-}
661-
662-.items-start {
663- align-items: start;
664-}
665-
666-.justify-end {
667- justify-content: end;
668-}
669-
670-.font-grey-light {
671- color: var(--grey-light);
672-}
673-
674-.hidden {
675- display: none;
676-}
677-
678-.align-right {
679- text-align: right;
680-}
681-
682-.text-transform-none {
683- text-transform: none;
684-}
685-
686-/* ==== MARKDOWN ==== */
687-
688-.md > :first-child {
689- margin-top: 0 !important;
690-}
691-
692-.md h1,
693-.md h2,
694-.md h3,
695-.md h4 {
696- padding: 0;
697- font-weight: bold;
698-}
699-
700-.md h1 a,
701-.md h2 a,
702-.md h3 a,
703-.md h4 a {
704- color: var(--grey-light);
705- text-decoration: none;
706-}
707-
708-.md h1 a:hover,
709-.md h2 a:hover,
710-.md h3 a:hover,
711-.md h4 a:hover {
712- background-color: transparent;
713- color: var(--hover);
714- text-decoration: underline;
715-}
716-
717-h1 code, h2 code, h3 code, h4 code {
718- text-transform: none;
719-}
720-
721-/* ==== HELPERS ==== */
722-
723-.logo-header {
724- line-height: 1;
725- display: inline-block;
726- background-color: #ff79c6;
727- background-image: linear-gradient(to right, #ff5555, #ff79c6, #f8f859);
728- color: transparent;
729- background-clip: text;
730- border: 3px solid #ff79c6;
731- padding: 8px 10px 10px 10px;
732- border-radius: 10px;
733- background-size: 100%;
734- margin: 0;
735- -webkit-background-clip: text;
736- -moz-background-clip: text;
737- -webkit-text-fill-color: transparent;
738- -moz-text-fill-color: transparent;
739-}
740-
741-.btn {
742- border: 2px solid var(--link-color);
743- color: var(--link-color);
744- padding: 0.4rem 1rem;
745- font-weight: bold;
746- display: inline-block;
747-}
748-
749-.btn:hover {
750- background-color: var(--link-color);
751- color: var(--bg-color);
752-}
753-
754-.btn-link,
755-.btn-link:visited {
756- border: 2px solid var(--link-color);
757- color: var(--link-color);
758- padding: var(--grid-height);
759- text-decoration: none;
760- font-weight: bold;
761- display: inline-block;
762-}
763-
764-.btn-link:hover,
765-.btn-link:visited:hover {
766- background-color: var(--link-color);
767- color: var(--bg-color);
768- text-decoration: none;
769-}
770-
771-.box {
772- border: 1px solid var(--grey-light);
773- padding: var(--grid-height);
774- background-color: var(--pre);
775-}
776-
777-.box-sm {
778- border: 1px solid var(--grey-light);
779- padding: var(--grid-height);
780- background-color: var(--pre);
781-}
782-
783-.box-dashed {
784- border: 1px dashed var(--grey-light);
785- padding: var(--grid-height);
786-}
787-
788-.box-alert {
789- border: 1px solid var(--hover);
790- padding: var(--line-height);
791-}
792-
793-.box-sm-alert {
794- border: 1px solid var(--hover);
795- padding: var(--grid-height);
796-}
797-
798-.text-green {
799- color: var(--green);
800-}
801-
802-.prompt,
803-pre.cmd::before {
804- content: "$ ";
805- color: var(--green);
806- font-weight: bold;
807-}
808-
809-.list-none {
810- list-style-type: none;
811-}
812-
813-.list-square {
814- list-style-type: square;
815-}
816-
817-.list-disc {
818- list-style-type: disc;
819-}
820-
821-.list-decimal {
822- list-style-type: decimal;
823-}
824-
825-.pill {
826- border: 1px solid var(--link-color);
827- color: var(--link-color);
828-}
829-
830-.pill-alert {
831- border: 1px solid var(--hover);
832- color: var(--hover);
833-}
834-
835-.pill-info {
836- border: 1px solid var(--visited);
837- color: var(--visited);
838-}
839-
840-@media only screen and (max-width: 40em) {
841- body {
842- padding: 0 var(--grid-height);
843- }
844-
845- header {
846- margin: 0;
847- }
848-
849- .flex-collapse {
850- flex-direction: column;
851- }
852-}
853-
854-body {
855- position: relative;
856-}
857-
858-/*
859-.debug-grid {
860- width: 100%;
861- height: 100%;
862- position: absolute;
863- top: 0;
864- left: 0;
865- right: 0;
866- bottom: 0;
867- z-index: 1000;
868- pointer-events: none;
869- background-image:
870- repeating-linear-gradient(rgba(255, 121, 198, 0.18) 0 1px, transparent 1px 100%),
871- repeating-linear-gradient(90deg, rgba(139, 233, 253, 0.10) 0 1px, transparent 1px 100%);
872- background-size: 1ch var(--grid-height);
873- margin: 0;
874-}
875-*/
+0, -700
......@@ -1,700 +0,0 @@
1-@media (prefers-color-scheme: light) {
2- /* Background */
3- .bg {
4- background-color: #ffffff;
5- }
6- /* PreWrapper */
7- .chroma {
8- background-color: #ffffff;
9- }
10- /* Other */
11- .chroma .x {
12- }
13- /* Error */
14- .chroma .err {
15- background-color: #a848a8;
16- }
17- /* CodeLine */
18- .chroma .cl {
19- }
20- /* LineTableTD */
21- .chroma .lntd {
22- vertical-align: top;
23- padding: 0;
24- margin: 0;
25- border: 0;
26- }
27- /* LineTable */
28- .chroma .lntable {
29- border-spacing: 0;
30- padding: 0;
31- margin: 0;
32- border: 0;
33- }
34- /* LineHighlight */
35- .chroma .hl {
36- background-color: #ffffcc;
37- }
38- /* LineNumbersTable */
39- .chroma .lnt {
40- white-space: pre;
41- user-select: none;
42- margin-right: 0.4em;
43- padding: 0 0.4em 0 0.4em;
44- color: #7f7f7f;
45- }
46- /* LineNumbers */
47- .chroma .ln {
48- white-space: pre;
49- user-select: none;
50- margin-right: 0.4em;
51- padding: 0 0.4em 0 0.4em;
52- color: #7f7f7f;
53- }
54- /* Line */
55- .chroma .line {
56- display: flex;
57- }
58- /* Keyword */
59- .chroma .k {
60- color: #2838b0;
61- }
62- /* KeywordConstant */
63- .chroma .kc {
64- color: #444444;
65- font-style: italic;
66- }
67- /* KeywordDeclaration */
68- .chroma .kd {
69- color: #2838b0;
70- font-style: italic;
71- }
72- /* KeywordNamespace */
73- .chroma .kn {
74- color: #2838b0;
75- }
76- /* KeywordPseudo */
77- .chroma .kp {
78- color: #2838b0;
79- }
80- /* KeywordReserved */
81- .chroma .kr {
82- color: #2838b0;
83- }
84- /* KeywordType */
85- .chroma .kt {
86- color: #2838b0;
87- font-style: italic;
88- }
89- /* Name */
90- .chroma .n {
91- }
92- /* NameAttribute */
93- .chroma .na {
94- color: #388038;
95- }
96- /* NameBuiltin */
97- .chroma .nb {
98- color: #388038;
99- }
100- /* NameBuiltinPseudo */
101- .chroma .bp {
102- font-style: italic;
103- }
104- /* NameClass */
105- .chroma .nc {
106- color: #287088;
107- }
108- /* NameConstant */
109- .chroma .no {
110- color: #b85820;
111- }
112- /* NameDecorator */
113- .chroma .nd {
114- color: #287088;
115- }
116- /* NameEntity */
117- .chroma .ni {
118- color: #709030;
119- }
120- /* NameException */
121- .chroma .ne {
122- color: #908828;
123- }
124- /* NameFunction */
125- .chroma .nf {
126- color: #785840;
127- }
128- /* NameFunctionMagic */
129- .chroma .fm {
130- color: #b85820;
131- }
132- /* NameLabel */
133- .chroma .nl {
134- color: #289870;
135- }
136- /* NameNamespace */
137- .chroma .nn {
138- color: #289870;
139- }
140- /* NameOther */
141- .chroma .nx {
142- }
143- /* NameProperty */
144- .chroma .py {
145- }
146- /* NameTag */
147- .chroma .nt {
148- color: #2838b0;
149- }
150- /* NameVariable */
151- .chroma .nv {
152- color: #b04040;
153- }
154- /* NameVariableClass */
155- .chroma .vc {
156- }
157- /* NameVariableGlobal */
158- .chroma .vg {
159- color: #908828;
160- }
161- /* NameVariableInstance */
162- .chroma .vi {
163- }
164- /* NameVariableMagic */
165- .chroma .vm {
166- color: #b85820;
167- }
168- /* Literal */
169- .chroma .l {
170- }
171- /* LiteralDate */
172- .chroma .ld {
173- }
174- /* LiteralString */
175- .chroma .s {
176- color: #b83838;
177- }
178- /* LiteralStringAffix */
179- .chroma .sa {
180- color: #444444;
181- }
182- /* LiteralStringBacktick */
183- .chroma .sb {
184- color: #b83838;
185- }
186- /* LiteralStringChar */
187- .chroma .sc {
188- color: #a848a8;
189- }
190- /* LiteralStringDelimiter */
191- .chroma .dl {
192- color: #b85820;
193- }
194- /* LiteralStringDoc */
195- .chroma .sd {
196- color: #b85820;
197- font-style: italic;
198- }
199- /* LiteralStringDouble */
200- .chroma .s2 {
201- color: #b83838;
202- }
203- /* LiteralStringEscape */
204- .chroma .se {
205- color: #709030;
206- }
207- /* LiteralStringHeredoc */
208- .chroma .sh {
209- color: #b83838;
210- }
211- /* LiteralStringInterpol */
212- .chroma .si {
213- color: #b83838;
214- text-decoration: underline;
215- }
216- /* LiteralStringOther */
217- .chroma .sx {
218- color: #a848a8;
219- }
220- /* LiteralStringRegex */
221- .chroma .sr {
222- color: #a848a8;
223- }
224- /* LiteralStringSingle */
225- .chroma .s1 {
226- color: #b83838;
227- }
228- /* LiteralStringSymbol */
229- .chroma .ss {
230- color: #b83838;
231- }
232- /* LiteralNumber */
233- .chroma .m {
234- color: #444444;
235- }
236- /* LiteralNumberBin */
237- .chroma .mb {
238- color: #444444;
239- }
240- /* LiteralNumberFloat */
241- .chroma .mf {
242- color: #444444;
243- }
244- /* LiteralNumberHex */
245- .chroma .mh {
246- color: #444444;
247- }
248- /* LiteralNumberInteger */
249- .chroma .mi {
250- color: #444444;
251- }
252- /* LiteralNumberIntegerLong */
253- .chroma .il {
254- color: #444444;
255- }
256- /* LiteralNumberOct */
257- .chroma .mo {
258- color: #444444;
259- }
260- /* Operator */
261- .chroma .o {
262- color: #666666;
263- }
264- /* OperatorWord */
265- .chroma .ow {
266- color: #a848a8;
267- }
268- /* Punctuation */
269- .chroma .p {
270- color: #888888;
271- }
272- /* Comment */
273- .chroma .c {
274- color: #888888;
275- font-style: italic;
276- }
277- /* CommentHashbang */
278- .chroma .ch {
279- color: #287088;
280- font-style: italic;
281- }
282- /* CommentMultiline */
283- .chroma .cm {
284- color: #888888;
285- font-style: italic;
286- }
287- /* CommentSingle */
288- .chroma .c1 {
289- color: #888888;
290- font-style: italic;
291- }
292- /* CommentSpecial */
293- .chroma .cs {
294- color: #888888;
295- font-style: italic;
296- }
297- /* CommentPreproc */
298- .chroma .cp {
299- color: #289870;
300- }
301- /* CommentPreprocFile */
302- .chroma .cpf {
303- color: #289870;
304- }
305- /* Generic */
306- .chroma .g {
307- }
308- /* GenericDeleted */
309- .chroma .gd {
310- color: #c02828;
311- }
312- /* GenericEmph */
313- .chroma .ge {
314- font-style: italic;
315- }
316- /* GenericError */
317- .chroma .gr {
318- color: #c02828;
319- }
320- /* GenericHeading */
321- .chroma .gh {
322- color: #666666;
323- }
324- /* GenericInserted */
325- .chroma .gi {
326- color: #388038;
327- }
328- /* GenericOutput */
329- .chroma .go {
330- color: #666666;
331- }
332- /* GenericPrompt */
333- .chroma .gp {
334- color: #444444;
335- }
336- /* GenericStrong */
337- .chroma .gs {
338- font-weight: bold;
339- }
340- /* GenericSubheading */
341- .chroma .gu {
342- color: #444444;
343- }
344- /* GenericTraceback */
345- .chroma .gt {
346- color: #2838b0;
347- }
348- /* GenericUnderline */
349- .chroma .gl {
350- text-decoration: underline;
351- }
352- /* TextWhitespace */
353- .chroma .w {
354- color: #a89028;
355- }
356-}
357-
358-@media (prefers-color-scheme: dark) {
359- /* Background */
360- .bg {
361- color: #f8f8f2;
362- background-color: #282a36;
363- }
364- /* PreWrapper */
365- .chroma {
366- color: #f8f8f2;
367- background-color: #282a36;
368- }
369- /* Other */
370- .chroma .x {
371- }
372- /* Error */
373- .chroma .err {
374- }
375- /* CodeLine */
376- .chroma .cl {
377- }
378- /* LineTableTD */
379- .chroma .lntd {
380- vertical-align: top;
381- padding: 0;
382- margin: 0;
383- border: 0;
384- }
385- /* LineTable */
386- .chroma .lntable {
387- border-spacing: 0;
388- padding: 0;
389- margin: 0;
390- border: 0;
391- }
392- /* LineHighlight */
393- .chroma .hl {
394- background-color: #ffffcc;
395- }
396- /* LineNumbersTable */
397- .chroma .lnt {
398- white-space: pre;
399- user-select: none;
400- margin-right: 0.4em;
401- padding: 0 0.4em 0 0.4em;
402- color: #7f7f7f;
403- }
404- /* LineNumbers */
405- .chroma .ln {
406- white-space: pre;
407- user-select: none;
408- margin-right: 0.4em;
409- padding: 0 0.4em 0 0.4em;
410- color: #7f7f7f;
411- }
412- /* Line */
413- .chroma .line {
414- display: flex;
415- }
416- /* Keyword */
417- .chroma .k {
418- color: #ff79c6;
419- }
420- /* KeywordConstant */
421- .chroma .kc {
422- color: #ff79c6;
423- }
424- /* KeywordDeclaration */
425- .chroma .kd {
426- color: #8be9fd;
427- font-style: italic;
428- }
429- /* KeywordNamespace */
430- .chroma .kn {
431- color: #ff79c6;
432- }
433- /* KeywordPseudo */
434- .chroma .kp {
435- color: #ff79c6;
436- }
437- /* KeywordReserved */
438- .chroma .kr {
439- color: #ff79c6;
440- }
441- /* KeywordType */
442- .chroma .kt {
443- color: #8be9fd;
444- }
445- /* Name */
446- .chroma .n {
447- }
448- /* NameAttribute */
449- .chroma .na {
450- color: #50fa7b;
451- }
452- /* NameBuiltin */
453- .chroma .nb {
454- color: #8be9fd;
455- font-style: italic;
456- }
457- /* NameBuiltinPseudo */
458- .chroma .bp {
459- }
460- /* NameClass */
461- .chroma .nc {
462- color: #50fa7b;
463- }
464- /* NameConstant */
465- .chroma .no {
466- }
467- /* NameDecorator */
468- .chroma .nd {
469- }
470- /* NameEntity */
471- .chroma .ni {
472- }
473- /* NameException */
474- .chroma .ne {
475- }
476- /* NameFunction */
477- .chroma .nf {
478- color: #50fa7b;
479- }
480- /* NameFunctionMagic */
481- .chroma .fm {
482- }
483- /* NameLabel */
484- .chroma .nl {
485- color: #8be9fd;
486- font-style: italic;
487- }
488- /* NameNamespace */
489- .chroma .nn {
490- }
491- /* NameOther */
492- .chroma .nx {
493- }
494- /* NameProperty */
495- .chroma .py {
496- }
497- /* NameTag */
498- .chroma .nt {
499- color: #ff79c6;
500- }
501- /* NameVariable */
502- .chroma .nv {
503- color: #8be9fd;
504- font-style: italic;
505- }
506- /* NameVariableClass */
507- .chroma .vc {
508- color: #8be9fd;
509- font-style: italic;
510- }
511- /* NameVariableGlobal */
512- .chroma .vg {
513- color: #8be9fd;
514- font-style: italic;
515- }
516- /* NameVariableInstance */
517- .chroma .vi {
518- color: #8be9fd;
519- font-style: italic;
520- }
521- /* NameVariableMagic */
522- .chroma .vm {
523- }
524- /* Literal */
525- .chroma .l {
526- }
527- /* LiteralDate */
528- .chroma .ld {
529- }
530- /* LiteralString */
531- .chroma .s {
532- color: #f1fa8c;
533- }
534- /* LiteralStringAffix */
535- .chroma .sa {
536- color: #f1fa8c;
537- }
538- /* LiteralStringBacktick */
539- .chroma .sb {
540- color: #f1fa8c;
541- }
542- /* LiteralStringChar */
543- .chroma .sc {
544- color: #f1fa8c;
545- }
546- /* LiteralStringDelimiter */
547- .chroma .dl {
548- color: #f1fa8c;
549- }
550- /* LiteralStringDoc */
551- .chroma .sd {
552- color: #f1fa8c;
553- }
554- /* LiteralStringDouble */
555- .chroma .s2 {
556- color: #f1fa8c;
557- }
558- /* LiteralStringEscape */
559- .chroma .se {
560- color: #f1fa8c;
561- }
562- /* LiteralStringHeredoc */
563- .chroma .sh {
564- color: #f1fa8c;
565- }
566- /* LiteralStringInterpol */
567- .chroma .si {
568- color: #f1fa8c;
569- }
570- /* LiteralStringOther */
571- .chroma .sx {
572- color: #f1fa8c;
573- }
574- /* LiteralStringRegex */
575- .chroma .sr {
576- color: #f1fa8c;
577- }
578- /* LiteralStringSingle */
579- .chroma .s1 {
580- color: #f1fa8c;
581- }
582- /* LiteralStringSymbol */
583- .chroma .ss {
584- color: #f1fa8c;
585- }
586- /* LiteralNumber */
587- .chroma .m {
588- color: #bd93f9;
589- }
590- /* LiteralNumberBin */
591- .chroma .mb {
592- color: #bd93f9;
593- }
594- /* LiteralNumberFloat */
595- .chroma .mf {
596- color: #bd93f9;
597- }
598- /* LiteralNumberHex */
599- .chroma .mh {
600- color: #bd93f9;
601- }
602- /* LiteralNumberInteger */
603- .chroma .mi {
604- color: #bd93f9;
605- }
606- /* LiteralNumberIntegerLong */
607- .chroma .il {
608- color: #bd93f9;
609- }
610- /* LiteralNumberOct */
611- .chroma .mo {
612- color: #bd93f9;
613- }
614- /* Operator */
615- .chroma .o {
616- color: #ff79c6;
617- }
618- /* OperatorWord */
619- .chroma .ow {
620- color: #ff79c6;
621- }
622- /* Punctuation */
623- .chroma .p {
624- }
625- /* Comment */
626- .chroma .c {
627- color: #6272a4;
628- }
629- /* CommentHashbang */
630- .chroma .ch {
631- color: #6272a4;
632- }
633- /* CommentMultiline */
634- .chroma .cm {
635- color: #6272a4;
636- }
637- /* CommentSingle */
638- .chroma .c1 {
639- color: #6272a4;
640- }
641- /* CommentSpecial */
642- .chroma .cs {
643- color: #6272a4;
644- }
645- /* CommentPreproc */
646- .chroma .cp {
647- color: #ff79c6;
648- }
649- /* CommentPreprocFile */
650- .chroma .cpf {
651- color: #ff79c6;
652- }
653- /* Generic */
654- .chroma .g {
655- }
656- /* GenericDeleted */
657- .chroma .gd {
658- color: #ff5555;
659- }
660- /* GenericEmph */
661- .chroma .ge {
662- text-decoration: underline;
663- }
664- /* GenericError */
665- .chroma .gr {
666- }
667- /* GenericHeading */
668- .chroma .gh {
669- font-weight: bold;
670- }
671- /* GenericInserted */
672- .chroma .gi {
673- color: #50fa7b;
674- font-weight: bold;
675- }
676- /* GenericOutput */
677- .chroma .go {
678- color: #44475a;
679- }
680- /* GenericPrompt */
681- .chroma .gp {
682- }
683- /* GenericStrong */
684- .chroma .gs {
685- }
686- /* GenericSubheading */
687- .chroma .gu {
688- font-weight: bold;
689- }
690- /* GenericTraceback */
691- .chroma .gt {
692- }
693- /* GenericUnderline */
694- .chroma .gl {
695- text-decoration: underline;
696- }
697- /* TextWhitespace */
698- .chroma .w {
699- }
700-}
+0, -107
......@@ -1,107 +0,0 @@
1-package pastes
2-
3-import (
4- "fmt"
5- "strconv"
6- "strings"
7- "time"
8-
9- "github.com/araddon/dateparse"
10- "github.com/picosh/pico/pkg/db"
11- "github.com/picosh/pico/pkg/filehandlers"
12- "github.com/picosh/pico/pkg/pssh"
13- "github.com/picosh/pico/pkg/shared"
14-)
15-
16-var DEFAULT_EXPIRES_AT = 90
17-
18-type FileHooks struct {
19- Cfg *shared.ConfigSite
20- Db db.DB
21-}
22-
23-func (p *FileHooks) FileValidate(s *pssh.SSHServerConnSession, data *filehandlers.PostMetaData) (bool, error) {
24- if !shared.IsTextFile(string(data.Text)) {
25- err := fmt.Errorf(
26- "ERROR: (%s) invalid file must be plain text (utf-8), skipping",
27- data.Filename,
28- )
29- return false, err
30- }
31-
32- maxFileSize := int(p.Cfg.MaxAssetSize)
33- if data.FileSize > maxFileSize {
34- return false, fmt.Errorf(
35- "ERROR: file (%s) has exceeded maximum file size (%d bytes)",
36- data.Filename,
37- maxFileSize,
38- )
39- }
40-
41- return true, nil
42-}
43-
44-func (p *FileHooks) FileMeta(s *pssh.SSHServerConnSession, data *filehandlers.PostMetaData) error {
45- data.Title = shared.ToUpper(data.Slug)
46- // we want the slug to be the filename for pastes
47- data.Slug = data.Filename
48-
49- if data.ExpiresAt == nil || data.ExpiresAt.IsZero() {
50- // mark posts for deletion a X days after creation
51- expiresAt := time.Now().AddDate(0, 0, DEFAULT_EXPIRES_AT)
52- data.ExpiresAt = &expiresAt
53- }
54-
55- var hidden bool
56- var expiresFound bool
57- var expires *time.Time
58-
59- cmd := s.Command()
60-
61- for _, arg := range cmd {
62- if strings.Contains(arg, "=") {
63- splitArg := strings.Split(arg, "=")
64- if len(splitArg) != 2 {
65- continue
66- }
67-
68- switch splitArg[0] {
69- case "hidden":
70- val, err := strconv.ParseBool(splitArg[1])
71- if err != nil {
72- continue
73- }
74-
75- hidden = val
76- case "expires":
77- val, err := strconv.ParseBool(splitArg[1])
78- if err == nil {
79- expiresFound = !val
80- continue
81- }
82-
83- duration, err := time.ParseDuration(splitArg[1])
84- if err == nil {
85- expiresFound = true
86- expireTime := time.Now().Add(duration)
87- expires = &expireTime
88- continue
89- }
90-
91- expireTime, err := dateparse.ParseStrict(splitArg[1])
92- if err == nil {
93- expiresFound = true
94- expires = &expireTime
95- }
96- }
97- }
98- }
99-
100- data.Hidden = hidden
101-
102- if expiresFound {
103- data.ExpiresAt = expires
104- }
105-
106- return nil
107-}
+0, -97
......@@ -1,97 +0,0 @@
1-package pastes
2-
3-import (
4- "context"
5- "os"
6- "os/signal"
7- "syscall"
8- "time"
9-
10- "github.com/picosh/pico/pkg/db/postgres"
11- "github.com/picosh/pico/pkg/filehandlers"
12- "github.com/picosh/pico/pkg/pssh"
13- "github.com/picosh/pico/pkg/send/auth"
14- "github.com/picosh/pico/pkg/send/list"
15- "github.com/picosh/pico/pkg/send/pipe"
16- "github.com/picosh/pico/pkg/send/protocols/rsync"
17- "github.com/picosh/pico/pkg/send/protocols/scp"
18- "github.com/picosh/pico/pkg/send/protocols/sftp"
19- "github.com/picosh/pico/pkg/shared"
20-)
21-
22-func StartSshServer() {
23- appName := "pastes-ssh"
24-
25- host := shared.GetEnv("PASTES_HOST", "0.0.0.0")
26- port := shared.GetEnv("PASTES_SSH_PORT", "2222")
27- promPort := shared.GetEnv("PASTES_PROM_PORT", "9222")
28- cfg := NewConfigSite(appName)
29- logger := cfg.Logger
30-
31- ctx, cancel := context.WithCancel(context.Background())
32- defer cancel()
33-
34- dbh := postgres.NewDB(cfg.DbURL, cfg.Logger)
35- defer func() {
36- _ = dbh.Close()
37- }()
38- hooks := &FileHooks{
39- Cfg: cfg,
40- Db: dbh,
41- }
42-
43- fileMap := map[string]filehandlers.ReadWriteHandler{
44- "fallback": filehandlers.NewScpPostHandler(dbh, cfg, hooks),
45- }
46- handler := filehandlers.NewFileHandlerRouter(cfg, dbh, fileMap)
47- sshAuth := shared.NewSshAuthHandler(dbh, logger, "pastes")
48-
49- // Create a new SSH server
50- server, err := pssh.NewSSHServerWithConfig(
51- ctx,
52- logger,
53- appName,
54- host,
55- port,
56- promPort,
57- "ssh_data/term_info_ed25519",
58- sshAuth.PubkeyAuthHandler,
59- []pssh.SSHServerMiddleware{
60- pipe.Middleware(handler, ""),
61- list.Middleware(handler),
62- scp.Middleware(handler),
63- rsync.Middleware(handler),
64- auth.Middleware(handler),
65- pssh.PtyMdw(pssh.DeprecatedNotice(), 200*time.Millisecond),
66- pssh.LogMiddleware(handler, dbh),
67- },
68- []pssh.SSHServerMiddleware{
69- sftp.Middleware(handler),
70- pssh.LogMiddleware(handler, dbh),
71- },
72- nil,
73- )
74-
75- if err != nil {
76- logger.Error("failed to create ssh server", "err", err.Error())
77- os.Exit(1)
78- }
79-
80- done := make(chan os.Signal, 1)
81- signal.Notify(done, os.Interrupt, syscall.SIGINT, syscall.SIGTERM)
82- logger.Info("Starting SSH server", "addr", server.Config.ListenAddr)
83- go func() {
84- if err = server.ListenAndServe(); err != nil {
85- logger.Error("serve", "err", err.Error())
86- os.Exit(1)
87- }
88- }()
89-
90- exit := func() {
91- logger.Info("stopping ssh server")
92- cancel()
93- }
94-
95- <-done
96- exit()
97-}
+3, -4
......@@ -385,10 +385,9 @@ func (m *ErrMultiplePublicKeys) Error() string {
385385 }
386386
387387 type UserStats struct {
388- Prose UserServiceStats
389- Pastes UserServiceStats
390- Feeds UserServiceStats
391- Pages UserServiceStats
388+ Prose UserServiceStats
389+ Feeds UserServiceStats
390+ Pages UserServiceStats
392391 }
393392
394393 type UserServiceStats struct {
+0, -2
......@@ -2101,8 +2101,6 @@ func (me *PsqlDB) FindUserStats(userID string) (*db.UserStats, error) {
21012101 switch stat.Service {
21022102 case "prose":
21032103 stats.Prose = stat
2104- case "pastes":
2105- stats.Pastes = stat
21062104 case "feeds":
21072105 stats.Feeds = stat
21082106 }
+2, -2
......@@ -748,11 +748,11 @@ func TestFindExpiredPosts(t *testing.T) {
748748 Filename: "expired.txt",
749749 Slug: "expired",
750750 Title: "Expired",
751- Space: "pastes",
751+ Space: "prose",
752752 ExpiresAt: &expired,
753753 })
754754
755- posts, err := testDB.FindExpiredPosts("pastes")
755+ posts, err := testDB.FindExpiredPosts("prose")
756756 if err != nil {
757757 t.Fatalf("FindExpiredPosts failed: %v", err)
758758 }
+1, -2
......@@ -179,7 +179,7 @@ func (m *ServicesList) Draw(ctx vxfw.DrawContext) (vxfw.Surface, error) {
179179 brd := NewBorder(services)
180180 brd.Label = "services"
181181 brd.Style = vaxis.Style{Foreground: purp}
182- servicesHeight := 8
182+ servicesHeight := 7
183183 return brd.Draw(vxfw.DrawContext{
184184 Characters: ctx.Characters,
185185 Max: vxfw.Size{
......@@ -204,7 +204,6 @@ func (m *ServicesList) getServiceKv() []Kv {
204204 data := [][]string{
205205 {"name", "status"},
206206 {"pipe", "active"},
207- {"pastes", "active"},
208207 {"prose", proseStatus},
209208 {"pages", pagesStatus},
210209 }
+0, -2
......@@ -127,12 +127,10 @@ func (m *MenuPage) Draw(ctx vxfw.DrawContext) (vxfw.Surface, error) {
127127 if m.stats != nil {
128128 pages, _ := NewUsageInfo("pages", &m.stats.Pages).Draw(ctx)
129129 prose, _ := NewUsageInfo("prose", &m.stats.Prose).Draw(ctx)
130- pastes, _ := NewUsageInfo("pastes", &m.stats.Pastes).Draw(ctx)
131130 feeds, _ := NewUsageInfo("rss-to-email", &m.stats.Feeds).Draw(ctx)
132131 right = append(right,
133132 pages,
134133 prose,
135- pastes,
136134 feeds,
137135 )
138136 }