forked from tangled.org/core
Monorepo for Tangled — https://tangled.org

Compare changes

Choose any two refs to compare.

Changed files
+162 -46
appview
cmd
dolly
docs
nix
pkgs
+7
appview/db/db.go
··· 1173 1173 return err 1174 1174 }) 1175 1175 1176 + runMigration(conn, logger, "add-default-knot-profile", func(tx *sql.Tx) error { 1177 + _, err := tx.Exec(` 1178 + alter table profile add column default_knot text; 1179 + `) 1180 + return err 1181 + }) 1182 + 1176 1183 return &DB{ 1177 1184 db, 1178 1185 logger,
+11 -4
appview/db/profile.go
··· 138 138 description, 139 139 include_bluesky, 140 140 location, 141 - pronouns 141 + pronouns, 142 + default_knot 142 143 ) 143 - values (?, ?, ?, ?, ?)`, 144 + values (?, ?, ?, ?, ?, ?)`, 144 145 profile.Did, 145 146 profile.Description, 146 147 includeBskyValue, 147 148 profile.Location, 148 149 profile.Pronouns, 150 + profile.DefaultKnot, 149 151 ) 150 152 151 153 if err != nil { ··· 324 326 func GetProfile(e Execer, did string) (*models.Profile, error) { 325 327 var profile models.Profile 326 328 var pronouns sql.Null[string] 329 + var defaultKnot sql.Null[string] 327 330 328 331 profile.Did = did 329 332 330 333 includeBluesky := 0 331 334 332 335 err := e.QueryRow( 333 - `select description, include_bluesky, location, pronouns from profile where did = ?`, 336 + `select description, include_bluesky, location, pronouns, default_knot from profile where did = ?`, 334 337 did, 335 - ).Scan(&profile.Description, &includeBluesky, &profile.Location, &pronouns) 338 + ).Scan(&profile.Description, &includeBluesky, &profile.Location, &pronouns, &defaultKnot) 336 339 if err == sql.ErrNoRows { 337 340 profile := models.Profile{} 338 341 profile.Did = did ··· 349 352 350 353 if pronouns.Valid { 351 354 profile.Pronouns = pronouns.V 355 + } 356 + 357 + if defaultKnot.Valid { 358 + profile.DefaultKnot = defaultKnot.V 352 359 } 353 360 354 361 rows, err := e.Query(`select link from profile_links where did = ?`, did)
+10
appview/knots/knots.go
··· 81 81 return 82 82 } 83 83 84 + defaultKnot := "" 85 + profile, err := db.GetProfile(k.Db, user.Did) 86 + if err != nil { 87 + k.Logger.Warn("gettings user profile to get default knot", "error", err) 88 + } 89 + if profile != nil { 90 + defaultKnot = profile.DefaultKnot 91 + } 92 + 84 93 k.Pages.Knots(w, pages.KnotsParams{ 85 94 LoggedInUser: user, 86 95 Registrations: registrations, 87 96 Tabs: knotsTabs, 88 97 Tab: "knots", 98 + DefaultKnot: defaultKnot, 89 99 }) 90 100 } 91 101
+1
appview/models/profile.go
··· 20 20 Stats [2]VanityStat 21 21 PinnedRepos [6]syntax.ATURI 22 22 Pronouns string 23 + DefaultKnot string 23 24 } 24 25 25 26 func (p Profile) IsLinksEmpty() bool {
+3
appview/pages/pages.go
··· 419 419 Registrations []models.Registration 420 420 Tabs []map[string]any 421 421 Tab string 422 + DefaultKnot string 422 423 } 423 424 424 425 func (p *Pages) Knots(w io.Writer, params KnotsParams) error { ··· 484 485 type NewRepoParams struct { 485 486 LoggedInUser *oauth.User 486 487 Knots []string 488 + DefaultKnot string 487 489 } 488 490 489 491 func (p *Pages) NewRepo(w io.Writer, params NewRepoParams) error { ··· 494 496 LoggedInUser *oauth.User 495 497 Knots []string 496 498 RepoInfo repoinfo.RepoInfo 499 + DefaultKnot string 497 500 } 498 501 499 502 func (p *Pages) ForkRepo(w io.Writer, params ForkRepoParams) error {
+24
appview/pages/templates/knots/index.html
··· 31 31 <div class="flex flex-col gap-6"> 32 32 {{ block "list" . }} {{ end }} 33 33 {{ block "register" . }} {{ end }} 34 + {{ block "default-knot" . }} {{ end }} 34 35 </div> 35 36 </section> 36 37 {{ end }} ··· 59 60 {{ end }} 60 61 </div> 61 62 <div id="operation-error" class="text-red-500 dark:text-red-400"></div> 63 + </section> 64 + {{ end }} 65 + 66 + {{ define "default-knot" }} 67 + <section class="rounded w-full flex flex-col gap-2"> 68 + <h2 class="text-sm font-bold py-2 uppercase dark:text-gray-300">default knot</h2> 69 + <select id="default-knot" name="default-knot" 70 + class="p-1 max-w-64 border border-gray-200 bg-white dark:bg-gray-800 dark:text-white dark:border-gray-700" 71 + hx-post="/profile/default-knot" 72 + hx-swap="none" 73 + name="default-knot"> 74 + <option value="" > 75 + Choose a default Knot 76 + </option> 77 + <option value="knot1.tangled.sh" {{if eq $.DefaultKnot "knot1.tangled.sh"}}selected{{end}} > 78 + knot1.tangled.sh 79 + </option> 80 + {{ range $registration := .Registrations }} 81 + <option value="{{ .Domain }}" class="py-1" {{if eq $.DefaultKnot .Domain}}selected{{end}}> 82 + {{ .Domain }} 83 + </option> 84 + {{ end }} 85 + </select> 62 86 </section> 63 87 {{ end }} 64 88
+4
appview/pages/templates/layouts/base.html
··· 11 11 <script defer src="/static/htmx-ext-ws.min.js"></script> 12 12 <script defer src="/static/actor-typeahead.js" type="module"></script> 13 13 14 + <link rel="icon" href="/static/logos/dolly.ico" sizes="48x48"/> 15 + <link rel="icon" href="/static/logos/dolly.svg" sizes="any" type="image/svg+xml"/> 16 + <link rel="apple-touch-icon" href="/static/logos/dolly.png"/> 17 + 14 18 <!-- preconnect to image cdn --> 15 19 <link rel="preconnect" href="https://avatar.tangled.sh" /> 16 20 <link rel="preconnect" href="https://camo.tangled.sh" />
+3 -1
appview/pages/templates/repo/fork.html
··· 25 25 value="{{ . }}" 26 26 class="mr-2" 27 27 id="domain-{{ . }}" 28 - {{if eq (len $.Knots) 1}}checked{{end}} 28 + {{if eq (len $.Knots) 1}}checked 29 + {{else if eq $.DefaultKnot . }}checked 30 + {{end}} 29 31 /> 30 32 <label for="domain-{{ . }}" class="dark:text-white">{{ . }}</label> 31 33 </div>
+3 -1
appview/pages/templates/repo/new.html
··· 155 155 class="mr-2" 156 156 id="domain-{{ . }}" 157 157 required 158 - {{if eq (len $.Knots) 1}}checked{{end}} 158 + {{if eq (len $.Knots) 1}}checked 159 + {{else if eq $.DefaultKnot . }}checked 160 + {{end}} 159 161 /> 160 162 <label for="domain-{{ . }}" class="dark:text-white lowercase">{{ . }}</label> 161 163 </div>
+10
appview/repo/repo.go
··· 1004 1004 return 1005 1005 } 1006 1006 1007 + defaultKnot := "" 1008 + profile, err := db.GetProfile(rp.db, user.Did) 1009 + if err != nil { 1010 + rp.logger.Warn("gettings user profile to get default knot", "error", err) 1011 + } 1012 + if profile != nil { 1013 + defaultKnot = profile.DefaultKnot 1014 + } 1015 + 1007 1016 rp.pages.ForkRepo(w, pages.ForkRepoParams{ 1008 1017 LoggedInUser: user, 1009 1018 Knots: knots, 1010 1019 RepoInfo: rp.repoResolver.GetRepoInfo(r, user), 1020 + DefaultKnot: defaultKnot, 1011 1021 }) 1012 1022 1013 1023 case http.MethodPost:
+29
appview/state/manifest.go
··· 1 + package state 2 + 3 + import ( 4 + "encoding/json" 5 + "net/http" 6 + ) 7 + 8 + // https://developer.mozilla.org/en-US/docs/Web/Progressive_web_apps/Manifest 9 + // https://www.w3.org/TR/appmanifest/ 10 + var manifestData = map[string]any{ 11 + "name": "tangled", 12 + "description": "tightly-knit social coding.", 13 + "icons": []map[string]string{ 14 + { 15 + "src": "/static/logos/dolly.svg", 16 + "sizes": "144x144", 17 + }, 18 + }, 19 + "start_url": "/", 20 + "id": "https://tangled.org", 21 + "display": "standalone", 22 + "background_color": "#111827", 23 + "theme_color": "#111827", 24 + } 25 + 26 + func (p *State) WebAppManifest(w http.ResponseWriter, r *http.Request) { 27 + w.Header().Set("Content-Type", "application/manifest+json") 28 + json.NewEncoder(w).Encode(manifestData) 29 + }
+32
appview/state/profile.go
··· 616 616 s.updateProfile(profile, w, r) 617 617 } 618 618 619 + func (s *State) UpdateProfileDefaultKnot(w http.ResponseWriter, r *http.Request) { 620 + err := r.ParseForm() 621 + if err != nil { 622 + log.Println("invalid profile update form", err) 623 + return 624 + } 625 + user := s.oauth.GetUser(r) 626 + 627 + profile, err := db.GetProfile(s.db, user.Did) 628 + if err != nil { 629 + log.Printf("getting profile data for %s: %s", user.Did, err) 630 + } 631 + 632 + if profile == nil { 633 + return 634 + } 635 + 636 + profile.DefaultKnot = r.Form.Get("default-knot") 637 + 638 + tx, err := s.db.BeginTx(r.Context(), nil) 639 + if err != nil { 640 + log.Println("failed to start transaction", err) 641 + return 642 + } 643 + 644 + err = db.UpsertProfile(tx, profile) 645 + if err != nil { 646 + log.Println("failed to update profile", err) 647 + return 648 + } 649 + } 650 + 619 651 func (s *State) updateProfile(profile *models.Profile, w http.ResponseWriter, r *http.Request) { 620 652 user := s.oauth.GetUser(r) 621 653 tx, err := s.db.BeginTx(r.Context(), nil)
+2 -3
appview/state/router.go
··· 32 32 s.pages, 33 33 ) 34 34 35 - router.Get("/favicon.svg", s.Favicon) 36 - router.Get("/favicon.ico", s.Favicon) 37 - router.Get("/pwa-manifest.json", s.PWAManifest) 35 + router.Get("/pwa-manifest.json", s.WebAppManifest) 38 36 router.Get("/robots.txt", s.RobotsTxt) 39 37 40 38 userRouter := s.UserRouter(&middleware) ··· 164 162 r.Get("/edit-pins", s.EditPinsFragment) 165 163 r.Post("/bio", s.UpdateProfileBio) 166 164 r.Post("/pins", s.UpdateProfilePins) 165 + r.Post("/default-knot", s.UpdateProfileDefaultKnot) 167 166 }) 168 167 169 168 r.Mount("/settings", s.SettingsRouter())
+10 -36
appview/state/state.go
··· 202 202 return s.db.Close() 203 203 } 204 204 205 - func (s *State) Favicon(w http.ResponseWriter, r *http.Request) { 206 - w.Header().Set("Content-Type", "image/svg+xml") 207 - w.Header().Set("Cache-Control", "public, max-age=31536000") // one year 208 - w.Header().Set("ETag", `"favicon-svg-v1"`) 209 - 210 - if match := r.Header.Get("If-None-Match"); match == `"favicon-svg-v1"` { 211 - w.WriteHeader(http.StatusNotModified) 212 - return 213 - } 214 - 215 - s.pages.Favicon(w) 216 - } 217 - 218 205 func (s *State) RobotsTxt(w http.ResponseWriter, r *http.Request) { 219 206 w.Header().Set("Content-Type", "text/plain") 220 207 w.Header().Set("Cache-Control", "public, max-age=86400") // one day ··· 223 210 Allow: / 224 211 ` 225 212 w.Write([]byte(robotsTxt)) 226 - } 227 - 228 - // https://developer.mozilla.org/en-US/docs/Web/Progressive_web_apps/Manifest 229 - const manifestJson = `{ 230 - "name": "tangled", 231 - "description": "tightly-knit social coding.", 232 - "icons": [ 233 - { 234 - "src": "/favicon.svg", 235 - "sizes": "144x144" 236 - } 237 - ], 238 - "start_url": "/", 239 - "id": "org.tangled", 240 - 241 - "display": "standalone", 242 - "background_color": "#111827", 243 - "theme_color": "#111827" 244 - }` 245 - 246 - func (p *State) PWAManifest(w http.ResponseWriter, r *http.Request) { 247 - w.Header().Set("Content-Type", "application/json") 248 - w.Write([]byte(manifestJson)) 249 213 } 250 214 251 215 func (s *State) TermsOfService(w http.ResponseWriter, r *http.Request) { ··· 454 418 return 455 419 } 456 420 421 + defaultKnot := "" 422 + profile, err := db.GetProfile(s.db, user.Did) 423 + if err != nil { 424 + s.logger.Warn("gettings user profile to get default knot", "error", err) 425 + } 426 + if profile != nil { 427 + defaultKnot = profile.DefaultKnot 428 + } 429 + 457 430 s.pages.NewRepo(w, pages.NewRepoParams{ 458 431 LoggedInUser: user, 459 432 Knots: knots, 433 + DefaultKnot: defaultKnot, 460 434 }) 461 435 462 436 case http.MethodPost:
+1 -1
cmd/dolly/main.go
··· 47 47 os.Exit(1) 48 48 } 49 49 50 - if !isValidHexColor(fillColor) { 50 + if fillColor != "currentColor" && !isValidHexColor(fillColor) { 51 51 fmt.Fprintf(os.Stderr, "Invalid color format: %s. Use hex format like #FF5733\n", fillColor) 52 52 os.Exit(1) 53 53 }
+6
docs/logo.html
··· 1 + <div class="flex items-center gap-2 w-fit mx-auto"> 2 + <span class="w-16 h-16 [&>svg]:w-full [&>svg]:h-full text-black dark:text-white"> 3 + ${ dolly.svg() } 4 + </span> 5 + <span class="font-bold text-4xl not-italic text-black dark:text-white">tangled</span> 6 + </div>
+2
docs/template.html
··· 74 74 ${ x.svg() } 75 75 $if(toc-title)$$toc-title$$else$Table of Contents$endif$ 76 76 </button> 77 + ${ logo.html() } 77 78 ${ search.html() } 78 79 ${ table-of-contents:toc.html() } 79 80 </div> ··· 88 89 class="hidden md:flex md:flex-col gap-4 fixed left-0 top-0 w-80 h-screen 89 90 bg-gray-50 dark:bg-gray-800 border-r border-gray-200 dark:border-gray-700 90 91 p-4 z-50 overflow-y-auto"> 92 + ${ logo.html() } 91 93 ${ search.html() } 92 94 <div class="flex-1"> 93 95 $if(toc-title)$
+4
nix/pkgs/docs.nix
··· 5 5 inter-fonts-src, 6 6 ibm-plex-mono-src, 7 7 lucide-src, 8 + dolly, 8 9 src, 9 10 }: 10 11 runCommandLocal "docs" {} '' ··· 17 18 18 19 # icons 19 20 cp -rf ${lucide-src}/*.svg working/ 21 + 22 + # logo 23 + ${dolly}/bin/dolly -output working/dolly.svg -color currentColor 20 24 21 25 # content - chunked 22 26 ${pandoc}/bin/pandoc ${src}/docs/DOCS.md \