Monorepo for Tangled tangled.org
5

Configure Feed

Select the types of activity you want to include in your feed.

appview/pages/templates: restyle

Signed-off-by: Seongmin Lee <git@boltless.me>

author
Seongmin Lee
date (Jun 16, 2026, 2:08 AM +0900) commit c5c8a7ab parent dfc70c3e change-id tltpklks
+193 -77
+26 -2
appview/accountmigration/accountmigration.go
··· 67 67 s.logger.Error("knots lookup failed", "did", user.Did, "err", err) 68 68 knots = nil 69 69 } 70 - s.pages.AccountMigrateFromGitHub(w, pages.AccountMigrateFromGitHubParams{ 70 + if err := s.pages.AccountMigrateFromGitHub(w, pages.AccountMigrateFromGitHubParams{ 71 71 LoggedInUser: user, 72 72 Knots: knots, 73 - }) 73 + Repos: []pages.RepoImportParams{ 74 + { 75 + SourceKind: pages.RepoImportSourceGitHub, 76 + CloneUrl: "https://github.com/boltlessengineer/rest.nvim", 77 + Name: "rest.nvim", 78 + Description: "A very fast, powerful, extensible and asynchronous Neovim HTTP client written in Lua.", 79 + Website: "https://tangled.org", 80 + Topics: []string{"lua", "neovim", "curl", "http-client", "nvim", "neovim-plugin", "rest-client"}, 81 + Stars: 0, 82 + Selected: true, 83 + }, 84 + { 85 + SourceKind: pages.RepoImportSourceGitHub, 86 + CloneUrl: "https://github.com/boltlessengineer/dot", 87 + Name: "super-long-repo-name-fddasfsafdsfasdfaasdfasdfasfasdfasfdasdfasdfsadfsadfasdfaasdfasfasdfdsafd00000s", 88 + Description: "dotfiles", 89 + Website: "https://tangled.org", 90 + Topics: []string{}, 91 + Stars: 12, 92 + Selected: true, 93 + }, 94 + }, 95 + }); err != nil { 96 + s.logger.Error("failed to render", "err", err) 97 + } 74 98 default: 75 99 s.pages.AccountMigrate(w, pages.AccountMigrateParams{ 76 100 LoggedInUser: user,
+1
appview/pages/pages.go
··· 1811 1811 type AccountMigrateFromGitHubParams struct { 1812 1812 LoggedInUser *oauth.MultiAccountUser 1813 1813 Knots []string 1814 + Repos []RepoImportParams 1814 1815 } 1815 1816 1816 1817 type RepoImportSource string
+71 -52
appview/pages/templates/account/migrate/fragments/repoList.html
··· 1 1 {{ define "account/migrate/fragments/repoList" }} 2 2 {{ if not .Repos }} 3 - <p class="text-gray-500 dark:text-gray-400">No importable repositories found.</p> 3 + <p class="text-gray-500 dark:text-gray-400 mb-4">No importable repositories found.</p> 4 4 {{ else }} 5 5 <form action="/account/migrate/start" method="post" class="flex flex-col gap-3"> 6 6 <input type="hidden" name="count" value="{{ len .Repos }}" /> 7 7 8 - <div class="flex items-center gap-2 pb-2 border-b border-gray-200 dark:border-gray-700"> 9 - <button type="button" 10 - onclick="document.querySelectorAll('input[name^=selected_]').forEach(c => c.checked = true)" 11 - class="btn-flat"> 12 - select all 13 - </button> 14 - <button type="button" 15 - onclick="document.querySelectorAll('input[name^=selected_]').forEach(c => c.checked = false)" 16 - class="btn-flat"> 17 - clear 18 - </button> 19 - </div> 8 + <div class="w-full border border-gray-200 dark:border-gray-700 rounded rouded-sm divide-y divide-gray-200 dark:divide-gray-700"> 9 + {{ range $i, $repo := .Repos }} 10 + <details class="group"> 11 + <summary class="p-2 pl-3 flex items-center justify-between gap-2 cursor-pointer"> 12 + <div class="flex gap-2 min-w-0"> 13 + <label class="text-base py-0 min-w-0 flex items-center gap-2"> 14 + <input type="checkbox" id="selected_{{ $i }}" name="selected_{{ $i }}" value="1" {{ if $repo.Selected }}checked{{ end }} class="shrink-0" /> 15 + <span class="min-w-0 truncate">{{ $repo.Name }}</span> 16 + </label> 17 + </div> 20 18 21 - {{ range $i, $repo := .Repos }} 22 - <details class="border border-gray-200 dark:border-gray-700 rounded"> 23 - <summary class="flex flex-col gap-2 sm:flex-row sm:items-center sm:gap-3 px-3 py-2 cursor-pointer list-none"> 24 - <div class="flex items-center gap-3 flex-1 min-w-0"> 25 - <input type="checkbox" name="selected_{{ $i }}" value="1" id="selected_{{ $i }}" 26 - onclick="event.stopPropagation()" 27 - {{ if $repo.Selected }}checked{{ end }} /> 28 - <div class="flex-1 min-w-0"> 29 - <div class="font-medium truncate">{{ $repo.Name }}</div> 30 - <div class="text-xs text-gray-500 dark:text-gray-400 truncate">{{ $repo.CloneUrl }}</div> 19 + <div class="flex items-center gap-2 shrink-0"> 20 + <!-- 21 + <span class="hidden sm:block text-gray-600 dark:text-gray-400"> 22 + Updated 2 days ago 23 + </span> 24 + --> 25 + <div class="p-1"> 26 + {{ i "chevron-right" "size-4 group-open:hidden" }} 27 + {{ i "chevron-down" "size-4 hidden group-open:block" }} 28 + </div> 31 29 </div> 32 - </div> 33 - <select name="knot_{{ $i }}" 34 - onclick="event.stopPropagation()" 35 - class="w-full sm:w-auto rounded border border-gray-300 bg-white dark:bg-gray-800 dark:text-white dark:border-gray-600"> 36 - {{ range $.Knots }} 37 - <option value="{{ . }}">{{ . }}</option> 38 - {{ end }} 39 - </select> 40 - <span class="hidden sm:inline text-gray-400 text-xs select-none">edit</span> 41 - </summary> 30 + </summary> 31 + <div class="border-t border-gray-200 dark:border-gray-700 bg-gray-50 dark:bg-gray-900/50 pt-2 pb-4 px-3 flex flex-col gap-4"> 32 + <input type="hidden" name="website_{{ $i }}" value="{{ $repo.Website }}" /> 33 + <input type="hidden" name="topics_{{ $i }}" value="{{ join $repo.Topics "," }}" /> 34 + 35 + <label class="text-base py-0 space-y-1.5"> 36 + <span>Clone URL</span> 37 + <input type="text" name="clone_url_{{ $i }}" value="{{ $repo.CloneUrl }}" disabled class="w-full px-2 py-1" /> 38 + </label> 42 39 43 - <div class="border-t border-gray-200 dark:border-gray-700 p-3 flex flex-col gap-2"> 44 - <input type="hidden" name="clone_url_{{ $i }}" value="{{ $repo.CloneUrl }}" /> 45 - <input type="hidden" name="website_{{ $i }}" value="{{ $repo.Website }}" /> 46 - <input type="hidden" name="topics_{{ $i }}" value="{{ join $repo.Topics "," }}" /> 40 + <label class="text-base py-0 space-y-1.5"> 41 + <span>Name</span> 42 + <input type="text" name="name_{{ $i }}" value="{{ $repo.Name }}" required placeholder="repository-name" class="bg-white dark:bg-gray-900 w-full px-2 py-1" /> 43 + </label> 47 44 48 - <label class="flex flex-col gap-1 text-sm"> 49 - <span class="text-xs uppercase text-gray-500">Repository name</span> 50 - <input type="text" name="name_{{ $i }}" value="{{ $repo.Name }}" required 51 - class="border border-gray-300 dark:border-gray-700 rounded px-2 py-1 bg-transparent" /> 52 - </label> 45 + <label class="text-base py-0 space-y-1.5"> 46 + <span>Description</span> 47 + <textarea 48 + name="description_{{ $i }}" 49 + placeholder="repository description" 50 + class="bg-white dark:bg-gray-900 w-full px-2 py-1" 51 + >{{ $repo.Description }}</textarea> 52 + </label> 53 53 54 - <label class="flex flex-col gap-1 text-sm"> 55 - <span class="text-xs uppercase text-gray-500">Description</span> 56 - <input type="text" name="description_{{ $i }}" value="{{ $repo.Description }}" maxlength="140" 57 - class="border border-gray-300 dark:border-gray-700 rounded px-2 py-1 bg-transparent" /> 58 - </label> 59 - </div> 60 - </details> 61 - {{ end }} 54 + <fieldset class="space-y-1"> 55 + <legend class="text-base">Select a knot to migrate into</legend> 56 + <div class="w-full space-y-2"> 57 + {{ range $.Knots }} 58 + <label class="py-0 block flex items-center gap-2"> 59 + <input type="radio" name="knot_{{ $i }}" value="{{ . }}" required {{if eq (len $.Knots) 1}}checked{{end}} /> 60 + <span class="lowercase py-0.5">{{ . }}</span> 61 + </label> 62 + {{ else }} 63 + <p class="dark:text-white">No knots available.</p> 64 + {{ end }} 65 + </div> 66 + </fieldset> 67 + </div> 68 + </details> 69 + {{ end }} 70 + </div> 62 71 63 - <div class="pt-3"> 64 - <button type="submit" class="btn">Start migration</button> 72 + <div class="pt-3 flex justify-end"> 73 + <button type="submit" class="btn-create flex items-center gap-2" {{ if (not .Repos) }}disabled{{ end }}> 74 + {{ i "import" "size-4" }} 75 + {{ if gt (len .Repos) 1 }} 76 + Migrate {{ len .Repos }} Repositories 77 + {{ else }} 78 + Migrate Repository 79 + {{ end }} 80 + <span id="spinner" class="group"> 81 + {{ i "loader-circle" "size-4 animate-spin hidden group-[.htmx-request]:inline" }} 82 + </span> 83 + </button> 65 84 </div> 66 85 </form> 67 86 {{ end }}
+85 -23
appview/pages/templates/account/migrate/fromGitHub.html
··· 1 - {{ define "title" }}Migrate from GitHub{{ end }} 1 + {{ define "title" }}Migrate repositories from GitHub{{ end }} 2 2 3 3 {{ define "content" }} 4 - <div class="p-6"> 5 - <p class="text-xl font-bold dark:text-white">Migrate from GitHub</p> 6 - <p class="text-gray-500 dark:text-gray-400 pb-2"> 7 - Enter a GitHub username to list their public repositories. 8 - </p> 4 + <div class="mt-6 mb-4"> 5 + <h1 class="font-medium"> 6 + {{ i "import" "size-5 inline m-0.5" }} 7 + Migrate repositories 8 + </h1> 9 9 </div> 10 - <div class="bg-white dark:bg-gray-800 p-6 rounded w-full mx-auto drop-shadow-sm dark:text-white flex flex-col gap-4"> 11 - <form 12 - hx-post="/account/migrate/listGitHubRepos" 13 - hx-target="#source-repos" 14 - hx-swap="innerHTML" 15 - class="flex flex-wrap gap-2 items-center" 16 - > 17 - <input 18 - type="text" 19 - name="username" 20 - required 21 - placeholder="GitHub username" 22 - class="border border-gray-300 dark:border-gray-700 rounded px-2 py-1 bg-transparent flex-1 min-w-0" 23 - /> 24 - <button type="submit" class="btn">Load repos</button> 25 - </form> 10 + <div class="flex gap-3 p-3 my-4 rounded-md bg-yellow-50 dark:bg-yellow-900/50"> 11 + <div class="flex items-center px-1 text-yellow-700 dark:text-yellow-400"> 12 + {{ i "badge-info" "size-6" }} 13 + </div> 14 + <p>Migration covers your repos and commit history.<br>Issues and PRs aren't part of the import yet.</p> 15 + </div> 16 + <div class="bg-white dark:bg-gray-800 p-8 pb-0 rounded w-full mx-auto drop-shadow-sm dark:text-white"> 17 + {{ template "step-1" . }} 18 + {{ template "step-2" . }} 26 19 27 20 <span id="migrate-error" class="error"></span> 28 21 ··· 32 25 </p> 33 26 {{ end }} 34 27 35 - <div id="source-repos"></div> 28 + </div> 29 + {{ end }} 30 + 31 + {{ define "step-1" }} 32 + <div class="flex relative border-l border-gray-200 dark:border-gray-700 ml-3 pl-7"> 33 + <div class="absolute -left-3 -top-0"> 34 + {{ template "numberCircle" 1 }} 35 + </div> 36 + 37 + <!-- Content column --> 38 + <form 39 + hx-post="/account/migrate/listGitHubRepos" 40 + hx-target="#source-repos" 41 + hx-swap="innerHTML" 42 + class="flex-1 min-w-0 pb-12 space-y-3" 43 + > 44 + 45 + <h2 class="text-lg font-semibold uppercase dark:text-white">Connect Your Account</h2> 46 + <div class="flex gap-3 w-full"> 47 + <input 48 + id="username" 49 + type="text" 50 + name="username" 51 + required 52 + placeholder="github-username" 53 + class="flex-1 py-2" 54 + /> 55 + <button type="submit" class="btn"> 56 + Load repos 57 + {{ i "loader-circle" "size-4 animate-spin hidden group-[.htmx-request]:inline" }} 58 + </button> 59 + </div> 60 + </form> 61 + </div> 62 + {{ end }} 63 + 64 + {{ define "step-2" }} 65 + <div class="flex relative border-l border-gray-200 dark:border-gray-700 ml-3 pl-7"> 66 + <div class="absolute -left-3 -top-0"> 67 + {{ template "numberCircle" 2 }} 68 + </div> 69 + 70 + <!-- Content column --> 71 + <div class="flex-1 min-w-0 pb-12 space-y-3"> 72 + <div class="flex flex-wrap items-center justify-between gap-3"> 73 + <h2 class="text-lg font-semibold uppercase">Choose &amp; Configure Repositories</h2> 74 + <div class="ml-auto h-7 flex items-center"> 75 + <button 76 + class="btn-flat flex items-center gap-2" 77 + onclick="document.querySelectorAll('input[name^=selected_]').forEach(c => c.checked = !c.checked)" 78 + > 79 + {{ i "check-check" "size-4" }} 80 + Select all repositories 81 + </button> 82 + </div> 83 + </div> 84 + <div id="source-repos"> 85 + <!-- 86 + <p class="text-gray-500 dark:text-gray-400 mb-4">No repositories yet, connect your account first</p> 87 + --> 88 + 89 + {{ template "account/migrate/fragments/repoList" . }} 90 + </div> 91 + </div> 92 + </div> 93 + {{ end }} 94 + 95 + {{ define "numberCircle" }} 96 + <div class="size-7 bg-gray-200 dark:bg-gray-600 rounded-full flex items-center justify-center font-medium"> 97 + {{.}} 36 98 </div> 37 99 {{ end }}
+10
input.css
··· 102 102 focus:outline-none focus:ring-1 focus:ring-gray-400 dark:focus:ring-gray-500; 103 103 } 104 104 105 + input:disabled, 106 + input:readonly, 107 + textarea:disabled, 108 + textarea:readonly { 109 + @apply bg-gray-100 dark:bg-gray-900 110 + text-gray-500 dark:text-gray-500 111 + border-gray-200 dark:border-gray-700 112 + cursor-not-allowed; 113 + } 114 + 105 115 input[type="checkbox"] { 106 116 @apply appearance-none size-4 p-0 rounded 107 117 bg-transparent border border-gray-200