[READ-ONLY] Mirror of https://github.com/flo-bit/bluesky-website-generator. bluesky-website-generator.vercel.app
0

Configure Feed

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

small fixes

+710 -109
+1
package.json
··· 51 51 "@atproto/oauth-client-node": "^0.2.16", 52 52 "@atproto/syntax": "^0.4.0", 53 53 "@fuxui/base": "^0.0.7", 54 + "@fuxui/colors": "^0.1.7", 54 55 "@fuxui/social": "^0.0.9", 55 56 "@libsql/client": "^0.14.0", 56 57 "@oslojs/encoding": "^1.1.0",
+23
pnpm-lock.yaml
··· 20 20 '@fuxui/base': 21 21 specifier: ^0.0.7 22 22 version: 0.0.7(svelte@5.28.2)(tailwindcss@4.1.5) 23 + '@fuxui/colors': 24 + specifier: ^0.1.7 25 + version: 0.1.7(svelte@5.28.2)(tailwindcss@4.1.5) 23 26 '@fuxui/social': 24 27 specifier: ^0.0.9 25 28 version: 0.0.9(svelte@5.28.2)(tailwindcss@4.1.5) ··· 686 689 svelte: '>=5' 687 690 tailwindcss: '>=3' 688 691 692 + '@fuxui/colors@0.1.7': 693 + resolution: {integrity: sha512-nudN3g26OgPXxRJK20QQ9DzVls1uIHS06mPAv+1feBCwXN44E4q6UF0CjuKPV27pq3itxFPkgnX7r2PTidc5WQ==, tarball: https://registry.npmjs.org/@fuxui/colors/-/colors-0.1.7.tgz} 694 + peerDependencies: 695 + svelte: '>=5' 696 + tailwindcss: '>=3' 697 + 689 698 '@fuxui/social@0.0.9': 690 699 resolution: {integrity: sha512-fWvXr0B+vUKX4sU+fymeLxpO2Z5/BR4ix+iCq/zdX3wpERJqr228+btU9SVnNllVjulspdnnrrrPJcIioNZBIw==, tarball: https://registry.npmjs.org/@fuxui/social/-/social-0.0.9.tgz} 691 700 peerDependencies: ··· 1056 1065 resolution: {integrity: sha512-FE1stRoqdHSb7RxesMfCXE8icwI1W6zGE/512ae3ZDrpkQYTTYeSyUJPRCjZd8CwVAhpDUbi1YR8pcZioFJQ/w==, tarball: https://registry.npmjs.org/@tailwindcss/vite/-/vite-4.1.5.tgz} 1057 1066 peerDependencies: 1058 1067 vite: ^5.2.0 || ^6 1068 + 1069 + '@texel/color@1.1.10': 1070 + resolution: {integrity: sha512-0du+jhP68nvdXGoS/dxAyqkmWWzsogtU3chygmNf/u4QtDIbG1e5OfcVIWfx29B5ANCLEDOPEQIvOhrvwhlORg==, tarball: https://registry.npmjs.org/@texel/color/-/color-1.1.10.tgz} 1059 1071 1060 1072 '@tiptap/core@2.11.9': 1061 1073 resolution: {integrity: sha512-UZSxQLLyJst47xep3jlyKM6y1ebZnmvbGsB7njBVjfxf5H+4yFpRJwwNqrBHM/vyU55LCtPChojqaYC1wXLf6g==, tarball: https://registry.npmjs.org/@tiptap/core/-/core-2.11.9.tgz} ··· 3228 3240 tailwind-variants: 1.0.0(tailwindcss@4.1.5) 3229 3241 tailwindcss: 4.1.5 3230 3242 3243 + '@fuxui/colors@0.1.7(svelte@5.28.2)(tailwindcss@4.1.5)': 3244 + dependencies: 3245 + '@fuxui/base': 0.0.7(svelte@5.28.2)(tailwindcss@4.1.5) 3246 + '@texel/color': 1.1.10 3247 + '@use-gesture/vanilla': 10.3.1 3248 + bits-ui: 1.4.4(svelte@5.28.2) 3249 + svelte: 5.28.2 3250 + tailwindcss: 4.1.5 3251 + 3231 3252 '@fuxui/social@0.0.9(svelte@5.28.2)(tailwindcss@4.1.5)': 3232 3253 dependencies: 3233 3254 '@atproto/api': 0.15.5 ··· 3563 3584 '@tailwindcss/oxide': 4.1.5 3564 3585 tailwindcss: 4.1.5 3565 3586 vite: 6.3.4(@types/node@22.15.3)(jiti@2.4.2)(lightningcss@1.29.2) 3587 + 3588 + '@texel/color@1.1.10': {} 3566 3589 3567 3590 '@tiptap/core@2.11.9(@tiptap/pm@2.11.9)': 3568 3591 dependencies:
+360 -23
src/app.css
··· 4 4 5 5 @source "../node_modules/@fuxui"; 6 6 7 - @theme { 8 - --color-base-50: var(--color-zinc-50); 9 - --color-base-100: var(--color-zinc-100); 10 - --color-base-200: var(--color-zinc-200); 11 - --color-base-300: var(--color-zinc-300); 12 - --color-base-400: var(--color-zinc-400); 13 - --color-base-500: var(--color-zinc-500); 14 - --color-base-600: var(--color-zinc-600); 15 - --color-base-700: var(--color-zinc-700); 16 - --color-base-800: var(--color-zinc-800); 17 - --color-base-900: var(--color-zinc-900); 18 - --color-base-950: var(--color-zinc-950); 7 + @custom-variant dark (&:is(.dark *)); 19 8 20 - --color-accent-50: var(--color-emerald-50); 21 - --color-accent-100: var(--color-emerald-100); 22 - --color-accent-200: var(--color-emerald-200); 23 - --color-accent-300: var(--color-emerald-300); 24 - --color-accent-400: var(--color-emerald-400); 25 - --color-accent-500: var(--color-emerald-500); 26 - --color-accent-600: var(--color-emerald-600); 27 - --color-accent-700: var(--color-emerald-700); 28 - --color-accent-800: var(--color-emerald-800); 29 - --color-accent-900: var(--color-emerald-900); 30 - --color-accent-950: var(--color-emerald-950); 9 + @theme inline { 10 + --color-base-50: var(--base-50); 11 + --color-base-100: var(--base-100); 12 + --color-base-200: var(--base-200); 13 + --color-base-300: var(--base-300); 14 + --color-base-400: var(--base-400); 15 + --color-base-500: var(--base-500); 16 + --color-base-600: var(--base-600); 17 + --color-base-700: var(--base-700); 18 + --color-base-800: var(--base-800); 19 + --color-base-900: var(--base-900); 20 + --color-base-950: var(--base-950); 21 + 22 + --color-accent-50: var(--accent-50); 23 + --color-accent-100: var(--accent-100); 24 + --color-accent-200: var(--accent-200); 25 + --color-accent-300: var(--accent-300); 26 + --color-accent-400: var(--accent-400); 27 + --color-accent-500: var(--accent-500); 28 + --color-accent-600: var(--accent-600); 29 + --color-accent-700: var(--accent-700); 30 + --color-accent-800: var(--accent-800); 31 + --color-accent-900: var(--accent-900); 32 + --color-accent-950: var(--accent-950); 31 33 } 34 + 35 + @layer base { 36 + :root { 37 + --accent-50: var(--color-pink-50); 38 + --accent-100: var(--color-pink-100); 39 + --accent-200: var(--color-pink-200); 40 + --accent-300: var(--color-pink-300); 41 + --accent-400: var(--color-pink-400); 42 + --accent-500: var(--color-pink-500); 43 + --accent-600: var(--color-pink-600); 44 + --accent-700: var(--color-pink-700); 45 + --accent-800: var(--color-pink-800); 46 + --accent-900: var(--color-pink-900); 47 + --accent-950: var(--color-pink-950); 48 + 49 + --base-50: var(--color-stone-50); 50 + --base-100: var(--color-stone-100); 51 + --base-200: var(--color-stone-200); 52 + --base-300: var(--color-stone-300); 53 + --base-400: var(--color-stone-400); 54 + --base-500: var(--color-stone-500); 55 + --base-600: var(--color-stone-600); 56 + --base-700: var(--color-stone-700); 57 + --base-800: var(--color-stone-800); 58 + --base-900: var(--color-stone-900); 59 + --base-950: var(--color-stone-950); 60 + } 61 + 62 + .red { 63 + --accent-50: var(--color-red-50); 64 + --accent-100: var(--color-red-100); 65 + --accent-200: var(--color-red-200); 66 + --accent-300: var(--color-red-300); 67 + --accent-400: var(--color-red-400); 68 + --accent-500: var(--color-red-500); 69 + --accent-600: var(--color-red-600); 70 + --accent-700: var(--color-red-700); 71 + --accent-800: var(--color-red-800); 72 + --accent-900: var(--color-red-900); 73 + --accent-950: var(--color-red-950); 74 + } 75 + 76 + .orange { 77 + --accent-50: var(--color-orange-50); 78 + --accent-100: var(--color-orange-100); 79 + --accent-200: var(--color-orange-200); 80 + --accent-300: var(--color-orange-300); 81 + --accent-400: var(--color-orange-400); 82 + --accent-500: var(--color-orange-500); 83 + --accent-600: var(--color-orange-600); 84 + --accent-700: var(--color-orange-700); 85 + --accent-800: var(--color-orange-800); 86 + --accent-900: var(--color-orange-900); 87 + --accent-950: var(--color-orange-950); 88 + } 89 + 90 + .amber { 91 + --accent-50: var(--color-amber-50); 92 + --accent-100: var(--color-amber-100); 93 + --accent-200: var(--color-amber-200); 94 + --accent-300: var(--color-amber-300); 95 + --accent-400: var(--color-amber-400); 96 + --accent-500: var(--color-amber-500); 97 + --accent-600: var(--color-amber-600); 98 + --accent-700: var(--color-amber-700); 99 + --accent-800: var(--color-amber-800); 100 + --accent-900: var(--color-amber-900); 101 + --accent-950: var(--color-amber-950); 102 + } 103 + 104 + .yellow { 105 + --accent-50: var(--color-yellow-50); 106 + --accent-100: var(--color-yellow-100); 107 + --accent-200: var(--color-yellow-200); 108 + --accent-300: var(--color-yellow-300); 109 + --accent-400: var(--color-yellow-400); 110 + --accent-500: var(--color-yellow-500); 111 + --accent-600: var(--color-yellow-600); 112 + --accent-700: var(--color-yellow-700); 113 + --accent-800: var(--color-yellow-800); 114 + --accent-900: var(--color-yellow-900); 115 + --accent-950: var(--color-yellow-950); 116 + } 117 + 118 + .lime { 119 + --accent-50: var(--color-lime-50); 120 + --accent-100: var(--color-lime-100); 121 + --accent-200: var(--color-lime-200); 122 + --accent-300: var(--color-lime-300); 123 + --accent-400: var(--color-lime-400); 124 + --accent-500: var(--color-lime-500); 125 + --accent-600: var(--color-lime-600); 126 + --accent-700: var(--color-lime-700); 127 + --accent-800: var(--color-lime-800); 128 + --accent-900: var(--color-lime-900); 129 + --accent-950: var(--color-lime-950); 130 + } 131 + 132 + .green { 133 + --accent-50: var(--color-green-50); 134 + --accent-100: var(--color-green-100); 135 + --accent-200: var(--color-green-200); 136 + --accent-300: var(--color-green-300); 137 + --accent-400: var(--color-green-400); 138 + --accent-500: var(--color-green-500); 139 + --accent-600: var(--color-green-600); 140 + --accent-700: var(--color-green-700); 141 + --accent-800: var(--color-green-800); 142 + --accent-900: var(--color-green-900); 143 + --accent-950: var(--color-green-950); 144 + } 145 + 146 + .emerald { 147 + --accent-50: var(--color-emerald-50); 148 + --accent-100: var(--color-emerald-100); 149 + --accent-200: var(--color-emerald-200); 150 + --accent-300: var(--color-emerald-300); 151 + --accent-400: var(--color-emerald-400); 152 + --accent-500: var(--color-emerald-500); 153 + --accent-600: var(--color-emerald-600); 154 + --accent-700: var(--color-emerald-700); 155 + --accent-800: var(--color-emerald-800); 156 + --accent-900: var(--color-emerald-900); 157 + --accent-950: var(--color-emerald-950); 158 + } 159 + 160 + .teal { 161 + --accent-50: var(--color-teal-50); 162 + --accent-100: var(--color-teal-100); 163 + --accent-200: var(--color-teal-200); 164 + --accent-300: var(--color-teal-300); 165 + --accent-400: var(--color-teal-400); 166 + --accent-500: var(--color-teal-500); 167 + --accent-600: var(--color-teal-600); 168 + --accent-700: var(--color-teal-700); 169 + --accent-800: var(--color-teal-800); 170 + --accent-900: var(--color-teal-900); 171 + --accent-950: var(--color-teal-950); 172 + } 173 + 174 + .cyan { 175 + --accent-50: var(--color-cyan-50); 176 + --accent-100: var(--color-cyan-100); 177 + --accent-200: var(--color-cyan-200); 178 + --accent-300: var(--color-cyan-300); 179 + --accent-400: var(--color-cyan-400); 180 + --accent-500: var(--color-cyan-500); 181 + --accent-600: var(--color-cyan-600); 182 + --accent-700: var(--color-cyan-700); 183 + --accent-800: var(--color-cyan-800); 184 + --accent-900: var(--color-cyan-900); 185 + --accent-950: var(--color-cyan-950); 186 + } 187 + 188 + .sky { 189 + --accent-50: var(--color-sky-50); 190 + --accent-100: var(--color-sky-100); 191 + --accent-200: var(--color-sky-200); 192 + --accent-300: var(--color-sky-300); 193 + --accent-400: var(--color-sky-400); 194 + --accent-500: var(--color-sky-500); 195 + --accent-600: var(--color-sky-600); 196 + --accent-700: var(--color-sky-700); 197 + --accent-800: var(--color-sky-800); 198 + --accent-900: var(--color-sky-900); 199 + --accent-950: var(--color-sky-950); 200 + } 201 + .blue { 202 + --accent-50: var(--color-blue-50); 203 + --accent-100: var(--color-blue-100); 204 + --accent-200: var(--color-blue-200); 205 + --accent-300: var(--color-blue-300); 206 + --accent-400: var(--color-blue-400); 207 + --accent-500: var(--color-blue-500); 208 + --accent-600: var(--color-blue-600); 209 + --accent-700: var(--color-blue-700); 210 + --accent-800: var(--color-blue-800); 211 + --accent-900: var(--color-blue-900); 212 + --accent-950: var(--color-blue-950); 213 + } 214 + 215 + .indigo { 216 + --accent-50: var(--color-indigo-50); 217 + --accent-100: var(--color-indigo-100); 218 + --accent-200: var(--color-indigo-200); 219 + --accent-300: var(--color-indigo-300); 220 + --accent-400: var(--color-indigo-400); 221 + --accent-500: var(--color-indigo-500); 222 + --accent-600: var(--color-indigo-600); 223 + --accent-700: var(--color-indigo-700); 224 + --accent-800: var(--color-indigo-800); 225 + --accent-900: var(--color-indigo-900); 226 + --accent-950: var(--color-indigo-950); 227 + } 228 + 229 + .violet { 230 + --accent-50: var(--color-violet-50); 231 + --accent-100: var(--color-violet-100); 232 + --accent-200: var(--color-violet-200); 233 + --accent-300: var(--color-violet-300); 234 + --accent-400: var(--color-violet-400); 235 + --accent-500: var(--color-violet-500); 236 + --accent-600: var(--color-violet-600); 237 + --accent-700: var(--color-violet-700); 238 + --accent-800: var(--color-violet-800); 239 + --accent-900: var(--color-violet-900); 240 + --accent-950: var(--color-violet-950); 241 + } 242 + 243 + .purple { 244 + --accent-50: var(--color-purple-50); 245 + --accent-100: var(--color-purple-100); 246 + --accent-200: var(--color-purple-200); 247 + --accent-300: var(--color-purple-300); 248 + --accent-400: var(--color-purple-400); 249 + --accent-500: var(--color-purple-500); 250 + --accent-600: var(--color-purple-600); 251 + --accent-700: var(--color-purple-700); 252 + --accent-800: var(--color-purple-800); 253 + --accent-900: var(--color-purple-900); 254 + --accent-950: var(--color-purple-950); 255 + } 256 + 257 + .fuchsia { 258 + --accent-50: var(--color-fuchsia-50); 259 + --accent-100: var(--color-fuchsia-100); 260 + --accent-200: var(--color-fuchsia-200); 261 + --accent-300: var(--color-fuchsia-300); 262 + --accent-400: var(--color-fuchsia-400); 263 + --accent-500: var(--color-fuchsia-500); 264 + --accent-600: var(--color-fuchsia-600); 265 + --accent-700: var(--color-fuchsia-700); 266 + --accent-800: var(--color-fuchsia-800); 267 + --accent-900: var(--color-fuchsia-900); 268 + --accent-950: var(--color-fuchsia-950); 269 + } 270 + 271 + .pink { 272 + --accent-50: var(--color-pink-50); 273 + --accent-100: var(--color-pink-100); 274 + --accent-200: var(--color-pink-200); 275 + --accent-300: var(--color-pink-300); 276 + --accent-400: var(--color-pink-400); 277 + --accent-500: var(--color-pink-500); 278 + --accent-600: var(--color-pink-600); 279 + --accent-700: var(--color-pink-700); 280 + --accent-800: var(--color-pink-800); 281 + --accent-900: var(--color-pink-900); 282 + --accent-950: var(--color-pink-950); 283 + } 284 + 285 + .rose { 286 + --accent-50: var(--color-rose-50); 287 + --accent-100: var(--color-rose-100); 288 + --accent-200: var(--color-rose-200); 289 + --accent-300: var(--color-rose-300); 290 + --accent-400: var(--color-rose-400); 291 + --accent-500: var(--color-rose-500); 292 + --accent-600: var(--color-rose-600); 293 + --accent-700: var(--color-rose-700); 294 + --accent-800: var(--color-rose-800); 295 + --accent-900: var(--color-rose-900); 296 + --accent-950: var(--color-rose-950); 297 + } 298 + 299 + .gray { 300 + --base-50: var(--color-gray-50); 301 + --base-100: var(--color-gray-100); 302 + --base-200: var(--color-gray-200); 303 + --base-300: var(--color-gray-300); 304 + --base-400: var(--color-gray-400); 305 + --base-500: var(--color-gray-500); 306 + --base-600: var(--color-gray-600); 307 + --base-700: var(--color-gray-700); 308 + --base-800: var(--color-gray-800); 309 + --base-900: var(--color-gray-900); 310 + --base-950: var(--color-gray-950); 311 + } 312 + 313 + .zinc { 314 + --base-50: var(--color-zinc-50); 315 + --base-100: var(--color-zinc-100); 316 + --base-200: var(--color-zinc-200); 317 + --base-300: var(--color-zinc-300); 318 + --base-400: var(--color-zinc-400); 319 + --base-500: var(--color-zinc-500); 320 + --base-600: var(--color-zinc-600); 321 + --base-700: var(--color-zinc-700); 322 + --base-800: var(--color-zinc-800); 323 + --base-900: var(--color-zinc-900); 324 + --base-950: var(--color-zinc-950); 325 + } 326 + 327 + .neutral { 328 + --base-50: var(--color-neutral-50); 329 + --base-100: var(--color-neutral-100); 330 + --base-200: var(--color-neutral-200); 331 + --base-300: var(--color-neutral-300); 332 + --base-400: var(--color-neutral-400); 333 + --base-500: var(--color-neutral-500); 334 + --base-600: var(--color-neutral-600); 335 + --base-700: var(--color-neutral-700); 336 + --base-800: var(--color-neutral-800); 337 + --base-900: var(--color-neutral-900); 338 + --base-950: var(--color-neutral-950); 339 + } 340 + 341 + .stone { 342 + --base-50: var(--color-stone-50); 343 + --base-100: var(--color-stone-100); 344 + --base-200: var(--color-stone-200); 345 + --base-300: var(--color-stone-300); 346 + --base-400: var(--color-stone-400); 347 + --base-500: var(--color-stone-500); 348 + --base-600: var(--color-stone-600); 349 + --base-700: var(--color-stone-700); 350 + --base-800: var(--color-stone-800); 351 + --base-900: var(--color-stone-900); 352 + --base-950: var(--color-stone-950); 353 + } 354 + 355 + .slate { 356 + --base-50: var(--color-slate-50); 357 + --base-100: var(--color-slate-100); 358 + --base-200: var(--color-slate-200); 359 + --base-300: var(--color-slate-300); 360 + --base-400: var(--color-slate-400); 361 + --base-500: var(--color-slate-500); 362 + --base-600: var(--color-slate-600); 363 + --base-700: var(--color-slate-700); 364 + --base-800: var(--color-slate-800); 365 + --base-900: var(--color-slate-900); 366 + --base-950: var(--color-slate-950); 367 + } 368 + }
+32 -1
src/app.html
··· 1 1 <!doctype html> 2 - <html lang="en" class="dark"> 2 + <html lang="en"> 3 3 <head> 4 4 <meta charset="utf-8" /> 5 5 <link rel="icon" href="%sveltekit.assets%/favicon.png" /> 6 6 <meta name="viewport" content="width=device-width, initial-scale=1" /> 7 7 %sveltekit.head% 8 + 9 + 10 + <script> 11 + let dark = localStorage.getItem('darkMode'); 12 + if (dark) { 13 + dark = JSON.parse(dark); 14 + } else { 15 + dark = window.matchMedia('(prefers-color-scheme: dark)').matches; 16 + } 17 + 18 + var root = document.getElementsByTagName('html')[0]; 19 + if (dark) { 20 + root.classList.add('dark'); 21 + } else { 22 + root.classList.remove('dark'); 23 + } 24 + 25 + // get accent color from local storage 26 + let accentColor = localStorage.getItem('accentColor'); 27 + if (accentColor) { 28 + accentColor = JSON.parse(accentColor); 29 + root.classList.add(accentColor); 30 + } 31 + 32 + // get base color from local storage 33 + let baseColor = localStorage.getItem('baseColor'); 34 + if (baseColor) { 35 + baseColor = JSON.parse(baseColor); 36 + root.classList.add(baseColor); 37 + } 38 + </script> 8 39 </head> 9 40 <body data-sveltekit-preload-data="hover" class="bg-base-50 dark:bg-base-950"> 10 41 <div style="display: contents">%sveltekit.body%</div>
+73 -1
src/lib/Links.svelte
··· 1 1 <script lang="ts"> 2 + import { toast, Subheading, Input, Button } from '@fuxui/base'; 3 + import { editingState } from '../routes/state.svelte'; 4 + 2 5 let { links } = $props(); 6 + 7 + let link = $state(''); 8 + 9 + let loading = $state(false); 10 + 11 + async function addLink(event: Event) { 12 + event.preventDefault(); 13 + if (loading) return; 14 + loading = true; 15 + 16 + // check if link is already in the list 17 + if (editingState.links.some((l) => l.url === link)) { 18 + toast.error('Link already exists'); 19 + loading = false; 20 + return; 21 + } 22 + 23 + const response = await fetch(`/api/get-og?url=${link}`); 24 + const og = await response.json(); 25 + 26 + if (!og.success) { 27 + toast.error('Invalid link'); 28 + loading = false; 29 + return; 30 + } 31 + 32 + editingState.links.push({ 33 + url: link, 34 + title: og.ogTitle, 35 + description: og.ogDescription, 36 + image: og.ogImage?.[0]?.url 37 + }); 38 + 39 + // save links to local storage 40 + localStorage.setItem('links', JSON.stringify(editingState.links)); 41 + 42 + link = ''; 43 + loading = false; 44 + 45 + toast('Link added'); 46 + 47 + console.log(editingState.links); 48 + } 49 + 50 + function removeLink(url: string) { 51 + editingState.links = editingState.links.filter((l) => l.url !== url); 52 + localStorage.setItem('links', JSON.stringify(editingState.links)); 53 + } 3 54 </script> 4 55 56 + <div class="px-2"> 57 + <Subheading class="mt-4 mb-4">Add a link:</Subheading> 58 + <form onsubmit={addLink} class="flex gap-2"> 59 + <Input bind:value={link} name="link" class="grow" /> 60 + <Button type="submit" disabled={loading}>{loading ? 'Adding...' : 'Add'}</Button> 61 + </form> 62 + </div> 5 63 <div class="mt-6 flex flex-col gap-6 px-4 text-white"> 6 64 {#each links as link} 7 - <div> 65 + <div class="relative"> 8 66 <a href={link.url} target="_blank" class="text-accent-600 dark:text-accent-400 font-medium"> 9 67 {link.title} 10 68 </a> ··· 12 70 <div class="text-base-600 dark:text-base-400 mt-1 text-sm"> 13 71 {link.description} 14 72 </div> 73 + 74 + <Button 75 + onclick={() => removeLink(link.url)} 76 + variant="secondary" 77 + size="iconSm" 78 + class="absolute top-0 right-0" 79 + > 80 + <svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor"> 81 + <path stroke-linecap="round" stroke-linejoin="round" d="m14.74 9-.346 9m-4.788 0L9.26 9m9.968-3.21c.342.052.682.107 1.022.166m-1.022-.165L18.16 19.673a2.25 2.25 0 0 1-2.244 2.077H8.084a2.25 2.25 0 0 1-2.244-2.077L4.772 5.79m14.456 0a48.108 48.108 0 0 0-3.478-.397m-12 .562c.34-.059.68-.114 1.022-.165m0 0a48.11 48.11 0 0 1 3.478-.397m7.5 0v-.916c0-1.18-.91-2.164-2.09-2.201a51.964 51.964 0 0 0-3.32 0c-1.18.037-2.09 1.022-2.09 2.201v.916m7.5 0a48.667 48.667 0 0 0-7.5 0" /> 82 + </svg> 83 + 84 + 85 + <span class="sr-only">remove link</span> 86 + </Button> 15 87 </div> 16 88 {/each} 17 89 </div>
+33
src/lib/Navbar.svelte
··· 1 + <script lang="ts"> 2 + import { ThemeToggle, Navbar, Button } from '@fuxui/base'; 3 + 4 + import ThemeSelectDropdown from './ThemeSelectDropdown.svelte'; 5 + </script> 6 + 7 + <Navbar> 8 + <div class="flex items-center gap-2"> 9 + <!-- <Button variant="link" class="block font-normal lg:hidden backdrop-blur-none" popovertarget="mobile-menu"> 10 + <svg 11 + xmlns="http://www.w3.org/2000/svg" 12 + viewBox="0 0 24 24" 13 + fill="currentColor" 14 + class="size-6!" 15 + > 16 + <path 17 + fill-rule="evenodd" 18 + d="M3 9a.75.75 0 0 1 .75-.75h16.5a.75.75 0 0 1 0 1.5H3.75A.75.75 0 0 1 3 9Zm0 6.75a.75.75 0 0 1 .75-.75h16.5a.75.75 0 0 1 0 1.5H3.75a.75.75 0 0 1-.75-.75Z" 19 + clip-rule="evenodd" 20 + ></path> 21 + </svg> 22 + </Button> 23 + 24 + <Logo class={showLogo ? 'lg:ml-5' : 'lg:hidden'} /> --> 25 + </div> 26 + <div class="flex items-center gap-3"> 27 + <ThemeSelectDropdown /> 28 + <ThemeToggle class="backdrop-blur-none" /> 29 + <Button size="lg">Publish</Button> 30 + 31 + <!-- <Github href="https://github.com/flo-bit/ui-kit" class="mr-2 p-2" /> --> 32 + </div> 33 + </Navbar>
+7 -47
src/lib/Posts.svelte
··· 1 1 <script lang="ts"> 2 - import { AtpBaseClient, type Agent } from '@atproto/api'; 3 - import type { FeedViewPost } from '@atproto/api/dist/client/types/app/bsky/feed/defs'; 4 - import { blueskyPostToPostData, Post, type PostData } from '@fuxui/social'; 5 - import { onMount } from 'svelte'; 6 - 7 - export async function getPostsOfUser({ 8 - actor, 9 - cursor, 10 - limit = 20 11 - }: { 12 - actor: string; 13 - cursor?: string; 14 - limit?: number; 15 - }) { 16 - let agent = new AtpBaseClient({ service: 'https://api.bsky.app' }); 17 - const perLimit = limit > 100 ? 100 : limit; 18 - const posts: FeedViewPost[] = []; 19 - do { 20 - const { data } = await agent.app.bsky.feed.getAuthorFeed({ 21 - actor, 22 - cursor, 23 - limit: perLimit, 24 - filter: 'posts_no_replies' 25 - }); 26 - posts.push(...data.feed); 27 - cursor = data.cursor; 28 - } while (cursor && posts.length < limit); 29 - return { feed: posts, cursor }; 30 - } 31 - 32 - let { did } = $props(); 33 - 34 - let blueskyPosts: PostData[] = $state([]); 35 - 36 - onMount(async () => { 37 - const posts = await getPostsOfUser({ actor: did }); 38 - console.log(posts); 2 + import { Post, type PostData } from '@fuxui/social'; 39 3 40 - blueskyPosts = posts.feed.map((post) => { 41 - console.log(post); 42 - return blueskyPostToPostData(post.post); 43 - }); 44 - }); 4 + let { posts }: { posts: PostData[] } = $props(); 45 5 </script> 46 6 47 7 <div class="mt-4"> 48 - {#if blueskyPosts} 49 - {#each blueskyPosts as post} 50 - <Post data={post} class="py-4">hello</Post> 51 - {/each} 52 - {/if} 8 + {#if posts} 9 + {#each posts as post} 10 + <Post data={post} class="py-4">hello</Post> 11 + {/each} 12 + {/if} 53 13 </div>
+106
src/lib/SelectTheme.svelte
··· 1 + <script lang="ts" module> 2 + let accentColor = $state({ 3 + class: '', 4 + label: '' 5 + }); 6 + 7 + let baseColor = $state({ 8 + class: '', 9 + label: '' 10 + }); 11 + </script> 12 + 13 + <script lang="ts"> 14 + import { Subheading, Paragraph } from '@fuxui/base'; 15 + import { ColorSelect } from '@fuxui/colors'; 16 + import { onMount } from 'svelte'; 17 + 18 + let accentColors = [ 19 + { class: 'text-red-500', label: 'red' }, 20 + { class: 'text-orange-500', label: 'orange' }, 21 + { class: 'text-amber-500', label: 'amber' }, 22 + { class: 'text-yellow-500', label: 'yellow' }, 23 + { class: 'text-lime-500', label: 'lime' }, 24 + { class: 'text-green-500', label: 'green' }, 25 + { class: 'text-emerald-500', label: 'emerald' }, 26 + { class: 'text-teal-500', label: 'teal' }, 27 + { class: 'text-cyan-500', label: 'cyan' }, 28 + { class: 'text-sky-500', label: 'sky' }, 29 + { class: 'text-blue-500', label: 'blue' }, 30 + { class: 'text-indigo-500', label: 'indigo' }, 31 + { class: 'text-violet-500', label: 'violet' }, 32 + { class: 'text-purple-500', label: 'purple' }, 33 + { class: 'text-fuchsia-500', label: 'fuchsia' }, 34 + { class: 'text-pink-500', label: 'pink' }, 35 + { class: 'text-rose-500', label: 'rose' } 36 + ]; 37 + 38 + let baseColors = [ 39 + { class: 'text-gray-500', label: 'gray' }, 40 + { class: 'text-stone-500', label: 'stone' }, 41 + { class: 'text-zinc-500', label: 'zinc' }, 42 + { class: 'text-neutral-500', label: 'neutral' }, 43 + { class: 'text-slate-500', label: 'slate' } 44 + ]; 45 + 46 + onMount(() => { 47 + let savedAccentColor = localStorage.getItem('accentColor'); 48 + if (savedAccentColor) { 49 + accentColor.label = JSON.parse(savedAccentColor); 50 + } else { 51 + accentColor.label = 'pink'; 52 + } 53 + 54 + let savedBaseColor = localStorage.getItem('baseColor'); 55 + if (savedBaseColor) { 56 + baseColor.label = JSON.parse(savedBaseColor); 57 + } else { 58 + baseColor.label = 'stone'; 59 + } 60 + }); 61 + 62 + let { showTitle = true } = $props(); 63 + </script> 64 + 65 + {#if showTitle} 66 + <Subheading class="mb-4">Select Theme</Subheading> 67 + {/if} 68 + 69 + <Paragraph class="mb-2">Accent Color</Paragraph> 70 + <ColorSelect 71 + bind:selected={accentColor} 72 + colors={accentColors} 73 + onselected={(color, previous) => { 74 + if (typeof previous === 'string' || typeof color === 'string') { 75 + return; 76 + } 77 + 78 + document.documentElement.classList.remove(previous.label.toLowerCase()); 79 + document.documentElement.classList.add(color.label.toLowerCase()); 80 + 81 + localStorage.setItem('accentColor', JSON.stringify(color.label)); 82 + 83 + window.dispatchEvent( 84 + new CustomEvent('theme-changed', { detail: { accentColor: color.label } }) 85 + ); 86 + }} 87 + class="w-64" 88 + /> 89 + 90 + <Paragraph class="mt-4 mb-2">Base Color</Paragraph> 91 + <ColorSelect 92 + bind:selected={baseColor} 93 + colors={baseColors} 94 + onselected={(color, previous) => { 95 + if (typeof previous === 'string' || typeof color === 'string') { 96 + return; 97 + } 98 + 99 + document.documentElement.classList.remove(previous.label.toLowerCase()); 100 + document.documentElement.classList.add(color.label.toLowerCase()); 101 + 102 + localStorage.setItem('baseColor', JSON.stringify(color.label)); 103 + 104 + window.dispatchEvent(new CustomEvent('theme-changed', { detail: { baseColor: color.label } })); 105 + }} 106 + />
+25
src/lib/ThemeSelectDropdown.svelte
··· 1 + <script> 2 + import { buttonVariants, Popover, cn } from '@fuxui/base'; 3 + import SelectTheme from './SelectTheme.svelte'; 4 + </script> 5 + 6 + <Popover> 7 + {#snippet child({ props })} 8 + <button {...props} 9 + class={cn( 10 + buttonVariants({ variant: 'link', size: 'default' }), 11 + 'flex cursor-pointer items-center gap-0 -space-x-2 backdrop-blur-none' 12 + )} 13 + > 14 + <div 15 + class=" from-accent-500 to-accent-600 border-accent-700 dark:border-accent-400 z-10 size-6 rounded-full border bg-gradient-to-b" 16 + ></div> 17 + 18 + <div 19 + class=" from-base-500 to-base-600 border-base-700 dark:border-base-400 size-6 rounded-full border bg-gradient-to-b" 20 + ></div> 21 + </button> 22 + {/snippet} 23 + <!-- add class w-72? --> 24 + <SelectTheme showTitle={false} /> 25 + </Popover>
+50 -37
src/routes/+page.svelte
··· 1 1 <script lang="ts"> 2 - import { Badge, Button, Heading, Input, Subheading, Tabs } from '@fuxui/base'; 3 - import { BlueskyLogin, UserProfile } from '@fuxui/social'; 2 + import { Badge, Button, Input, Subheading, Tabs, ThemeToggle, toast, Toaster } from '@fuxui/base'; 3 + import { BlueskyLogin, blueskyPostToPostData, UserProfile, type PostData } from '@fuxui/social'; 4 4 import { editingState } from './state.svelte.js'; 5 5 import Editor from '$lib/Editor.svelte'; 6 6 import Posts from '$lib/Posts.svelte'; 7 7 import Links from '$lib/Links.svelte'; 8 + import { onMount } from 'svelte'; 9 + import { AtpBaseClient } from '@atproto/api'; 10 + import type { FeedViewPost } from '@atproto/api/dist/client/types/app/bsky/feed/defs.js'; 11 + import Navbar from '$lib/Navbar.svelte'; 8 12 9 13 let { data } = $props(); 10 14 11 15 let form = $state<HTMLFormElement | null>(null); 12 16 13 - let link = $state(''); 14 - 15 - async function addLink(event: Event) { 16 - event.preventDefault(); 17 + onMount(async () => { 18 + editingState.links = JSON.parse(localStorage.getItem('links') ?? '[]'); 17 19 18 - // check if link is already in the list 19 - if (editingState.links.some((l) => l.url === link)) { 20 - return; 21 - } 22 - 23 - const response = await fetch(`/api/get-og?url=${link}`); 24 - const og = await response.json(); 20 + if (!data.user) return; 25 21 26 - if(!og.success) return; 22 + const posts = await getPostsOfUser({ actor: data.user.did }); 23 + console.log(posts); 27 24 28 - editingState.links.push({ 29 - url: link, 30 - title: og.ogTitle, 31 - description: og.ogDescription, 32 - image: og.ogImage?.[0]?.url 25 + blueskyPosts = posts.feed.map((post) => { 26 + console.log(post); 27 + return blueskyPostToPostData(post.post); 33 28 }); 29 + }); 34 30 35 - // save links to local storage 36 - localStorage.setItem('links', JSON.stringify(editingState.links)); 37 - 38 - link = ''; 39 - 40 - console.log(editingState.links); 31 + export async function getPostsOfUser({ 32 + actor, 33 + cursor, 34 + limit = 20 35 + }: { 36 + actor: string; 37 + cursor?: string; 38 + limit?: number; 39 + }) { 40 + let agent = new AtpBaseClient({ service: 'https://api.bsky.app' }); 41 + const perLimit = limit > 100 ? 100 : limit; 42 + const posts: FeedViewPost[] = []; 43 + do { 44 + const { data } = await agent.app.bsky.feed.getAuthorFeed({ 45 + actor, 46 + cursor, 47 + limit: perLimit, 48 + filter: 'posts_no_replies' 49 + }); 50 + posts.push(...data.feed); 51 + cursor = data.cursor; 52 + } while (cursor && posts.length < limit); 53 + return { feed: posts, cursor }; 41 54 } 55 + 56 + let blueskyPosts: PostData[] = $state([]); 42 57 </script> 43 58 44 - <div class="mx-auto max-w-2xl"> 59 + <div class="mx-auto max-w-2xl py-16"> 45 60 {#if !data.user} 46 61 <BlueskyLogin 47 62 login={async (handle) => { ··· 68 83 <input type="hidden" name="handle" value="handle" /> 69 84 </form> 70 85 {:else} 71 - <Button size="lg" class="fixed top-2 right-2">Publish</Button> 72 - <UserProfile class="" profile={{ ...data.user, description: '' }} /> 86 + <UserProfile class="" profile={{ ...data.user, description: undefined }} /> 73 87 88 + <div class="-mt-8"> 74 89 <Tabs 75 90 active={editingState.active} 76 91 items={[ ··· 92 107 <div class="px-4"> 93 108 {#if editingState.active === 'about'} 94 109 <div 95 - class="focus-within:outline-accent-400 hover:bg-base-900/20 group focus-within:bg-accent-500/5 focus-within:hover:bg-accent-500/5 relative mt-4 rounded-2xl px-2 py-0.5 focus-within:outline" 110 + class="focus-within:outline-accent-400 hover:not-focus-within:bg-base-200/40 dark:hover:not-focus-within:bg-base-900/20 group focus-within:bg-accent-500/5 relative mt-4 rounded-2xl px-2 py-0.5 focus-within:outline" 96 111 > 97 112 <Editor /> 98 113 <Badge ··· 104 119 </div> 105 120 {:else if editingState.active === 'links'} 106 121 <div> 107 - <div class="px-2"> 108 - <Subheading class="mb-4 mt-4">Add a link:</Subheading> 109 - <form onsubmit={addLink} class="flex gap-2"> 110 - <Input bind:value={link} name="link" class="grow" /> 111 - <Button type="submit">Add</Button> 112 - </form> 113 - </div> 114 122 <Links links={editingState.links} /> 115 123 </div> 116 124 {:else if editingState.active === 'feed'} 117 - <Posts did={data.user.did} /> 125 + <Posts posts={blueskyPosts} /> 118 126 {/if} 119 127 </div> 120 128 121 129 <form method="POST" action="/?/logout" class="fixed right-2 bottom-2"> 122 130 <Button type="submit">Logout</Button> 123 131 </form> 132 + </div> 124 133 {/if} 125 134 </div> 135 + 136 + <Toaster /> 137 + 138 + <Navbar />