atproto pastebin service: https://plonk.li

Compare changes

Choose any two refs to compare.

+2 -2
flake.nix
··· 81 81 description = "Port to run plonk on"; 82 82 }; 83 83 cookie_secret = mkOption { 84 - type = types.string; 84 + type = types.str; 85 85 default = "00000000000000000000000000000000"; 86 86 description = "Cookie secret"; 87 87 }; ··· 104 104 PLONK_PORT = "${toString config.services.plonk.port}"; 105 105 PLONK_NODE_ENV = "production"; 106 106 PLONK_HOST = "localhost"; 107 - PLONK_PUBLIC_URL = "plonk.li"; 107 + PLONK_PUBLIC_URL = "https://plonk.li"; 108 108 PLONK_DB_PATH = "plonk.db"; 109 109 PLONK_COOKIE_SECRET = config.services.plonk.cookie_secret; 110 110 };
+2 -1
package.json
··· 50 50 "!src/**/*.test.*" 51 51 ], 52 52 "loader": { 53 - ".pug": "copy" 53 + ".pug": "copy", 54 + ".woff2": "copy" 54 55 }, 55 56 "splitting": false, 56 57 "sourcemap": true,
src/assets/NerdIosevka-Regular.woff2

This is a binary file and will not be displayed.

+1 -1
src/auth/client.ts
··· 12 12 client_name: "plonk.li", 13 13 client_id: publicUrl 14 14 ? `${url}/client-metadata.json` 15 - : `http://localhost?redirect_uri=${enc(`${url}/oauth/callback`)}&scope=${enc("atproto transition:generic")}`, 15 + : `http://${env.PLONK_HOST}?redirect_uri=${enc(`${url}/oauth/callback`)}&scope=${enc("atproto transition:generic")}`, 16 16 client_uri: url, 17 17 redirect_uris: [`${url}/oauth/callback`], 18 18 scope: "atproto transition:generic",
+9
src/mixins/footer.pug
··· 1 + mixin footer() 2 + hr 3 + div.footer 4 + div.left-side 5 + div.right-side 6 + p 7 + | made by 8 + a(href="https://bsky.app/profile/oppi.li") @oppi.li 9 +
+1 -1
src/mixins/head.pug
··· 2 2 head 3 3 meta(name="viewport" content="width=device-width, initial-scale=1.0") 4 4 meta(charset='UTF-8') 5 - title #{title} 5 + title #{title} · plonk.li 6 6 link(rel="stylesheet", href="/styles.css") 7 7 link(rel="preconnect" href="https://rsms.me/") 8 8 link(rel="stylesheet" href="https://rsms.me/inter/inter.css")
-2
src/mixins/header.pug
··· 1 1 mixin header(ownDid, didHandleMap) 2 2 div.header 3 3 div.left-side 4 - if ownDid 5 - | logged in as @#{didHandleMap[ownDid]} 6 4 div.right-side 7 5 p 8 6 a(href="/") home
+3 -2
src/mixins/post.pug
··· 1 1 mixin post(paste, handle, did) 2 2 div.post 3 3 p 4 - a(href=`/p/${paste.shortUrl}`) 4 + a(href=`/p/${paste.shortUrl}`).post-link 5 5 | #{paste.title} 6 6 p.post-info 7 7 | by ··· 13 13 | #{paste.lang} 14 14 | · 15 15 | #{paste.code.split('\n').length} loc 16 - 16 + | ·  17 + a(href=`/p/${paste.shortUrl}/#comments`) #{paste.commentCount} #{pluralize(paste.commentCount, 'comment')}
+4
src/mixins/utils.pug
··· 2 2 function randInt(min, max) { 3 3 return Math.floor(Math.random() * (max - min + 1)) + min; 4 4 } 5 + - 6 + function pluralize(count, noun) { 7 + return count==1?noun:`${noun}s`; 8 + } 5 9 - 6 10 function timeDifference(current, previous) { 7 11 if (!current || !previous) {
+82 -5
src/public/styles.css
··· 1 - @import url('https://fonts.googleapis.com/css2?family=IBM+Plex+Mono:ital,wght@0,100;0,200;0,300;0,400;0,500;0,600;0,700;1,100;1,200;1,300;1,400;1,500;1,600;1,700&display=swap'); 1 + @font-face { 2 + font-family: 'NerdIosevka'; 3 + src: url('../assets/NerdIosevka-Regular.woff2') format('woff2'); 4 + font-weight: normal; 5 + font-style: monospace; 6 + } 2 7 3 8 :root { 4 9 /* Light mode colors */ ··· 28 33 } 29 34 30 35 * { 31 - font-family: 'IBM Plex Mono', monospace; 36 + font-family: 'NerdIosevka', monospace; 37 + font-size: 0.9rem; 32 38 } 33 39 34 40 body { ··· 52 58 } 53 59 54 60 pre { 55 - background-color: var(--bg-color-muted); 56 61 padding: 1rem; 57 62 overflow-x: auto; 58 63 } 59 64 65 + .comment-body { 66 + background-color: var(--bg-color); 67 + padding: 0; 68 + margin-top: 0.1rem; 69 + } 70 + 71 + .comment-info { 72 + margin-bottom: 0; 73 + } 74 + 60 75 input, textarea, select, button { 61 76 border: none; 62 77 padding: 1rem; ··· 93 108 hr { 94 109 border: none; 95 110 border-top: 1px solid var(--bg-color-muted); 96 - padding: 1rem; 97 111 } 98 112 99 113 .post-form { ··· 127 141 align-self: flex-end; 128 142 } 129 143 144 + .post-link { 145 + color: var(--text-color); 146 + text-decoration: none; 147 + } 148 + .post-link:hover { 149 + text-decoration: underline; 150 + } 151 + .post-link:visited { 152 + color: var(--text-color-muted); 153 + } 154 + 155 + .post-info { 156 + margin-top: 0; 157 + } 158 + 159 + .post-info, .comment-info { 160 + color: var(--text-color-muted); 161 + } 162 + .post-info a, .comment-info a { 163 + color: var(--text-color-muted); 164 + text-decoration: none; 165 + } 166 + .post-info a:visited, .comment-info a:visited { 167 + color: var(--text-color-muted); 168 + } 169 + .post-info a:hover, .comment-info a:hover { 170 + text-decoration: underline; 171 + } 172 + 130 173 .timeline, .comments { 131 174 display: flex; 132 175 flex-direction: column; 133 176 gap: 1rem; 177 + padding-bottom: 1rem; 134 178 } 135 179 136 180 .login-input-title { ··· 141 185 flex: 1 142 186 } 143 187 144 - .header { 188 + .header, .footer { 145 189 display: flex; 146 190 flex-direction: row; 147 191 justify-content: space-between; ··· 154 198 text-indent: 1px; 155 199 text-overflow: ''; 156 200 } 201 + 202 + .code-line { 203 + display: flex; 204 + } 205 + 206 + .code-line-num { 207 + white-space: pre; 208 + -webkit-user-select: none; 209 + user-select: none; 210 + margin-right: 0.4em; 211 + padding: 0 0.4em 0 0.4em; 212 + color: var(--text-color-muted); 213 + text-align: right; 214 + } 215 + 216 + .code-line-content { 217 + color: var(--text-color); 218 + } 219 + 220 + .header, .footer { 221 + color: var(--text-color); 222 + } 223 + 224 + .header a, .header a:visited, 225 + .footer a, .footer a:visited { 226 + color: var(--link-color); 227 + text-decoration: none; 228 + } 229 + 230 + .header a:hover, 231 + .footer a:hover { 232 + text-decoration: underline; 233 + }
+40 -12
src/routes.ts
··· 49 49 export const createRouter = (ctx: Ctx) => { 50 50 const router = express.Router(); 51 51 52 + router.use("/assets", express.static(path.join(__dirname, "assets"))); 52 53 // OAuth metadata 53 54 router.get("/client-metadata.json", async (_req, res) => { 54 55 return res.json(ctx.oauthClient.clientMetadata); ··· 104 105 const agent = await getSessionAgent(req, res, ctx); 105 106 const pastes = await ctx.db 106 107 .selectFrom("paste") 107 - .selectAll() 108 - .orderBy("indexedAt", "desc") 108 + .leftJoin("comment", "comment.pasteUri", "paste.uri") 109 + .select([ 110 + "paste.uri", 111 + "paste.shortUrl", 112 + "paste.authorDid", 113 + "paste.code", 114 + "paste.lang", 115 + "paste.title", 116 + "paste.createdAt", 117 + "paste.indexedAt as pasteIndexedAt", 118 + ctx.db.fn.count("comment.uri").as("commentCount") 119 + ]) 120 + .groupBy("paste.uri") 121 + .orderBy("pasteIndexedAt", "desc") 109 122 .limit(25) 110 123 .execute(); 111 124 ··· 129 142 const { authorDid } = req.params; 130 143 const pastes = await ctx.db 131 144 .selectFrom("paste") 132 - .selectAll() 133 - .where("authorDid", "=", authorDid) 145 + .leftJoin("comment", "comment.pasteUri", "paste.uri") 146 + .select([ 147 + "paste.uri", 148 + "paste.shortUrl", 149 + "paste.authorDid as pasteAuthorDid", 150 + "paste.code", 151 + "paste.lang", 152 + "paste.title", 153 + "paste.createdAt as pasteCreatedAt", 154 + "paste.indexedAt as pasteIndexedAt", 155 + ctx.db.fn.count("comment.uri").as("commentCount") 156 + ]) 157 + .groupBy("paste.uri") 158 + .where("pasteAuthorDid", "=", authorDid) 159 + .orderBy("pasteCreatedAt", "desc") 134 160 .execute(); 135 161 let didHandleMap: Record<string, string> = {}; 136 162 didHandleMap[authorDid] = await ctx.resolver.resolveDidToHandle(authorDid); ··· 193 219 authorDid: pasteAuthorDid, 194 220 }; 195 221 196 - const comments = ret.map((row) => { 197 - return { 198 - uri: row.commentUri, 199 - authorDid: row.commentAuthorDid, 200 - body: row.commentBody, 201 - createdAt: row.commentCreatedAt, 202 - }; 203 - }); 222 + const comments = ret 223 + .filter((row) => row.commentUri) 224 + .map((row) => { 225 + return { 226 + uri: row.commentUri, 227 + authorDid: row.commentAuthorDid, 228 + body: row.commentBody, 229 + createdAt: row.commentCreatedAt, 230 + }; 231 + }); 204 232 205 233 const ownAgent = await getSessionAgent(req, res, ctx); 206 234 if (!ownAgent) {
+4
src/views/index.pug
··· 1 1 include ../mixins/mkPost 2 2 include ../mixins/head 3 3 include ../mixins/header 4 + include ../mixins/footer 4 5 include ../mixins/utils 5 6 include ../mixins/post 6 7 ··· 19 20 "c", 20 21 "c#", 21 22 "c++", 23 + "cobol", 22 24 ].toSorted()) 23 25 doctype html 24 26 html ··· 34 36 each paste in pastes 35 37 - var handle = didHandleMap[paste.authorDid] 36 38 +post(paste, handle, paste.authorDid) 39 + 40 + +footer()
+5 -8
src/views/login.pug
··· 1 + include ../mixins/head 2 + include ../mixins/footer 3 + 1 4 doctype html 2 5 html 3 - head 4 - meta(name="viewport" content="width=device-width, initial-scale=1.0") 5 - meta(charset='UTF-8') 6 - title login 7 - link(rel="stylesheet", href="/styles.css") 8 - link(rel="preconnect" href="https://rsms.me/") 9 - link(rel="stylesheet" href="https://rsms.me/inter/inter.css") 10 - script(src="https://cdn.dashjs.org/latest/dash.all.min.js") 6 + +head("login") 11 7 body 12 8 main#content 13 9 h1 login ··· 15 11 div.login-row 16 12 input(type="text" name="handle" placeholder="enter handle" required).login-input-title 17 13 button(type="submit").login-submit-button login 14 + +footer()
+37 -18
src/views/paste.pug
··· 1 1 - var now = new Date() 2 2 include ../mixins/head 3 3 include ../mixins/header 4 + include ../mixins/footer 4 5 include ../mixins/utils 5 6 doctype html 6 7 html 7 - +head(paste.title) 8 + +head(`${paste.title} · ${didHandleMap[paste.authorDid]}`) 8 9 body 9 10 main#content 10 11 +header(ownDid, didHandleMap) 11 12 h1 #{paste.title} 12 - p 13 - | by @#{didHandleMap[paste.authorDid]} · 13 + p.post-info 14 + | @#{didHandleMap[paste.authorDid]} · 14 15 | #{timeDifference(now, Date.parse(paste.createdAt))} ago · 15 16 | #{paste.lang} · 16 17 | #{paste.code.split('\n').length} loc · 17 18 a(href=`/r/${paste.shortUrl}`) raw 19 + | &nbsp;· 20 + | #{comments.length} #{pluralize(comments.length, 'comment')} 18 21 pre 19 - | #{paste.code} 22 + code 23 + - var lines = paste.code.split(/\r?\n|\r|\n/g) 24 + - var tot_chars = lines.length.toString().length 25 + each line, idx in lines 26 + span.code-line 27 + span.code-line-num(id=`L${idx + 1}` style=`min-width: ${tot_chars}ch;`) 28 + | #{idx + 1} 29 + span.code-line-content #{line} 20 30 hr 21 31 22 - div.comments 23 - each comment in comments 24 - div.comment(id=`${encodeURIComponent(comment.uri)}`) 25 - p 26 - | by @#{didHandleMap[comment.authorDid]} · 27 - | #{timeDifference(now, Date.parse(paste.createdAt))} ago 28 - p 29 - | #{comment.body} 30 - hr 32 + if comments.length != 0 33 + h1(id="comments") comments 34 + div.comments 35 + each comment in comments 36 + div.comment(id=`${encodeURIComponent(comment.uri)}`) 37 + p.comment-info 38 + a(href=`/u/${comment.authorDid}`) 39 + | @#{didHandleMap[comment.authorDid]} 40 + | &nbsp;· 41 + | #{timeDifference(now, Date.parse(paste.createdAt))} ago 42 + pre.comment-body #{comment.body} 43 + 44 + if ownDid 45 + form(action=`/${encodeURIComponent(paste.uri)}/comment` method="post").post-form 46 + div.post-row 47 + textarea#code(name="comment" rows="5" placeholder="add a comment" required).post-input-code 31 48 32 - form(action=`/${encodeURIComponent(paste.uri)}/comment` method="post").post-form 33 - div.post-row 34 - textarea#code(name="comment" rows="5" placeholder="add a comment" required).post-input-code 49 + div.post-submit-row 50 + button(type="submit").post-input-submit zonk! 51 + else 52 + p 53 + a(href="/login") login 54 + |&nbsp;to post a comment 35 55 36 - div.post-submit-row 37 - button(type="submit").post-input-submit zonk! 56 + +footer()
+3
src/views/user.pug
··· 2 2 - var handle = didHandleMap[authorDid] 3 3 include ../mixins/head 4 4 include ../mixins/header 5 + include ../mixins/footer 5 6 include ../mixins/utils 6 7 include ../mixins/post 7 8 doctype html ··· 14 15 div.timeline 15 16 each paste in pastes 16 17 +post(paste, handle, authorDid) 18 + 19 + +footer()
+463
x
··· 1 + From 489bb110616aa4da596aed7ae0a048c919ed333e Mon Sep 17 00:00:00 2001 2 + From: Akshay <nerdy@peppe.rs> 3 + Date: Thu, 26 Dec 2024 19:19:37 +0000 4 + Subject: [PATCH 1/3] add footer, comments, linenrs etc 5 + 6 + --- 7 + src/mixins/footer.pug | 9 +++++++++ 8 + src/mixins/post.pug | 3 ++- 9 + src/mixins/utils.pug | 4 ++++ 10 + src/public/styles.css | 21 +++++++++++++++++++-- 11 + src/routes.ts | 33 +++++++++++++++++++++++++++++---- 12 + src/views/index.pug | 3 +++ 13 + src/views/paste.pug | 28 +++++++++++++++++++++------- 14 + 7 files changed, 87 insertions(+), 14 deletions(-) 15 + create mode 100644 src/mixins/footer.pug 16 + 17 + diff --git a/src/mixins/footer.pug b/src/mixins/footer.pug 18 + new file mode 100644 19 + index 0000000..be71086 20 + --- /dev/null 21 + +++ b/src/mixins/footer.pug 22 + @@ -0,0 +1,9 @@ 23 + +mixin footer() 24 + + hr 25 + + div.footer 26 + + div.left-side 27 + + div.right-side 28 + + p 29 + + | made by 30 + + a(href="https://bsky.app/profile/oppi.li") @oppi.li 31 + + 32 + diff --git a/src/mixins/post.pug b/src/mixins/post.pug 33 + index 77d78aa..e98bcf8 100644 34 + --- a/src/mixins/post.pug 35 + +++ b/src/mixins/post.pug 36 + @@ -13,4 +13,5 @@ mixin post(paste, handle, did) 37 + | #{paste.lang} 38 + | · 39 + | #{paste.code.split('\n').length} loc 40 + - 41 + + | ·&nbsp; 42 + + a(href=`/p/${paste.shortUrl}/#comments`) #{paste.commentCount} #{pluralize(paste.commentCount, 'comment')} 43 + diff --git a/src/mixins/utils.pug b/src/mixins/utils.pug 44 + index 857bddd..08507d3 100644 45 + --- a/src/mixins/utils.pug 46 + +++ b/src/mixins/utils.pug 47 + @@ -2,6 +2,10 @@ 48 + function randInt(min, max) { 49 + return Math.floor(Math.random() * (max - min + 1)) + min; 50 + } 51 + +- 52 + + function pluralize(count, noun) { 53 + + return count==1?noun:`${noun}s`; 54 + + } 55 + - 56 + function timeDifference(current, previous) { 57 + if (!current || !previous) { 58 + diff --git a/src/public/styles.css b/src/public/styles.css 59 + index f88b533..6f80f5f 100644 60 + --- a/src/public/styles.css 61 + +++ b/src/public/styles.css 62 + @@ -104,7 +104,6 @@ textarea { 63 + hr { 64 + border: none; 65 + border-top: 1px solid var(--bg-color-muted); 66 + - padding: 1rem; 67 + } 68 + 69 + .post-form { 70 + @@ -152,7 +151,7 @@ hr { 71 + flex: 1 72 + } 73 + 74 + -.header { 75 + +.header, .footer { 76 + display: flex; 77 + flex-direction: row; 78 + justify-content: space-between; 79 + @@ -165,3 +164,21 @@ select { 80 + text-indent: 1px; 81 + text-overflow: ''; 82 + } 83 + + 84 + +.code-line { 85 + + display: flex; 86 + +} 87 + + 88 + +.code-line-num { 89 + + white-space: pre; 90 + + -webkit-user-select: none; 91 + + user-select: none; 92 + + margin-right: 0.4em; 93 + + padding: 0 0.4em 0 0.4em; 94 + + color: var(--text-color-muted); 95 + + text-align: right; 96 + +} 97 + + 98 + +.code-line-content { 99 + + color: var(--text-color); 100 + +} 101 + diff --git a/src/routes.ts b/src/routes.ts 102 + index 70f931d..17fa00e 100644 103 + --- a/src/routes.ts 104 + +++ b/src/routes.ts 105 + @@ -105,8 +105,20 @@ export const createRouter = (ctx: Ctx) => { 106 + const agent = await getSessionAgent(req, res, ctx); 107 + const pastes = await ctx.db 108 + .selectFrom("paste") 109 + - .selectAll() 110 + - .orderBy("indexedAt", "desc") 111 + + .leftJoin("comment", "comment.pasteUri", "paste.uri") 112 + + .select([ 113 + + "paste.uri", 114 + + "paste.shortUrl", 115 + + "paste.authorDid", 116 + + "paste.code", 117 + + "paste.lang", 118 + + "paste.title", 119 + + "paste.createdAt", 120 + + "paste.indexedAt as pasteIndexedAt", 121 + + ctx.db.fn.count("comment.uri").as("commentCount") 122 + + ]) 123 + + .groupBy("paste.uri") 124 + + .orderBy("pasteIndexedAt", "desc") 125 + .limit(25) 126 + .execute(); 127 + 128 + @@ -130,8 +142,21 @@ export const createRouter = (ctx: Ctx) => { 129 + const { authorDid } = req.params; 130 + const pastes = await ctx.db 131 + .selectFrom("paste") 132 + - .selectAll() 133 + - .where("authorDid", "=", authorDid) 134 + + .leftJoin("comment", "comment.pasteUri", "paste.uri") 135 + + .select([ 136 + + "paste.uri", 137 + + "paste.shortUrl", 138 + + "paste.authorDid as pasteAuthorDid", 139 + + "paste.code", 140 + + "paste.lang", 141 + + "paste.title", 142 + + "paste.createdAt as pasteCreatedAt", 143 + + "paste.indexedAt as pasteIndexedAt", 144 + + ctx.db.fn.count("comment.uri").as("commentCount") 145 + + ]) 146 + + .groupBy("paste.uri") 147 + + .where("pasteAuthorDid", "=", authorDid) 148 + + .orderBy("pasteCreatedAt", "desc") 149 + .execute(); 150 + let didHandleMap: Record<string, string> = {}; 151 + didHandleMap[authorDid] = await ctx.resolver.resolveDidToHandle(authorDid); 152 + diff --git a/src/views/index.pug b/src/views/index.pug 153 + index 3443deb..e04403d 100644 154 + --- a/src/views/index.pug 155 + +++ b/src/views/index.pug 156 + @@ -1,6 +1,7 @@ 157 + include ../mixins/mkPost 158 + include ../mixins/head 159 + include ../mixins/header 160 + +include ../mixins/footer 161 + include ../mixins/utils 162 + include ../mixins/post 163 + 164 + @@ -19,6 +20,7 @@ include ../mixins/post 165 + "c", 166 + "c#", 167 + "c++", 168 + + "cobol", 169 + ].toSorted()) 170 + doctype html 171 + html 172 + @@ -34,3 +36,4 @@ html 173 + each paste in pastes 174 + - var handle = didHandleMap[paste.authorDid] 175 + +post(paste, handle, paste.authorDid) 176 + + +footer() 177 + diff --git a/src/views/paste.pug b/src/views/paste.pug 178 + index 29516d3..f8a0906 100644 179 + --- a/src/views/paste.pug 180 + +++ b/src/views/paste.pug 181 + @@ -15,12 +15,21 @@ html 182 + | #{paste.lang} · 183 + | #{paste.code.split('\n').length} loc · 184 + a(href=`/r/${paste.shortUrl}`) raw 185 + + | &nbsp;·&nbsp; 186 + + | #{comments.length} #{pluralize(comments.length, 'comment')} 187 + pre 188 + - | #{paste.code} 189 + + code 190 + + - var lines = paste.code.split(/\r?\n|\r|\n/g) 191 + + - var tot_chars = lines.length.toString().length 192 + + each line, idx in lines 193 + + span.code-line 194 + + span.code-line-num(id=`L${idx + 1}` style=`min-width: ${tot_chars}ch;`) 195 + + | #{idx + 1} 196 + + span.code-line-content #{line} 197 + hr 198 + 199 + if comments.length != 0 200 + - h1 comments 201 + + h1(id="comments") comments 202 + div.comments 203 + each comment in comments 204 + div.comment(id=`${encodeURIComponent(comment.uri)}`) 205 + @@ -33,9 +42,14 @@ html 206 + pre.comment-body #{comment.body} 207 + hr 208 + 209 + - form(action=`/${encodeURIComponent(paste.uri)}/comment` method="post").post-form 210 + - div.post-row 211 + - textarea#code(name="comment" rows="5" placeholder="add a comment" required).post-input-code 212 + + if ownDid 213 + + form(action=`/${encodeURIComponent(paste.uri)}/comment` method="post").post-form 214 + + div.post-row 215 + + textarea#code(name="comment" rows="5" placeholder="add a comment" required).post-input-code 216 + 217 + - div.post-submit-row 218 + - button(type="submit").post-input-submit zonk! 219 + + div.post-submit-row 220 + + button(type="submit").post-input-submit zonk! 221 + + else 222 + + p 223 + + a(href="/login") login 224 + + |&nbsp;to post a comment 225 + -- 226 + 2.47.0 227 + 228 + From 436f4d5e8912c50068d8c70e623a23ce2ca9e6e7 Mon Sep 17 00:00:00 2001 229 + From: Akshay <nerdy@peppe.rs> 230 + Date: Thu, 26 Dec 2024 21:12:07 +0000 231 + Subject: [PATCH 2/3] footer everywhere 232 + 233 + --- 234 + src/views/index.pug | 1 + 235 + src/views/login.pug | 2 ++ 236 + src/views/paste.pug | 6 ++++-- 237 + src/views/user.pug | 3 +++ 238 + 4 files changed, 10 insertions(+), 2 deletions(-) 239 + 240 + diff --git a/src/views/index.pug b/src/views/index.pug 241 + index e04403d..bc9085a 100644 242 + --- a/src/views/index.pug 243 + +++ b/src/views/index.pug 244 + @@ -36,4 +36,5 @@ html 245 + each paste in pastes 246 + - var handle = didHandleMap[paste.authorDid] 247 + +post(paste, handle, paste.authorDid) 248 + + 249 + +footer() 250 + diff --git a/src/views/login.pug b/src/views/login.pug 251 + index 55aa048..b5a35e0 100644 252 + --- a/src/views/login.pug 253 + +++ b/src/views/login.pug 254 + @@ -1,4 +1,5 @@ 255 + include ../mixins/head 256 + +include ../mixins/footer 257 + 258 + doctype html 259 + html 260 + @@ -10,3 +11,4 @@ html 261 + div.login-row 262 + input(type="text" name="handle" placeholder="enter handle" required).login-input-title 263 + button(type="submit").login-submit-button login 264 + + +footer() 265 + diff --git a/src/views/paste.pug b/src/views/paste.pug 266 + index f8a0906..653c02e 100644 267 + --- a/src/views/paste.pug 268 + +++ b/src/views/paste.pug 269 + @@ -1,6 +1,7 @@ 270 + - var now = new Date() 271 + include ../mixins/head 272 + include ../mixins/header 273 + +include ../mixins/footer 274 + include ../mixins/utils 275 + doctype html 276 + html 277 + @@ -40,7 +41,6 @@ html 278 + | #{timeDifference(now, Date.parse(paste.createdAt))} ago 279 + p 280 + pre.comment-body #{comment.body} 281 + - hr 282 + 283 + if ownDid 284 + form(action=`/${encodeURIComponent(paste.uri)}/comment` method="post").post-form 285 + @@ -49,7 +49,9 @@ html 286 + 287 + div.post-submit-row 288 + button(type="submit").post-input-submit zonk! 289 + - else 290 + + else 291 + p 292 + a(href="/login") login 293 + |&nbsp;to post a comment 294 + + 295 + + +footer() 296 + diff --git a/src/views/user.pug b/src/views/user.pug 297 + index 3523e16..b2b2743 100644 298 + --- a/src/views/user.pug 299 + +++ b/src/views/user.pug 300 + @@ -2,6 +2,7 @@ 301 + - var handle = didHandleMap[authorDid] 302 + include ../mixins/head 303 + include ../mixins/header 304 + +include ../mixins/footer 305 + include ../mixins/utils 306 + include ../mixins/post 307 + doctype html 308 + @@ -14,3 +15,5 @@ html 309 + div.timeline 310 + each paste in pastes 311 + +post(paste, handle, authorDid) 312 + + 313 + + +footer() 314 + -- 315 + 2.47.0 316 + 317 + From a717f0702e818515e34187cde86c15c00d2e66ba Mon Sep 17 00:00:00 2001 318 + From: Akshay <nerdy@peppe.rs> 319 + Date: Thu, 26 Dec 2024 23:14:37 +0000 320 + Subject: [PATCH 3/3] stylin for links 321 + 322 + --- 323 + src/mixins/post.pug | 2 +- 324 + src/public/styles.css | 51 ++++++++++++++++++++++++++++++++++++++++++- 325 + src/views/paste.pug | 9 ++++---- 326 + 3 files changed, 55 insertions(+), 7 deletions(-) 327 + 328 + diff --git a/src/mixins/post.pug b/src/mixins/post.pug 329 + index e98bcf8..51af1ff 100644 330 + --- a/src/mixins/post.pug 331 + +++ b/src/mixins/post.pug 332 + @@ -1,7 +1,7 @@ 333 + mixin post(paste, handle, did) 334 + div.post 335 + p 336 + - a(href=`/p/${paste.shortUrl}`) 337 + + a(href=`/p/${paste.shortUrl}`).post-link 338 + | #{paste.title} 339 + p.post-info 340 + | by 341 + diff --git a/src/public/styles.css b/src/public/styles.css 342 + index 6f80f5f..b153f92 100644 343 + --- a/src/public/styles.css 344 + +++ b/src/public/styles.css 345 + @@ -58,7 +58,6 @@ a:visited { 346 + } 347 + 348 + pre { 349 + - background-color: var(--bg-color-muted); 350 + padding: 1rem; 351 + overflow-x: auto; 352 + } 353 + @@ -66,6 +65,11 @@ pre { 354 + .comment-body { 355 + background-color: var(--bg-color); 356 + padding: 0; 357 + + margin-top: 0.1rem; 358 + +} 359 + + 360 + +.comment-info { 361 + + margin-bottom: 0; 362 + } 363 + 364 + input, textarea, select, button { 365 + @@ -137,10 +141,40 @@ hr { 366 + align-self: flex-end; 367 + } 368 + 369 + +.post-link { 370 + + color: var(--text-color); 371 + + text-decoration: none; 372 + +} 373 + +.post-link:hover { 374 + + text-decoration: underline; 375 + +} 376 + +.post-link:visited { 377 + + color: var(--text-color-muted); 378 + +} 379 + + 380 + +.post-info { 381 + + margin-top: 0; 382 + +} 383 + + 384 + +.post-info, .comment-info { 385 + + color: var(--text-color-muted); 386 + +} 387 + +.post-info a, .comment-info a { 388 + + color: var(--text-color-muted); 389 + + text-decoration: none; 390 + +} 391 + +.post-info a:visited, .comment-info a:visited { 392 + + color: var(--text-color-muted); 393 + +} 394 + +.post-info a:hover, .comment-info a:hover { 395 + + text-decoration: underline; 396 + +} 397 + + 398 + .timeline, .comments { 399 + display: flex; 400 + flex-direction: column; 401 + gap: 1rem; 402 + + padding-bottom: 1rem; 403 + } 404 + 405 + .login-input-title { 406 + @@ -182,3 +216,18 @@ select { 407 + .code-line-content { 408 + color: var(--text-color); 409 + } 410 + + 411 + +.header, .footer { 412 + + color: var(--text-color); 413 + +} 414 + + 415 + +.header a, .header a:visited, 416 + +.footer a, .footer a:visited { 417 + + color: var(--link-color); 418 + + text-decoration: none; 419 + +} 420 + + 421 + +.header a:hover, 422 + +.footer a:hover { 423 + + text-decoration: underline; 424 + +} 425 + diff --git a/src/views/paste.pug b/src/views/paste.pug 426 + index 653c02e..3014107 100644 427 + --- a/src/views/paste.pug 428 + +++ b/src/views/paste.pug 429 + @@ -10,13 +10,13 @@ html 430 + main#content 431 + +header(ownDid, didHandleMap) 432 + h1 #{paste.title} 433 + - p 434 + + p.post-info 435 + | @#{didHandleMap[paste.authorDid]} · 436 + | #{timeDifference(now, Date.parse(paste.createdAt))} ago · 437 + | #{paste.lang} · 438 + | #{paste.code.split('\n').length} loc · 439 + a(href=`/r/${paste.shortUrl}`) raw 440 + - | &nbsp;·&nbsp; 441 + + | &nbsp;· 442 + | #{comments.length} #{pluralize(comments.length, 'comment')} 443 + pre 444 + code 445 + @@ -34,13 +34,12 @@ html 446 + div.comments 447 + each comment in comments 448 + div.comment(id=`${encodeURIComponent(comment.uri)}`) 449 + - p 450 + + p.comment-info 451 + a(href=`/u/${comment.authorDid}`) 452 + | @#{didHandleMap[comment.authorDid]} 453 + | &nbsp;· 454 + | #{timeDifference(now, Date.parse(paste.createdAt))} ago 455 + - p 456 + - pre.comment-body #{comment.body} 457 + + pre.comment-body #{comment.body} 458 + 459 + if ownDid 460 + form(action=`/${encodeURIComponent(paste.uri)}/comment` method="post").post-form 461 + -- 462 + 2.47.0 463 +
+465
y
··· 1 + From 6df2fc36269c7114fb845fa0ac667e08f6e7d09b Mon Sep 17 00:00:00 2001 2 + From: Akshay <nerdy@peppe.rs> 3 + Date: Thu, 26 Dec 2024 19:19:37 +0000 4 + Subject: [PATCH 1/3] add footer, comments, linenrs etc 5 + 6 + --- 7 + src/mixins/footer.pug | 9 +++++++++ 8 + src/mixins/post.pug | 3 ++- 9 + src/mixins/utils.pug | 4 ++++ 10 + src/public/styles.css | 21 +++++++++++++++++++-- 11 + src/routes.ts | 33 +++++++++++++++++++++++++++++---- 12 + src/views/index.pug | 3 +++ 13 + src/views/paste.pug | 28 +++++++++++++++++++++------- 14 + 7 files changed, 87 insertions(+), 14 deletions(-) 15 + create mode 100644 src/mixins/footer.pug 16 + 17 + diff --git a/src/mixins/footer.pug b/src/mixins/footer.pug 18 + new file mode 100644 19 + index 0000000..be71086 20 + --- /dev/null 21 + +++ b/src/mixins/footer.pug 22 + @@ -0,0 +1,9 @@ 23 + +mixin footer() 24 + + hr 25 + + div.footer 26 + + div.left-side 27 + + div.right-side 28 + + p 29 + + | made by 30 + + a(href="https://bsky.app/profile/oppi.li") @oppi.li 31 + + 32 + diff --git a/src/mixins/post.pug b/src/mixins/post.pug 33 + index 77d78aa..e98bcf8 100644 34 + --- a/src/mixins/post.pug 35 + +++ b/src/mixins/post.pug 36 + @@ -13,4 +13,5 @@ mixin post(paste, handle, did) 37 + | #{paste.lang} 38 + | · 39 + | #{paste.code.split('\n').length} loc 40 + - 41 + + | ·&nbsp; 42 + + a(href=`/p/${paste.shortUrl}/#comments`) #{paste.commentCount} #{pluralize(paste.commentCount, 'comment')} 43 + diff --git a/src/mixins/utils.pug b/src/mixins/utils.pug 44 + index 857bddd..08507d3 100644 45 + --- a/src/mixins/utils.pug 46 + +++ b/src/mixins/utils.pug 47 + @@ -2,6 +2,10 @@ 48 + function randInt(min, max) { 49 + return Math.floor(Math.random() * (max - min + 1)) + min; 50 + } 51 + +- 52 + + function pluralize(count, noun) { 53 + + return count==1?noun:`${noun}s`; 54 + + } 55 + - 56 + function timeDifference(current, previous) { 57 + if (!current || !previous) { 58 + diff --git a/src/public/styles.css b/src/public/styles.css 59 + index f88b533..6f80f5f 100644 60 + --- a/src/public/styles.css 61 + +++ b/src/public/styles.css 62 + @@ -104,7 +104,6 @@ textarea { 63 + hr { 64 + border: none; 65 + border-top: 1px solid var(--bg-color-muted); 66 + - padding: 1rem; 67 + } 68 + 69 + .post-form { 70 + @@ -152,7 +151,7 @@ hr { 71 + flex: 1 72 + } 73 + 74 + -.header { 75 + +.header, .footer { 76 + display: flex; 77 + flex-direction: row; 78 + justify-content: space-between; 79 + @@ -165,3 +164,21 @@ select { 80 + text-indent: 1px; 81 + text-overflow: ''; 82 + } 83 + + 84 + +.code-line { 85 + + display: flex; 86 + +} 87 + + 88 + +.code-line-num { 89 + + white-space: pre; 90 + + -webkit-user-select: none; 91 + + user-select: none; 92 + + margin-right: 0.4em; 93 + + padding: 0 0.4em 0 0.4em; 94 + + color: var(--text-color-muted); 95 + + text-align: right; 96 + +} 97 + + 98 + +.code-line-content { 99 + + color: var(--text-color); 100 + +} 101 + diff --git a/src/routes.ts b/src/routes.ts 102 + index 70f931d..17fa00e 100644 103 + --- a/src/routes.ts 104 + +++ b/src/routes.ts 105 + @@ -105,8 +105,20 @@ export const createRouter = (ctx: Ctx) => { 106 + const agent = await getSessionAgent(req, res, ctx); 107 + const pastes = await ctx.db 108 + .selectFrom("paste") 109 + - .selectAll() 110 + - .orderBy("indexedAt", "desc") 111 + + .leftJoin("comment", "comment.pasteUri", "paste.uri") 112 + + .select([ 113 + + "paste.uri", 114 + + "paste.shortUrl", 115 + + "paste.authorDid", 116 + + "paste.code", 117 + + "paste.lang", 118 + + "paste.title", 119 + + "paste.createdAt", 120 + + "paste.indexedAt as pasteIndexedAt", 121 + + ctx.db.fn.count("comment.uri").as("commentCount") 122 + + ]) 123 + + .groupBy("paste.uri") 124 + + .orderBy("pasteIndexedAt", "desc") 125 + .limit(25) 126 + .execute(); 127 + 128 + @@ -130,8 +142,21 @@ export const createRouter = (ctx: Ctx) => { 129 + const { authorDid } = req.params; 130 + const pastes = await ctx.db 131 + .selectFrom("paste") 132 + - .selectAll() 133 + - .where("authorDid", "=", authorDid) 134 + + .leftJoin("comment", "comment.pasteUri", "paste.uri") 135 + + .select([ 136 + + "paste.uri", 137 + + "paste.shortUrl", 138 + + "paste.authorDid as pasteAuthorDid", 139 + + "paste.code", 140 + + "paste.lang", 141 + + "paste.title", 142 + + "paste.createdAt as pasteCreatedAt", 143 + + "paste.indexedAt as pasteIndexedAt", 144 + + ctx.db.fn.count("comment.uri").as("commentCount") 145 + + ]) 146 + + .groupBy("paste.uri") 147 + + .where("pasteAuthorDid", "=", authorDid) 148 + + .orderBy("pasteCreatedAt", "desc") 149 + .execute(); 150 + let didHandleMap: Record<string, string> = {}; 151 + didHandleMap[authorDid] = await ctx.resolver.resolveDidToHandle(authorDid); 152 + diff --git a/src/views/index.pug b/src/views/index.pug 153 + index 3443deb..e04403d 100644 154 + --- a/src/views/index.pug 155 + +++ b/src/views/index.pug 156 + @@ -1,6 +1,7 @@ 157 + include ../mixins/mkPost 158 + include ../mixins/head 159 + include ../mixins/header 160 + +include ../mixins/footer 161 + include ../mixins/utils 162 + include ../mixins/post 163 + 164 + @@ -19,6 +20,7 @@ include ../mixins/post 165 + "c", 166 + "c#", 167 + "c++", 168 + + "cobol", 169 + ].toSorted()) 170 + doctype html 171 + html 172 + @@ -34,3 +36,4 @@ html 173 + each paste in pastes 174 + - var handle = didHandleMap[paste.authorDid] 175 + +post(paste, handle, paste.authorDid) 176 + + +footer() 177 + diff --git a/src/views/paste.pug b/src/views/paste.pug 178 + index 29516d3..f8a0906 100644 179 + --- a/src/views/paste.pug 180 + +++ b/src/views/paste.pug 181 + @@ -15,12 +15,21 @@ html 182 + | #{paste.lang} · 183 + | #{paste.code.split('\n').length} loc · 184 + a(href=`/r/${paste.shortUrl}`) raw 185 + + | &nbsp;·&nbsp; 186 + + | #{comments.length} #{pluralize(comments.length, 'comment')} 187 + pre 188 + - | #{paste.code} 189 + + code 190 + + - var lines = paste.code.split(/\r?\n|\r|\n/g) 191 + + - var tot_chars = lines.length.toString().length 192 + + each line, idx in lines 193 + + span.code-line 194 + + span.code-line-num(id=`L${idx + 1}` style=`min-width: ${tot_chars}ch;`) 195 + + | #{idx + 1} 196 + + span.code-line-content #{line} 197 + hr 198 + 199 + if comments.length != 0 200 + - h1 comments 201 + + h1(id="comments") comments 202 + div.comments 203 + each comment in comments 204 + div.comment(id=`${encodeURIComponent(comment.uri)}`) 205 + @@ -33,9 +42,14 @@ html 206 + pre.comment-body #{comment.body} 207 + hr 208 + 209 + - form(action=`/${encodeURIComponent(paste.uri)}/comment` method="post").post-form 210 + - div.post-row 211 + - textarea#code(name="comment" rows="5" placeholder="add a comment" required).post-input-code 212 + + if ownDid 213 + + form(action=`/${encodeURIComponent(paste.uri)}/comment` method="post").post-form 214 + + div.post-row 215 + + textarea#code(name="comment" rows="5" placeholder="add a comment" required).post-input-code 216 + 217 + - div.post-submit-row 218 + - button(type="submit").post-input-submit zonk! 219 + + div.post-submit-row 220 + + button(type="submit").post-input-submit zonk! 221 + + else 222 + + p 223 + + a(href="/login") login 224 + + |&nbsp;to post a comment 225 + -- 226 + 2.47.0 227 + 228 + 229 + From ccd3269be5d287b627c8badc96bd0ce5f0d3a0bf Mon Sep 17 00:00:00 2001 230 + From: Akshay <nerdy@peppe.rs> 231 + Date: Thu, 26 Dec 2024 21:12:07 +0000 232 + Subject: [PATCH 2/3] footer everywhere 233 + 234 + --- 235 + src/views/index.pug | 1 + 236 + src/views/login.pug | 2 ++ 237 + src/views/paste.pug | 6 ++++-- 238 + src/views/user.pug | 3 +++ 239 + 4 files changed, 10 insertions(+), 2 deletions(-) 240 + 241 + diff --git a/src/views/index.pug b/src/views/index.pug 242 + index e04403d..bc9085a 100644 243 + --- a/src/views/index.pug 244 + +++ b/src/views/index.pug 245 + @@ -36,4 +36,5 @@ html 246 + each paste in pastes 247 + - var handle = didHandleMap[paste.authorDid] 248 + +post(paste, handle, paste.authorDid) 249 + + 250 + +footer() 251 + diff --git a/src/views/login.pug b/src/views/login.pug 252 + index 55aa048..b5a35e0 100644 253 + --- a/src/views/login.pug 254 + +++ b/src/views/login.pug 255 + @@ -1,4 +1,5 @@ 256 + include ../mixins/head 257 + +include ../mixins/footer 258 + 259 + doctype html 260 + html 261 + @@ -10,3 +11,4 @@ html 262 + div.login-row 263 + input(type="text" name="handle" placeholder="enter handle" required).login-input-title 264 + button(type="submit").login-submit-button login 265 + + +footer() 266 + diff --git a/src/views/paste.pug b/src/views/paste.pug 267 + index f8a0906..653c02e 100644 268 + --- a/src/views/paste.pug 269 + +++ b/src/views/paste.pug 270 + @@ -1,6 +1,7 @@ 271 + - var now = new Date() 272 + include ../mixins/head 273 + include ../mixins/header 274 + +include ../mixins/footer 275 + include ../mixins/utils 276 + doctype html 277 + html 278 + @@ -40,7 +41,6 @@ html 279 + | #{timeDifference(now, Date.parse(paste.createdAt))} ago 280 + p 281 + pre.comment-body #{comment.body} 282 + - hr 283 + 284 + if ownDid 285 + form(action=`/${encodeURIComponent(paste.uri)}/comment` method="post").post-form 286 + @@ -49,7 +49,9 @@ html 287 + 288 + div.post-submit-row 289 + button(type="submit").post-input-submit zonk! 290 + - else 291 + + else 292 + p 293 + a(href="/login") login 294 + |&nbsp;to post a comment 295 + + 296 + + +footer() 297 + diff --git a/src/views/user.pug b/src/views/user.pug 298 + index 3523e16..b2b2743 100644 299 + --- a/src/views/user.pug 300 + +++ b/src/views/user.pug 301 + @@ -2,6 +2,7 @@ 302 + - var handle = didHandleMap[authorDid] 303 + include ../mixins/head 304 + include ../mixins/header 305 + +include ../mixins/footer 306 + include ../mixins/utils 307 + include ../mixins/post 308 + doctype html 309 + @@ -14,3 +15,5 @@ html 310 + div.timeline 311 + each paste in pastes 312 + +post(paste, handle, authorDid) 313 + + 314 + + +footer() 315 + -- 316 + 2.47.0 317 + 318 + 319 + From 0d1ee81ee0630ca852f51a9a49293fb7d0fc7a67 Mon Sep 17 00:00:00 2001 320 + From: Akshay <nerdy@peppe.rs> 321 + Date: Thu, 26 Dec 2024 23:14:37 +0000 322 + Subject: [PATCH 3/3] stylin for links 323 + 324 + --- 325 + src/mixins/post.pug | 2 +- 326 + src/public/styles.css | 51 ++++++++++++++++++++++++++++++++++++++++++- 327 + src/views/paste.pug | 9 ++++---- 328 + 3 files changed, 55 insertions(+), 7 deletions(-) 329 + 330 + diff --git a/src/mixins/post.pug b/src/mixins/post.pug 331 + index e98bcf8..51af1ff 100644 332 + --- a/src/mixins/post.pug 333 + +++ b/src/mixins/post.pug 334 + @@ -1,7 +1,7 @@ 335 + mixin post(paste, handle, did) 336 + div.post 337 + p 338 + - a(href=`/p/${paste.shortUrl}`) 339 + + a(href=`/p/${paste.shortUrl}`).post-link 340 + | #{paste.title} 341 + p.post-info 342 + | by 343 + diff --git a/src/public/styles.css b/src/public/styles.css 344 + index 6f80f5f..b153f92 100644 345 + --- a/src/public/styles.css 346 + +++ b/src/public/styles.css 347 + @@ -58,7 +58,6 @@ a:visited { 348 + } 349 + 350 + pre { 351 + - background-color: var(--bg-color-muted); 352 + padding: 1rem; 353 + overflow-x: auto; 354 + } 355 + @@ -66,6 +65,11 @@ pre { 356 + .comment-body { 357 + background-color: var(--bg-color); 358 + padding: 0; 359 + + margin-top: 0.1rem; 360 + +} 361 + + 362 + +.comment-info { 363 + + margin-bottom: 0; 364 + } 365 + 366 + input, textarea, select, button { 367 + @@ -137,10 +141,40 @@ hr { 368 + align-self: flex-end; 369 + } 370 + 371 + +.post-link { 372 + + color: var(--text-color); 373 + + text-decoration: none; 374 + +} 375 + +.post-link:hover { 376 + + text-decoration: underline; 377 + +} 378 + +.post-link:visited { 379 + + color: var(--text-color-muted); 380 + +} 381 + + 382 + +.post-info { 383 + + margin-top: 0; 384 + +} 385 + + 386 + +.post-info, .comment-info { 387 + + color: var(--text-color-muted); 388 + +} 389 + +.post-info a, .comment-info a { 390 + + color: var(--text-color-muted); 391 + + text-decoration: none; 392 + +} 393 + +.post-info a:visited, .comment-info a:visited { 394 + + color: var(--text-color-muted); 395 + +} 396 + +.post-info a:hover, .comment-info a:hover { 397 + + text-decoration: underline; 398 + +} 399 + + 400 + .timeline, .comments { 401 + display: flex; 402 + flex-direction: column; 403 + gap: 1rem; 404 + + padding-bottom: 1rem; 405 + } 406 + 407 + .login-input-title { 408 + @@ -182,3 +216,18 @@ select { 409 + .code-line-content { 410 + color: var(--text-color); 411 + } 412 + + 413 + +.header, .footer { 414 + + color: var(--text-color); 415 + +} 416 + + 417 + +.header a, .header a:visited, 418 + +.footer a, .footer a:visited { 419 + + color: var(--link-color); 420 + + text-decoration: none; 421 + +} 422 + + 423 + +.header a:hover, 424 + +.footer a:hover { 425 + + text-decoration: underline; 426 + +} 427 + diff --git a/src/views/paste.pug b/src/views/paste.pug 428 + index 653c02e..3014107 100644 429 + --- a/src/views/paste.pug 430 + +++ b/src/views/paste.pug 431 + @@ -10,13 +10,13 @@ html 432 + main#content 433 + +header(ownDid, didHandleMap) 434 + h1 #{paste.title} 435 + - p 436 + + p.post-info 437 + | @#{didHandleMap[paste.authorDid]} · 438 + | #{timeDifference(now, Date.parse(paste.createdAt))} ago · 439 + | #{paste.lang} · 440 + | #{paste.code.split('\n').length} loc · 441 + a(href=`/r/${paste.shortUrl}`) raw 442 + - | &nbsp;·&nbsp; 443 + + | &nbsp;· 444 + | #{comments.length} #{pluralize(comments.length, 'comment')} 445 + pre 446 + code 447 + @@ -34,13 +34,12 @@ html 448 + div.comments 449 + each comment in comments 450 + div.comment(id=`${encodeURIComponent(comment.uri)}`) 451 + - p 452 + + p.comment-info 453 + a(href=`/u/${comment.authorDid}`) 454 + | @#{didHandleMap[comment.authorDid]} 455 + | &nbsp;· 456 + | #{timeDifference(now, Date.parse(paste.createdAt))} ago 457 + - p 458 + - pre.comment-body #{comment.body} 459 + + pre.comment-body #{comment.body} 460 + 461 + if ownDid 462 + form(action=`/${encodeURIComponent(paste.uri)}/comment` method="post").post-form 463 + -- 464 + 2.47.0 465 +