Monorepo for Tangled tangled.org

appview/pages: add templates to render strings

Signed-off-by: oppiliappan <me@oppi.li>

oppi.li b3ddcbda 80f5ba4d

verified
Changed files
+159
appview
pages
templates
strings
+74
appview/pages/pages.go
··· 31 31 chromahtml "github.com/alecthomas/chroma/v2/formatters/html" 32 32 "github.com/alecthomas/chroma/v2/lexers" 33 33 "github.com/alecthomas/chroma/v2/styles" 34 + "github.com/bluesky-social/indigo/atproto/identity" 34 35 "github.com/bluesky-social/indigo/atproto/syntax" 35 36 "github.com/go-git/go-git/v5/plumbing" 36 37 "github.com/go-git/go-git/v5/plumbing/object" ··· 1142 1143 func (p *Pages) Workflow(w io.Writer, params WorkflowParams) error { 1143 1144 params.Active = "pipelines" 1144 1145 return p.executeRepo("repo/pipelines/workflow", w, params) 1146 + } 1147 + 1148 + type PutStringParams struct { 1149 + LoggedInUser *oauth.User 1150 + Action string 1151 + 1152 + // this is supplied in the case of editing an existing string 1153 + String db.String 1154 + } 1155 + 1156 + func (p *Pages) PutString(w io.Writer, params PutStringParams) error { 1157 + return p.execute("strings/put", w, params) 1158 + } 1159 + 1160 + type StringsDashboardParams struct { 1161 + LoggedInUser *oauth.User 1162 + Card ProfileCard 1163 + Strings []db.String 1164 + } 1165 + 1166 + func (p *Pages) StringsDashboard(w io.Writer, params StringsDashboardParams) error { 1167 + return p.execute("strings/dashboard", w, params) 1168 + } 1169 + 1170 + type SingleStringParams struct { 1171 + LoggedInUser *oauth.User 1172 + ShowRendered bool 1173 + RenderToggle bool 1174 + RenderedContents template.HTML 1175 + String db.String 1176 + Stats db.StringStats 1177 + Owner identity.Identity 1178 + } 1179 + 1180 + func (p *Pages) SingleString(w io.Writer, params SingleStringParams) error { 1181 + var style *chroma.Style = styles.Get("catpuccin-latte") 1182 + 1183 + if params.ShowRendered { 1184 + switch markup.GetFormat(params.String.Filename) { 1185 + case markup.FormatMarkdown: 1186 + p.rctx.RendererType = markup.RendererTypeDefault 1187 + htmlString := p.rctx.RenderMarkdown(params.String.Contents) 1188 + params.RenderedContents = template.HTML(p.rctx.Sanitize(htmlString)) 1189 + } 1190 + } 1191 + 1192 + c := params.String.Contents 1193 + formatter := chromahtml.New( 1194 + chromahtml.InlineCode(false), 1195 + chromahtml.WithLineNumbers(true), 1196 + chromahtml.WithLinkableLineNumbers(true, "L"), 1197 + chromahtml.Standalone(false), 1198 + chromahtml.WithClasses(true), 1199 + ) 1200 + 1201 + lexer := lexers.Get(filepath.Base(params.String.Filename)) 1202 + if lexer == nil { 1203 + lexer = lexers.Fallback 1204 + } 1205 + 1206 + iterator, err := lexer.Tokenise(nil, c) 1207 + if err != nil { 1208 + return fmt.Errorf("chroma tokenize: %w", err) 1209 + } 1210 + 1211 + var code bytes.Buffer 1212 + err = formatter.Format(&code, style, iterator) 1213 + if err != nil { 1214 + return fmt.Errorf("chroma format: %w", err) 1215 + } 1216 + 1217 + params.String.Contents = code.String() 1218 + return p.execute("strings/string", w, params) 1145 1219 } 1146 1220 1147 1221 func (p *Pages) Static() http.Handler {
+85
appview/pages/templates/strings/string.html
··· 1 + {{ define "title" }}{{ .String.Filename }} · by {{ didOrHandle .Owner.DID.String .Owner.Handle.String }}{{ end }} 2 + 3 + {{ define "extrameta" }} 4 + {{ $ownerId := didOrHandle .Owner.DID.String .Owner.Handle.String }} 5 + <meta property="og:title" content="{{ .String.Filename }} · by {{ $ownerId }}" /> 6 + <meta property="og:type" content="object" /> 7 + <meta property="og:url" content="https://tangled.sh/strings/{{ $ownerId }}/{{ .String.Rkey }}" /> 8 + <meta property="og:description" content="{{ .String.Description }}" /> 9 + {{ end }} 10 + 11 + {{ define "topbar" }} 12 + {{ template "layouts/topbar" $ }} 13 + {{ end }} 14 + 15 + {{ define "content" }} 16 + {{ $ownerId := didOrHandle .Owner.DID.String .Owner.Handle.String }} 17 + <section id="string-header" class="mb-4 py-2 px-6 dark:text-white"> 18 + <div class="text-lg flex items-center justify-between"> 19 + <div> 20 + <a href="/strings/{{ $ownerId }}">{{ $ownerId }}</a> 21 + <span class="select-none">/</span> 22 + <a href="/{{ $ownerId }}/{{ .String.Rkey }}" class="font-bold">{{ .String.Filename }}</a> 23 + </div> 24 + {{ if and .LoggedInUser (eq .LoggedInUser.Did .String.Did) }} 25 + <div class="flex gap-2 text-base"> 26 + <a class="btn flex items-center gap-2 no-underline hover:no-underline p-2 group" 27 + hx-boost="true" 28 + href="/strings/{{ .String.Did }}/{{ .String.Rkey }}/edit"> 29 + {{ i "pencil" "size-4" }} 30 + <span class="hidden md:inline">edit</span> 31 + {{ i "loader-circle" "w-4 h-4 animate-spin hidden group-[.htmx-request]:inline" }} 32 + </a> 33 + <button 34 + class="btn text-red-500 hover:text-red-700 dark:text-red-400 dark:hover:text-red-300 gap-2 group p-2" 35 + title="Delete string" 36 + hx-delete="/strings/{{ .String.Did }}/{{ .String.Rkey }}/" 37 + hx-swap="none" 38 + hx-confirm="Are you sure you want to delete the gist `{{ .String.Filename }}`?" 39 + > 40 + {{ i "trash-2" "size-4" }} 41 + <span class="hidden md:inline">delete</span> 42 + {{ i "loader-circle" "w-4 h-4 animate-spin hidden group-[.htmx-request]:inline" }} 43 + </button> 44 + </div> 45 + {{ end }} 46 + </div> 47 + <span class="flex items-center"> 48 + {{ with .String.Description }} 49 + {{ . }} 50 + <span class="select-none px-1 md:px-2 [&:before]:content-['·']"></span> 51 + {{ end }} 52 + 53 + {{ with .String.Edited }} 54 + <span>edited {{ template "repo/fragments/shortTimeAgo" . }}</span> 55 + {{ else }} 56 + {{ template "repo/fragments/shortTimeAgo" .String.Created }} 57 + {{ end }} 58 + </span> 59 + </section> 60 + <section class="bg-white dark:bg-gray-800 px-6 py-4 rounded relative w-full dark:text-white"> 61 + <div class="flex justify-between items-center text-gray-500 dark:text-gray-400 text-sm md:text-base pb-2 mb-3 text-base border-b border-gray-200 dark:border-gray-700"> 62 + <span>{{ .String.Filename }}</span> 63 + <div> 64 + <span>{{ .Stats.LineCount }} lines</span> 65 + <span class="select-none px-1 md:px-2 [&:before]:content-['·']"></span> 66 + <span>{{ byteFmt .Stats.ByteCount }}</span> 67 + <span class="select-none px-1 md:px-2 [&:before]:content-['·']"></span> 68 + <a href="/strings/{{ $ownerId }}/{{ .String.Rkey }}/raw">view raw</a> 69 + {{ if .RenderToggle }} 70 + <span class="select-none px-1 md:px-2 [&:before]:content-['·']"></span> 71 + <a href="?code={{ .ShowRendered }}" hx-boost="true"> 72 + view {{ if .ShowRendered }}code{{ else }}rendered{{ end }} 73 + </a> 74 + {{ end }} 75 + </div> 76 + </div> 77 + <div class="overflow-auto relative"> 78 + {{ if .ShowRendered }} 79 + <div id="blob-contents" class="prose dark:prose-invert">{{ .RenderedContents }}</div> 80 + {{ else }} 81 + <div id="blob-contents" class="whitespace-pre peer-target:bg-yellow-200 dark:peer-target:bg-yellow-900">{{ .String.Contents | escapeHtml }}</div> 82 + {{ end }} 83 + </div> 84 + </section> 85 + {{ end }}