forked from tangled.org/core
this repo has no description

Compare changes

Choose any two refs to compare.

Changed files
+222 -11
appview
notify
pages
templates
posthog
strings
contrib
+18
appview/notify/merged_notifier.go
··· 66 notifier.UpdateProfile(ctx, profile) 67 } 68 }
··· 66 notifier.UpdateProfile(ctx, profile) 67 } 68 } 69 + 70 + func (m *mergedNotifier) NewString(ctx context.Context, string *db.String) { 71 + for _, notifier := range m.notifiers { 72 + notifier.NewString(ctx, string) 73 + } 74 + } 75 + 76 + func (m *mergedNotifier) EditString(ctx context.Context, string *db.String) { 77 + for _, notifier := range m.notifiers { 78 + notifier.EditString(ctx, string) 79 + } 80 + } 81 + 82 + func (m *mergedNotifier) DeleteString(ctx context.Context, did, rkey string) { 83 + for _, notifier := range m.notifiers { 84 + notifier.DeleteString(ctx, did, rkey) 85 + } 86 + }
+8
appview/notify/notifier.go
··· 21 NewPullComment(ctx context.Context, comment *db.PullComment) 22 23 UpdateProfile(ctx context.Context, profile *db.Profile) 24 } 25 26 // BaseNotifier is a listener that does nothing ··· 42 func (m *BaseNotifier) NewPullComment(ctx context.Context, comment *db.PullComment) {} 43 44 func (m *BaseNotifier) UpdateProfile(ctx context.Context, profile *db.Profile) {}
··· 21 NewPullComment(ctx context.Context, comment *db.PullComment) 22 23 UpdateProfile(ctx context.Context, profile *db.Profile) 24 + 25 + NewString(ctx context.Context, s *db.String) 26 + EditString(ctx context.Context, s *db.String) 27 + DeleteString(ctx context.Context, did, rkey string) 28 } 29 30 // BaseNotifier is a listener that does nothing ··· 46 func (m *BaseNotifier) NewPullComment(ctx context.Context, comment *db.PullComment) {} 47 48 func (m *BaseNotifier) UpdateProfile(ctx context.Context, profile *db.Profile) {} 49 + 50 + func (m *BaseNotifier) NewString(ctx context.Context, s *db.String) {} 51 + func (m *BaseNotifier) EditString(ctx context.Context, s *db.String) {} 52 + func (m *BaseNotifier) DeleteString(ctx context.Context, did, rkey string) {}
+90
appview/pages/templates/fragments/multiline-select.html
···
··· 1 + {{ define "fragments/multiline-select" }} 2 + <script> 3 + function highlight(scroll = false) { 4 + document.querySelectorAll(".hl").forEach(el => { 5 + el.classList.remove("hl"); 6 + }); 7 + 8 + const hash = window.location.hash; 9 + if (!hash || !hash.startsWith("#L")) { 10 + return; 11 + } 12 + 13 + const rangeStr = hash.substring(2); 14 + const parts = rangeStr.split("-"); 15 + let startLine, endLine; 16 + 17 + if (parts.length === 2) { 18 + startLine = parseInt(parts[0], 10); 19 + endLine = parseInt(parts[1], 10); 20 + } else { 21 + startLine = parseInt(parts[0], 10); 22 + endLine = startLine; 23 + } 24 + 25 + if (isNaN(startLine) || isNaN(endLine)) { 26 + console.log("nan"); 27 + console.log(startLine); 28 + console.log(endLine); 29 + return; 30 + } 31 + 32 + let target = null; 33 + 34 + for (let i = startLine; i<= endLine; i++) { 35 + const idEl = document.getElementById(`L${i}`); 36 + if (idEl) { 37 + const el = idEl.closest(".line"); 38 + if (el) { 39 + el.classList.add("hl"); 40 + target = el; 41 + } 42 + } 43 + } 44 + 45 + if (scroll && target) { 46 + target.scrollIntoView({ 47 + behavior: "smooth", 48 + block: "center", 49 + }); 50 + } 51 + } 52 + 53 + document.addEventListener("DOMContentLoaded", () => { 54 + console.log("DOMContentLoaded"); 55 + highlight(true); 56 + }); 57 + window.addEventListener("hashchange", () => { 58 + console.log("hashchange"); 59 + highlight(); 60 + }); 61 + window.addEventListener("popstate", () => { 62 + console.log("popstate"); 63 + highlight(); 64 + }); 65 + 66 + const lineNumbers = document.querySelectorAll('a[href^="#L"'); 67 + let startLine = null; 68 + 69 + lineNumbers.forEach(el => { 70 + el.addEventListener("click", (event) => { 71 + event.preventDefault(); 72 + const currentLine = parseInt(el.href.split("#L")[1]); 73 + 74 + if (event.shiftKey && startLine !== null) { 75 + const endLine = currentLine; 76 + const min = Math.min(startLine, endLine); 77 + const max = Math.max(startLine, endLine); 78 + const newHash = `#L${min}-${max}`; 79 + history.pushState(null, '', newHash); 80 + } else { 81 + const newHash = `#L${currentLine}`; 82 + history.pushState(null, '', newHash); 83 + startLine = currentLine; 84 + } 85 + 86 + highlight(); 87 + }); 88 + }); 89 + </script> 90 + {{ end }}
+7 -2
appview/pages/templates/layouts/profilebase.html
··· 9 10 {{ define "content" }} 11 {{ template "profileTabs" . }} 12 - <section class="bg-white dark:bg-gray-800 p-6 rounded w-full dark:text-white drop-shadow-sm"> 13 <div class="grid grid-cols-1 md:grid-cols-11 gap-4"> 14 - <div class="md:col-span-3 order-1 md:order-1"> 15 <div class="flex flex-col gap-4"> 16 {{ template "user/fragments/profileCard" .Card }} 17 {{ block "punchcard" .Card.Punchcard }} {{ end }} 18 </div> 19 </div> 20 {{ block "profileContent" . }} {{ end }} 21 </div> 22 </section>
··· 9 10 {{ define "content" }} 11 {{ template "profileTabs" . }} 12 + <section class="bg-white dark:bg-gray-800 px-2 py-6 md:p-6 rounded w-full dark:text-white drop-shadow-sm"> 13 <div class="grid grid-cols-1 md:grid-cols-11 gap-4"> 14 + {{ $style := "hidden md:block md:col-span-3" }} 15 + {{ if eq $.Active "overview" }} 16 + {{ $style = "md:col-span-3" }} 17 + {{ end }} 18 + <div class="{{ $style }} order-1 order-1"> 19 <div class="flex flex-col gap-4"> 20 {{ template "user/fragments/profileCard" .Card }} 21 {{ block "punchcard" .Card.Punchcard }} {{ end }} 22 </div> 23 </div> 24 + 25 {{ block "profileContent" . }} {{ end }} 26 </div> 27 </section>
+1
appview/pages/templates/repo/blob.html
··· 78 {{ end }} 79 </div> 80 {{ end }} 81 {{ end }}
··· 78 {{ end }} 79 </div> 80 {{ end }} 81 + {{ template "fragments/multiline-select" }} 82 {{ end }}
+6 -1
appview/pages/templates/repo/fragments/shortTimeAgo.html
··· 1 {{ define "repo/fragments/shortTimeAgo" }} 2 - {{ template "repo/fragments/timeWrapper" (dict "Time" . "Content" (print (. | shortRelTimeFmt) " ago")) }} 3 {{ end }} 4
··· 1 {{ define "repo/fragments/shortTimeAgo" }} 2 + {{ $formatted := shortRelTimeFmt . }} 3 + {{ $content := printf "%s ago" $formatted }} 4 + {{ if eq $formatted "now" }} 5 + {{ $content = "now" }} 6 + {{ end }} 7 + {{ template "repo/fragments/timeWrapper" (dict "Time" . "Content" $content) }} 8 {{ end }} 9
+3 -2
appview/pages/templates/strings/string.html
··· 23 hx-boost="true" 24 href="/strings/{{ .String.Did }}/{{ .String.Rkey }}/edit"> 25 {{ i "pencil" "size-4" }} 26 - <span class="hidden md:inline">edit</span> 27 {{ i "loader-circle" "w-4 h-4 animate-spin hidden group-[.htmx-request]:inline" }} 28 </a> 29 <button ··· 34 hx-confirm="Are you sure you want to delete the string `{{ .String.Filename }}`?" 35 > 36 {{ i "trash-2" "size-4" }} 37 - <span class="hidden md:inline">delete</span> 38 {{ i "loader-circle" "w-4 h-4 animate-spin hidden group-[.htmx-request]:inline" }} 39 </button> 40 </div> ··· 80 <div id="blob-contents" class="whitespace-pre peer-target:bg-yellow-200 dark:peer-target:bg-yellow-900">{{ .String.Contents | escapeHtml }}</div> 81 {{ end }} 82 </div> 83 </section> 84 {{ end }}
··· 23 hx-boost="true" 24 href="/strings/{{ .String.Did }}/{{ .String.Rkey }}/edit"> 25 {{ i "pencil" "size-4" }} 26 + <span class="hidden md:inline">edit</span> 27 {{ i "loader-circle" "w-4 h-4 animate-spin hidden group-[.htmx-request]:inline" }} 28 </a> 29 <button ··· 34 hx-confirm="Are you sure you want to delete the string `{{ .String.Filename }}`?" 35 > 36 {{ i "trash-2" "size-4" }} 37 + <span class="hidden md:inline">delete</span> 38 {{ i "loader-circle" "w-4 h-4 animate-spin hidden group-[.htmx-request]:inline" }} 39 </button> 40 </div> ··· 80 <div id="blob-contents" class="whitespace-pre peer-target:bg-yellow-200 dark:peer-target:bg-yellow-900">{{ .String.Contents | escapeHtml }}</div> 81 {{ end }} 82 </div> 83 + {{ template "fragments/multiline-select" }} 84 </section> 85 {{ end }}
+33
appview/posthog/notifier.go
··· 129 log.Println("failed to enqueue posthog event:", err) 130 } 131 }
··· 129 log.Println("failed to enqueue posthog event:", err) 130 } 131 } 132 + 133 + func (n *posthogNotifier) DeleteString(ctx context.Context, did, rkey string) { 134 + err := n.client.Enqueue(posthog.Capture{ 135 + DistinctId: did, 136 + Event: "delete_string", 137 + Properties: posthog.Properties{"rkey": rkey}, 138 + }) 139 + if err != nil { 140 + log.Println("failed to enqueue posthog event:", err) 141 + } 142 + } 143 + 144 + func (n *posthogNotifier) EditString(ctx context.Context, string *db.String) { 145 + err := n.client.Enqueue(posthog.Capture{ 146 + DistinctId: string.Did.String(), 147 + Event: "edit_string", 148 + Properties: posthog.Properties{"rkey": string.Rkey}, 149 + }) 150 + if err != nil { 151 + log.Println("failed to enqueue posthog event:", err) 152 + } 153 + } 154 + 155 + func (n *posthogNotifier) CreateString(ctx context.Context, string *db.String) { 156 + err := n.client.Enqueue(posthog.Capture{ 157 + DistinctId: string.Did.String(), 158 + Event: "create_string", 159 + Properties: posthog.Properties{"rkey": string.Rkey}, 160 + }) 161 + if err != nil { 162 + log.Println("failed to enqueue posthog event:", err) 163 + } 164 + }
+8
appview/strings/strings.go
··· 12 "tangled.sh/tangled.sh/core/appview/config" 13 "tangled.sh/tangled.sh/core/appview/db" 14 "tangled.sh/tangled.sh/core/appview/middleware" 15 "tangled.sh/tangled.sh/core/appview/oauth" 16 "tangled.sh/tangled.sh/core/appview/pages" 17 "tangled.sh/tangled.sh/core/appview/pages/markup" ··· 36 IdResolver *idresolver.Resolver 37 Logger *slog.Logger 38 Knotstream *eventconsumer.Consumer 39 } 40 41 func (s *Strings) Router(mw *middleware.Middleware) http.Handler { ··· 284 return 285 } 286 287 // if that went okay, redir to the string 288 s.Pages.HxRedirect(w, "/strings/"+user.Handle+"/"+entry.Rkey) 289 } ··· 358 return 359 } 360 361 // successful 362 s.Pages.HxRedirect(w, "/strings/"+user.Handle+"/"+string.Rkey) 363 } ··· 399 fail("Failed to delete string.", err) 400 return 401 } 402 403 s.Pages.HxRedirect(w, "/strings/"+user.Handle) 404 }
··· 12 "tangled.sh/tangled.sh/core/appview/config" 13 "tangled.sh/tangled.sh/core/appview/db" 14 "tangled.sh/tangled.sh/core/appview/middleware" 15 + "tangled.sh/tangled.sh/core/appview/notify" 16 "tangled.sh/tangled.sh/core/appview/oauth" 17 "tangled.sh/tangled.sh/core/appview/pages" 18 "tangled.sh/tangled.sh/core/appview/pages/markup" ··· 37 IdResolver *idresolver.Resolver 38 Logger *slog.Logger 39 Knotstream *eventconsumer.Consumer 40 + Notifier notify.Notifier 41 } 42 43 func (s *Strings) Router(mw *middleware.Middleware) http.Handler { ··· 286 return 287 } 288 289 + s.Notifier.EditString(r.Context(), &entry) 290 + 291 // if that went okay, redir to the string 292 s.Pages.HxRedirect(w, "/strings/"+user.Handle+"/"+entry.Rkey) 293 } ··· 362 return 363 } 364 365 + s.Notifier.NewString(r.Context(), &string) 366 + 367 // successful 368 s.Pages.HxRedirect(w, "/strings/"+user.Handle+"/"+string.Rkey) 369 } ··· 405 fail("Failed to delete string.", err) 406 return 407 } 408 + 409 + s.Notifier.DeleteString(r.Context(), user.Did, rkey) 410 411 s.Pages.HxRedirect(w, "/strings/"+user.Handle) 412 }
+44
contrib/Tiltfile
···
··· 1 + common_env = { 2 + "TANGLED_VM_SPINDLE_OWNER": os.getenv("TANGLED_VM_SPINDLE_OWNER", default=""), 3 + "TANGLED_VM_KNOT_OWNER": os.getenv("TANGLED_VM_KNOT_OWNER", default=""), 4 + "TANGLED_DB_PATH": os.getenv("TANGLED_DB_PATH", default="dev.db"), 5 + "TANGLED_DEV": os.getenv("TANGLED_DEV", default="true"), 6 + } 7 + 8 + nix_globs = ["nix/**", "flake.nix", "flake.lock"] 9 + 10 + local_resource( 11 + name="appview", 12 + serve_cmd="nix run .#watch-appview", 13 + serve_dir="..", 14 + deps=nix_globs, 15 + env=common_env, 16 + allow_parallel=True, 17 + ) 18 + 19 + local_resource( 20 + name="tailwind", 21 + serve_cmd="nix run .#watch-tailwind", 22 + serve_dir="..", 23 + deps=nix_globs, 24 + env=common_env, 25 + allow_parallel=True, 26 + ) 27 + 28 + local_resource( 29 + name="redis", 30 + serve_cmd="redis-server", 31 + serve_dir="..", 32 + deps=nix_globs, 33 + env=common_env, 34 + allow_parallel=True, 35 + ) 36 + 37 + local_resource( 38 + name="vm", 39 + serve_cmd="nix run --impure .#vm", 40 + serve_dir="..", 41 + deps=nix_globs, 42 + env=common_env, 43 + allow_parallel=True, 44 + )
+2 -1
flake.nix
··· 151 nativeBuildInputs = [ 152 pkgs.go 153 pkgs.air 154 pkgs.gopls 155 pkgs.httpie 156 pkgs.litecli ··· 187 tailwind-watcher = 188 pkgs.writeShellScriptBin "run" 189 '' 190 - ${pkgs.tailwindcss}/bin/tailwindcss -w -i input.css -o ./appview/pages/static/tw.css 191 ''; 192 in { 193 fmt = {
··· 151 nativeBuildInputs = [ 152 pkgs.go 153 pkgs.air 154 + pkgs.tilt 155 pkgs.gopls 156 pkgs.httpie 157 pkgs.litecli ··· 188 tailwind-watcher = 189 pkgs.writeShellScriptBin "run" 190 '' 191 + ${pkgs.tailwindcss}/bin/tailwindcss --watch=always -i input.css -o ./appview/pages/static/tw.css 192 ''; 193 in { 194 fmt = {
+2 -5
input.css
··· 228 } 229 /* LineHighlight */ 230 .chroma .hl { 231 - background-color: #bcc0cc; 232 } 233 /* LineNumbersTable */ 234 .chroma .lnt { 235 white-space: pre; ··· 864 text-decoration: underline; 865 } 866 } 867 - 868 - .chroma .line:has(.ln:target) { 869 - @apply bg-amber-400/30 dark:bg-amber-500/20; 870 - }
··· 228 } 229 /* LineHighlight */ 230 .chroma .hl { 231 + @apply bg-amber-400/30 dark:bg-amber-500/20; 232 } 233 + 234 /* LineNumbersTable */ 235 .chroma .lnt { 236 white-space: pre; ··· 865 text-decoration: underline; 866 } 867 }