this repo has no descr,ription vt3e.cat

feat: projects page

vt3e.cat 1afc898b 0430f7a6

verified
+277 -6
+3
bun.lock
··· 7 7 "@atcute/atproto": "^3.1.10", 8 8 "@atcute/client": "^4.2.0", 9 9 "@atcute/lexicons": "^1.2.6", 10 + "@atcute/tangled": "^1.0.13", 10 11 "@iconify-prerendered/vue-material-symbols": "^0.28.1755063979", 11 12 "blurhash": "^2.0.5", 12 13 }, ··· 92 93 "@atcute/lexicon-doc": ["@atcute/lexicon-doc@1.1.3", "", { "dependencies": { "@badrap/valita": "^0.4.6" } }, "sha512-HlQBmB4NCZPzREyVzr7lzjRxSiRHook2xfa7DgA3dk3oYZ+KnnPEtS6M1sAmAAddtUdrOrJ+0xJPQHkfElZmpQ=="], 93 94 94 95 "@atcute/lexicons": ["@atcute/lexicons@1.2.6", "", { "dependencies": { "@atcute/uint8array": "^1.0.6", "@atcute/util-text": "^0.0.1", "@standard-schema/spec": "^1.1.0", "esm-env": "^1.2.2" } }, "sha512-s76UQd8D+XmHIzrjD9CJ9SOOeeLPHc+sMmcj7UFakAW/dDFXc579fcRdRfuUKvXBL5v1Gs2VgDdlh/IvvQZAwA=="], 96 + 97 + "@atcute/tangled": ["@atcute/tangled@1.0.13", "", { "dependencies": { "@atcute/atproto": "^3.1.9", "@atcute/lexicons": "^1.2.5" } }, "sha512-K95jmjDXl/f1FFzOJkk07ibNbFsPmn64sdrMACxQmUibO9WcfSjzjZLPXuH6WHFnCNtIBG3x1FQ7ndQgLoZAmw=="], 95 98 96 99 "@atcute/uint8array": ["@atcute/uint8array@1.0.6", "", {}, "sha512-ucfRBQc7BFT8n9eCyGOzDHEMKF/nZwhS2pPao4Xtab1ML3HdFYcX2DM1tadCzas85QTGxHe5urnUAAcNKGRi9A=="], 97 100
+1
package.json
··· 26 26 "@atcute/atproto": "^3.1.10", 27 27 "@atcute/client": "^4.2.0", 28 28 "@atcute/lexicons": "^1.2.6", 29 + "@atcute/tangled": "^1.0.13", 29 30 "@iconify-prerendered/vue-material-symbols": "^0.28.1755063979", 30 31 "blurhash": "^2.0.5" 31 32 }
+5 -3
pkgs/web/src/components/Card/CardLayout.vue
··· 264 264 padding: 1rem; 265 265 266 266 .card-intro { 267 - margin-bottom: 1.5rem; 268 - padding-bottom: 1rem; 269 - border-bottom: 1px solid hsla(var(--overlay1) / 0.25); 267 + &:has(> *) { 268 + margin-bottom: 1.5rem; 269 + padding-bottom: 1rem; 270 + border-bottom: 1px solid hsla(var(--overlay1) / 0.25); 271 + } 270 272 271 273 :deep(h2) { 272 274 margin-top: 0;
+267 -2
pkgs/web/src/views/ProjectsView.vue
··· 1 1 <script setup lang="ts"> 2 + import { onMounted, ref } from 'vue' 3 + import { IconWeb } from '@iconify-prerendered/vue-material-symbols' 4 + import { Client, simpleFetchHandler, ok } from '@atcute/client' 5 + import type { ShTangledRepo, ShTangledRepoLanguages } from '@atcute/tangled' 6 + 7 + import TangledLogo from '@/assets/icons/tangled.svg?raw' 8 + import SvgComponent from '@/components/SvgComponent.vue' 9 + 2 10 import CardLayout from '@/components/Card/CardLayout.vue' 11 + import { DID } from '@/utils/links' 12 + import type { ResourceUri } from '@atcute/lexicons' 13 + 14 + const repoUrl = (repo: ShTangledRepo.Main) => { 15 + return `https://tangled.org/${DID}/${repo.name}` 16 + } 17 + 18 + const repositories = ref<{ uri: ResourceUri; value: ShTangledRepo.Main }[]>([]) 19 + const repoLanguages = ref<Record<ResourceUri, ShTangledRepoLanguages.Language[]>>({}) 20 + const loading = ref(true) 21 + const error = ref<string | null>(null) 22 + 23 + const formatDate = (d?: string) => { 24 + if (!d) return '' 25 + try { 26 + return new Date(d).toLocaleDateString(undefined, { year: 'numeric', month: 'long' }) 27 + } catch { 28 + return d 29 + } 30 + } 31 + 32 + onMounted(async () => { 33 + const manager = simpleFetchHandler({ service: 'https://pds.wlo.moe' }) 34 + const client = new Client({ handler: manager }) 35 + 36 + const reposRes = await client.get('com.atproto.repo.listRecords', { 37 + params: { 38 + repo: DID, 39 + collection: 'sh.tangled.repo', 40 + }, 41 + }) 42 + 43 + if (!reposRes.ok) { 44 + error.value = 45 + reposRes.data.error || 'An unknown error occurred while fetching repositories from tangled.' 46 + loading.value = false 47 + return 48 + } 49 + 50 + repositories.value = reposRes.data.records.map((record) => { 51 + return { uri: record.uri, value: record.value as ShTangledRepo.Main } 52 + }) 53 + loading.value = false 54 + 55 + await Promise.all( 56 + repositories.value.map(async (repo) => { 57 + const knotManager = simpleFetchHandler({ service: `https://${repo.value.knot}` }) 58 + const knotRpc = new Client({ handler: knotManager }) 59 + 60 + const defaultBranch = ok( 61 + await knotRpc.get('sh.tangled.repo.getDefaultBranch', { 62 + params: { 63 + repo: `${DID}/${repo.value.name}`, 64 + }, 65 + }), 66 + ) 67 + 68 + const languages = ok( 69 + await knotRpc.get('sh.tangled.repo.languages', { 70 + params: { 71 + repo: `${DID}/${repo.value.name}`, 72 + ref: defaultBranch.name, 73 + }, 74 + }), 75 + ) 76 + 77 + repoLanguages.value[repo.uri] = languages.languages 78 + .filter((lang) => lang.name !== '') 79 + .slice(0, 3) 80 + }), 81 + ) 82 + }) 3 83 </script> 4 84 5 85 <template> 6 - <CardLayout title="placeholder!"> i love place holding :D </CardLayout> 86 + <CardLayout title="projects"> 87 + <section class="main"> 88 + <h2></h2> 89 + </section> 90 + 91 + <section class="repositories"> 92 + <h2>all repositories</h2> 93 + <p>these are all the repositories i've published on tangled.</p> 94 + 95 + <div class="repositories-list"> 96 + <article v-for="repo in repositories" :key="repo.uri" class="repository"> 97 + <div class="repo-header"> 98 + <div class="repo-meta"> 99 + <time 100 + class="repo-tag repo-created" 101 + :datetime="repo.value.createdAt" 102 + :aria-label="`created on ${formatDate(repo.value.createdAt)}`" 103 + :title="`created on ${formatDate(repo.value.createdAt)}`" 104 + > 105 + {{ formatDate(repo.value.createdAt) }} 106 + </time> 107 + <div class="tags" v-if="repo.value.topics && repo.value.topics.length"> 108 + <span v-for="tag in repo.value.topics" :key="tag" class="repo-tag"> 109 + {{ tag }} 110 + </span> 111 + </div> 112 + <div class="tags" v-if="repoLanguages[repo.uri] && repoLanguages[repo.uri]?.length"> 113 + <span v-for="lang in repoLanguages[repo.uri]" :key="lang.name" class="repo-tag">{{ 114 + lang.name 115 + }}</span> 116 + </div> 117 + </div> 118 + <h3 class="repo-name">{{ repo.value.name }}</h3> 119 + <p class="repo-description">{{ repo.value.description }}</p> 120 + </div> 121 + <div class="repo-links"> 122 + <a :href="repoUrl(repo.value)" target="_blank" rel="noopener noreferrer"> 123 + <SvgComponent :icon="TangledLogo" :decorative="true" /> 124 + repository 125 + </a> 126 + <a 127 + v-if="repo.value.website" 128 + :href="repo.value.website" 129 + target="_blank" 130 + rel="noopener noreferrer" 131 + > 132 + <IconWeb role="decorative" aria-hidden="true" /> 133 + website 134 + </a> 135 + </div> 136 + </article> 137 + </div> 138 + </section> 139 + </CardLayout> 7 140 </template> 8 141 9 - <style scoped></style> 142 + <style scoped lang="scss"> 143 + .repositories { 144 + h2 { 145 + margin-bottom: 0; 146 + font-size: 1.5rem; 147 + font-weight: 800; 148 + } 149 + p { 150 + margin-top: 0; 151 + margin-bottom: 1rem; 152 + color: var(--text-secondary); 153 + } 154 + } 155 + 156 + .repositories-list { 157 + display: flex; 158 + flex-direction: column; 159 + gap: 0.25rem; 160 + 161 + .repository { 162 + background-color: hsla(var(--surface0) / 1); 163 + padding: 0.5rem; 164 + border-radius: 0.5rem; 165 + 166 + &:first-child { 167 + border-radius: 1rem 1rem 0.5rem 0.5rem; 168 + } 169 + &:last-child { 170 + border-radius: 0.5rem 0.5rem 1rem 1rem; 171 + } 172 + 173 + .repo-header { 174 + margin-bottom: 0.75rem; 175 + 176 + .repo-meta { 177 + display: inline-flex; 178 + padding: 0.25rem; 179 + border-radius: 5rem; 180 + flex-direction: row; 181 + align-items: center; 182 + gap: 0.25rem; 183 + 184 + background-color: hsla(var(--accent) / 0.075); 185 + 186 + .repo-tag { 187 + font-size: 0.75rem; 188 + color: hsl(var(--subtext1)); 189 + background-color: hsla(var(--page-accent) / 0.1); 190 + user-select: none; 191 + padding: 0.15rem 0.5rem; 192 + border-radius: 2rem; 193 + font-weight: 700; 194 + text-transform: lowercase; 195 + 196 + &.repo-created { 197 + border-radius: 2rem; 198 + background-color: hsla(var(--page-accent) / 0.2); 199 + } 200 + &:hover { 201 + background-color: hsla(var(--page-accent) / 0.15); 202 + } 203 + } 204 + 205 + .tags { 206 + display: inline-flex; 207 + flex-wrap: wrap; 208 + gap: 0.2rem; 209 + 210 + .repo-tag { 211 + border-radius: 0.25rem; 212 + 213 + &:first-child { 214 + border-top-left-radius: 1rem; 215 + border-bottom-left-radius: 1rem; 216 + } 217 + &:last-child { 218 + border-top-right-radius: 1rem; 219 + border-bottom-right-radius: 1rem; 220 + } 221 + } 222 + } 223 + } 224 + 225 + .repo-name { 226 + margin: 0; 227 + font-size: 1.25rem; 228 + font-weight: 600; 229 + margin-left: 0.65rem; 230 + } 231 + 232 + .repo-description { 233 + color: var(--text-secondary); 234 + margin-left: 0.65rem; 235 + } 236 + } 237 + 238 + .repo-links { 239 + display: flex; 240 + gap: 0.5rem; 241 + 242 + a { 243 + padding: 0.5rem 0.85rem; 244 + background-color: hsla(var(--page-accent) / 0.05); 245 + border: 1px solid var(--border-color); 246 + border-radius: 0.5rem; 247 + font-size: 0.875rem; 248 + display: inline-flex; 249 + align-items: center; 250 + gap: 0.5rem; 251 + color: hsl(var(--subtext0)); 252 + text-decoration: none; 253 + font-weight: 500; 254 + 255 + :deep(svg) { 256 + fill: currentColor; 257 + height: 1.5em; 258 + width: 1.5em; 259 + } 260 + 261 + &:hover, 262 + &:focus-visible { 263 + background-color: hsla(var(--page-accent) / 0.1); 264 + color: hsl(var(--page-accent)); 265 + } 266 + &:active, 267 + &.active { 268 + background-color: hsla(var(--page-accent) / 0.04); 269 + } 270 + } 271 + } 272 + } 273 + } 274 + </style>
+1 -1
pkgs/web/tsconfig.app.json
··· 4 4 "exclude": ["src/**/__tests__/*"], 5 5 "compilerOptions": { 6 6 "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo", 7 - "types": ["@atcute/atproto", "@sillowww/gallery"], 7 + "types": ["@atcute/atproto", "@atcute/tangled", "@sillowww/gallery"], 8 8 "paths": { 9 9 "@/*": ["./src/*"], 10 10 },