From aaabe1ede49184092bc70341eb1acff2c5e1c842 Mon Sep 17 00:00:00 2001 From: Niko Date: Mon, 19 May 2025 16:09:42 +0200 Subject: [PATCH 1/7] Add CodeHart component. --- astro.config.mjs | 3 + package.json | 2 + pnpm-lock.yaml | 165 ++++++++++++++++++++ src/components/island/CodeHeart.svelte | 148 ++++++++++++++++++ src/components/schedule/session.astro | 58 ++++--- src/components/sessions/list-sessions.astro | 9 ++ src/pages/[...slug].astro | 1 + src/pages/session/[slug].astro | 22 ++- src/pages/speaker/[slug].astro | 8 + src/utils/storage.js | 116 ++++++++++++++ svelte.config.js | 5 + 11 files changed, 508 insertions(+), 29 deletions(-) create mode 100644 src/components/island/CodeHeart.svelte create mode 100644 src/utils/storage.js create mode 100644 svelte.config.js diff --git a/astro.config.mjs b/astro.config.mjs index 7841b0367..d2602dd81 100644 --- a/astro.config.mjs +++ b/astro.config.mjs @@ -14,6 +14,8 @@ import { execSync } from "node:child_process"; import compress from "astro-compress"; +import svelte from "@astrojs/svelte"; + let gitVersion = String(process.env.GIT_VERSION ?? "").slice(0, 7); if (!gitVersion) { @@ -89,6 +91,7 @@ export default defineConfig({ metaTags(), pagefind(), deleteUnusedImages(), + svelte(), compress(), ], output: "static", diff --git a/package.json b/package.json index 24b24792a..440321a54 100644 --- a/package.json +++ b/package.json @@ -15,6 +15,7 @@ "@astrojs/check": "^0.9.4", "@astrojs/mdx": "^4.2.6", "@astrojs/sitemap": "^3.3.1", + "@astrojs/svelte": "^7.0.13", "@astrojs/tailwind": "^5.1.5", "@fontsource-variable/inter": "^5.2.5", "@fortawesome/fontawesome-free": "^6.7.2", @@ -36,6 +37,7 @@ "rehype-slug": "^6.0.0", "remark-toc": "^9.0.0", "sharp": "^0.34.1", + "svelte": "^5.30.2", "tailwindcss": "^3.4.17", "typescript": "^5.8.3" }, diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 9bc549d0f..1e786bfa5 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -20,6 +20,9 @@ importers: '@astrojs/sitemap': specifier: ^3.3.1 version: 3.3.1 + '@astrojs/svelte': + specifier: ^7.0.13 + version: 7.0.13(@types/node@22.13.14)(astro@5.7.10(@types/node@22.13.14)(jiti@2.4.2)(lightningcss@1.29.3)(rollup@4.40.1)(terser@5.39.0)(tsx@4.19.4)(typescript@5.8.3)(yaml@2.7.1))(jiti@2.4.2)(lightningcss@1.29.3)(svelte@5.30.2)(terser@5.39.0)(tsx@4.19.4)(typescript@5.8.3)(yaml@2.7.1) '@astrojs/tailwind': specifier: ^5.1.5 version: 5.1.5(astro@5.7.10(@types/node@22.13.14)(jiti@2.4.2)(lightningcss@1.29.3)(rollup@4.40.1)(terser@5.39.0)(tsx@4.19.4)(typescript@5.8.3)(yaml@2.7.1))(tailwindcss@3.4.17) @@ -83,6 +86,9 @@ importers: sharp: specifier: ^0.34.1 version: 0.34.1 + svelte: + specifier: ^5.30.2 + version: 5.30.2 tailwindcss: specifier: ^3.4.17 version: 3.4.17 @@ -112,6 +118,10 @@ packages: resolution: {integrity: sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw==} engines: {node: '>=10'} + '@ampproject/remapping@2.3.0': + resolution: {integrity: sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==} + engines: {node: '>=6.0.0'} + '@astro-community/astro-embed-youtube@0.5.6': resolution: {integrity: sha512-/mRfCl/eTBUz0kmjD1psOy0qoDDBorVp0QumUacjFcIkBullYtbeFQ2ZGZ+3N/tA6cR/OIyzr2QA4dQXlY6USg==} peerDependencies: @@ -157,6 +167,14 @@ packages: '@astrojs/sitemap@3.3.1': resolution: {integrity: sha512-GRnDUCTviBSNfXJ0Jmur+1/C+z3g36jy79VyYggfe1uNyEYSTcmAfTTCmbytrRvJRNyJJnSfB/77Gnm9PiXRRg==} + '@astrojs/svelte@7.0.13': + resolution: {integrity: sha512-wAWZu3Y/shIa83qVWyhr34vqwfl0GOXLsTYp081mMMOzuiyDAqmusFILua2zpbhqsQhQlhH/BYbGLEIQ/zQpRA==} + engines: {node: ^18.17.1 || ^20.3.0 || >=22.0.0} + peerDependencies: + astro: ^5.0.0 + svelte: ^5.1.16 + typescript: ^5.3.3 + '@astrojs/tailwind@5.1.5': resolution: {integrity: sha512-1diguZEau7FZ9vIjzE4BwavGdhD3+JkdS8zmibl1ene+EHgIU5hI0NMgRYG3yea+Niaf7cyMwjeWeLvzq/maxg==} peerDependencies: @@ -810,6 +828,26 @@ packages: '@shikijs/vscode-textmate@10.0.2': resolution: {integrity: sha512-83yeghZ2xxin3Nj8z1NMd/NCuca+gsYXswywDy5bHvwlWL8tpTQmzGeUuHd9FC3E/SBEMvzJRwWEOz5gGes9Qg==} + '@sveltejs/acorn-typescript@1.0.5': + resolution: {integrity: sha512-IwQk4yfwLdibDlrXVE04jTZYlLnwsTT2PIOQQGNLWfjavGifnk1JD1LcZjZaBTRcxZu2FfPfNLOE04DSu9lqtQ==} + peerDependencies: + acorn: ^8.9.0 + + '@sveltejs/vite-plugin-svelte-inspector@4.0.1': + resolution: {integrity: sha512-J/Nmb2Q2y7mck2hyCX4ckVHcR5tu2J+MtBEQqpDrrgELZ2uvraQcK/ioCV61AqkdXFgriksOKIceDcQmqnGhVw==} + engines: {node: ^18.0.0 || ^20.0.0 || >=22} + peerDependencies: + '@sveltejs/vite-plugin-svelte': ^5.0.0 + svelte: ^5.0.0 + vite: ^6.0.0 + + '@sveltejs/vite-plugin-svelte@5.0.3': + resolution: {integrity: sha512-MCFS6CrQDu1yGwspm4qtli0e63vaPCehf6V7pIMP15AsWgMKrqDGCPFF/0kn4SP0ii4aySu4Pa62+fIRGFMjgw==} + engines: {node: ^18.0.0 || ^20.0.0 || >=22} + peerDependencies: + svelte: ^5.0.0 + vite: ^6.0.0 + '@swc/helpers@0.5.17': resolution: {integrity: sha512-5IKx/Y13RsYd+sauPb2x+U/xZikHjolzfuDgTAl/Tdf3Q8rslRvC19NKDLgAJQ6wsqADk10ntlv08nPFw/gO/A==} @@ -1288,10 +1326,17 @@ packages: decode-named-character-reference@1.1.0: resolution: {integrity: sha512-Wy+JTSbFThEOXQIR2L6mxJvEs+veIzpmqD7ynWxMXGpnk3smkHQOp6forLdHsKpAMW9iJpaBBIxz285t1n1C3w==} + dedent-js@1.0.1: + resolution: {integrity: sha512-OUepMozQULMLUmhxS95Vudo0jb0UchLimi3+pQ2plj61Fcy8axbP9hbiD4Sz6DPqn6XG3kfmziVfQ1rSys5AJQ==} + deepmerge-ts@7.1.5: resolution: {integrity: sha512-HOJkrhaYsweh+W+e74Yn7YStZOilkoPb6fycpwNLKzSPtruFs48nYis0zy5yJz1+ktUhHxoRDJ27RQAWLIJVJw==} engines: {node: '>=16.0.0'} + deepmerge@4.3.1: + resolution: {integrity: sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==} + engines: {node: '>=0.10.0'} + defu@6.1.4: resolution: {integrity: sha512-mEQCMmwJu317oSz8CwdIOdwf3xMif1ttiM8LTufzc3g6kR+9Pe236twL8j3IYT1F7GfRgGcW6MWxzZjLIkuHIg==} @@ -1419,11 +1464,17 @@ packages: engines: {node: '>=6.0'} hasBin: true + esm-env@1.2.2: + resolution: {integrity: sha512-Epxrv+Nr/CaL4ZcFGPJIYLWFom+YeV1DqMLHJoEd9SYRxNbaFruBwfEX/kkHUJf55j2+TUbmDcmuilbP1TmXHA==} + esprima@4.0.1: resolution: {integrity: sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==} engines: {node: '>=4'} hasBin: true + esrap@1.4.6: + resolution: {integrity: sha512-F/D2mADJ9SHY3IwksD4DAXjTt7qt7GWUf3/8RhCNWmC/67tyb55dpimHmy7EplakFaflV0R/PC+fdSPqrRHAQw==} + estraverse@5.3.0: resolution: {integrity: sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==} engines: {node: '>=4.0'} @@ -1698,6 +1749,9 @@ packages: resolution: {integrity: sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg==} engines: {node: '>=12'} + is-reference@3.0.3: + resolution: {integrity: sha512-ixkJoqQvAP88E6wLydLGGqCJsrFUnqoH6HnaczB8XmDH1oaWU+xxdptvikTgaEhtZ53Ky6YXiBuUI2WXLMCwjw==} + is-wsl@3.1.0: resolution: {integrity: sha512-UcVfVfaK4Sc4m7X3dUSoHoozQGBEFeDC+zVo06t98xe8CzHSZZBekNXH+tu0NalHolcJ/QAGqS46Hef7QXBIMw==} engines: {node: '>=16'} @@ -1820,6 +1874,9 @@ packages: lite-youtube-embed@0.3.3: resolution: {integrity: sha512-gFfVVnj6NRjxVfJKo3qoLtpi0v5mn3AcR4eKD45wrxQuxzveFJUb+7Cr6uV6n+DjO8X3p0UzPPquhGt0H/y+NA==} + locate-character@3.0.0: + resolution: {integrity: sha512-SW13ws7BjaeJ6p7Q6CO2nchbYEc3X3J6WrmTTDto7yMPqVSZTUyY5Tjbid+Ab8gLnATtygYtiDIJGQRRn2ZOiA==} + lodash.castarray@4.4.0: resolution: {integrity: sha512-aVx8ztPv7/2ULbArGJ2Y42bG1mEQ5mGjpdvrbJcJFU3TbYybe+QlLS4pst9zV52ymy2in1KpFPiZnAOATxD4+Q==} @@ -2596,6 +2653,16 @@ packages: resolution: {integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==} engines: {node: '>= 0.4'} + svelte2tsx@0.7.39: + resolution: {integrity: sha512-NX8a7eSqF1hr6WKArvXr7TV7DeE+y0kDFD7L5JP7TWqlwFidzGKaG415p992MHREiiEWOv2xIWXJ+mlONofs0A==} + peerDependencies: + svelte: ^3.55 || ^4.0.0-next.0 || ^4.0 || ^5.0.0-next.0 + typescript: ^4.9.4 || ^5.0.0 + + svelte@5.30.2: + resolution: {integrity: sha512-zfGFEwwPeILToOxOqQyFq/vc8euXrX2XyoffkBNgn/k8D1nxbLt5+mNaqQBmZF/vVhBGmkY6VmNK18p9Gf0auQ==} + engines: {node: '>=18'} + svgo@3.3.2: resolution: {integrity: sha512-OoohrmuUlBs8B8o6MB2Aevn+pRIH9zDALSR+6hhqVfa6fRwG/Qw9VUMSMW9VNg2CFc/MTIfabtdOVl9ODIJjpw==} engines: {node: '>=14.0.0'} @@ -3070,6 +3137,9 @@ packages: resolution: {integrity: sha512-GQHQqAopRhwU8Kt1DDM8NjibDXHC8eoh1erhGAJPEyveY9qqVeXvVikNKrDz69sHowPMorbPUrH/mx8c50eiBQ==} engines: {node: '>=18'} + zimmerframe@1.1.2: + resolution: {integrity: sha512-rAbqEGa8ovJy4pyBxZM70hg4pE6gDgaQ0Sl9M3enG3I0d6H4XSAM3GeNGLKnsBpuijUow064sf7ww1nutC5/3w==} + zod-to-json-schema@3.24.5: resolution: {integrity: sha512-/AuWwMP+YqiPbsJx5D6TfgRTc4kTLjsh5SOcd4bLsfUg2RcEXrFMJl1DGgdHy2aCfsIA/cr/1JM0xcB2GZji8g==} peerDependencies: @@ -3091,6 +3161,11 @@ snapshots: '@alloc/quick-lru@5.2.0': {} + '@ampproject/remapping@2.3.0': + dependencies: + '@jridgewell/gen-mapping': 0.3.8 + '@jridgewell/trace-mapping': 0.3.25 + '@astro-community/astro-embed-youtube@0.5.6(astro@5.7.10(@types/node@22.13.14)(jiti@2.4.2)(lightningcss@1.29.3)(rollup@4.40.1)(terser@5.39.0)(tsx@4.19.4)(typescript@5.8.3)(yaml@2.7.1))': dependencies: astro: 5.7.10(@types/node@22.13.14)(jiti@2.4.2)(lightningcss@1.29.3)(rollup@4.40.1)(terser@5.39.0)(tsx@4.19.4)(typescript@5.8.3)(yaml@2.7.1) @@ -3192,6 +3267,28 @@ snapshots: stream-replace-string: 2.0.0 zod: 3.24.3 + '@astrojs/svelte@7.0.13(@types/node@22.13.14)(astro@5.7.10(@types/node@22.13.14)(jiti@2.4.2)(lightningcss@1.29.3)(rollup@4.40.1)(terser@5.39.0)(tsx@4.19.4)(typescript@5.8.3)(yaml@2.7.1))(jiti@2.4.2)(lightningcss@1.29.3)(svelte@5.30.2)(terser@5.39.0)(tsx@4.19.4)(typescript@5.8.3)(yaml@2.7.1)': + dependencies: + '@sveltejs/vite-plugin-svelte': 5.0.3(svelte@5.30.2)(vite@6.3.4(@types/node@22.13.14)(jiti@2.4.2)(lightningcss@1.29.3)(terser@5.39.0)(tsx@4.19.4)(yaml@2.7.1)) + astro: 5.7.10(@types/node@22.13.14)(jiti@2.4.2)(lightningcss@1.29.3)(rollup@4.40.1)(terser@5.39.0)(tsx@4.19.4)(typescript@5.8.3)(yaml@2.7.1) + svelte: 5.30.2 + svelte2tsx: 0.7.39(svelte@5.30.2)(typescript@5.8.3) + typescript: 5.8.3 + vite: 6.3.4(@types/node@22.13.14)(jiti@2.4.2)(lightningcss@1.29.3)(terser@5.39.0)(tsx@4.19.4)(yaml@2.7.1) + transitivePeerDependencies: + - '@types/node' + - jiti + - less + - lightningcss + - sass + - sass-embedded + - stylus + - sugarss + - supports-color + - terser + - tsx + - yaml + '@astrojs/tailwind@5.1.5(astro@5.7.10(@types/node@22.13.14)(jiti@2.4.2)(lightningcss@1.29.3)(rollup@4.40.1)(terser@5.39.0)(tsx@4.19.4)(typescript@5.8.3)(yaml@2.7.1))(tailwindcss@3.4.17)': dependencies: astro: 5.7.10(@types/node@22.13.14)(jiti@2.4.2)(lightningcss@1.29.3)(rollup@4.40.1)(terser@5.39.0)(tsx@4.19.4)(typescript@5.8.3)(yaml@2.7.1) @@ -3724,6 +3821,32 @@ snapshots: '@shikijs/vscode-textmate@10.0.2': {} + '@sveltejs/acorn-typescript@1.0.5(acorn@8.14.1)': + dependencies: + acorn: 8.14.1 + + '@sveltejs/vite-plugin-svelte-inspector@4.0.1(@sveltejs/vite-plugin-svelte@5.0.3(svelte@5.30.2)(vite@6.3.4(@types/node@22.13.14)(jiti@2.4.2)(lightningcss@1.29.3)(terser@5.39.0)(tsx@4.19.4)(yaml@2.7.1)))(svelte@5.30.2)(vite@6.3.4(@types/node@22.13.14)(jiti@2.4.2)(lightningcss@1.29.3)(terser@5.39.0)(tsx@4.19.4)(yaml@2.7.1))': + dependencies: + '@sveltejs/vite-plugin-svelte': 5.0.3(svelte@5.30.2)(vite@6.3.4(@types/node@22.13.14)(jiti@2.4.2)(lightningcss@1.29.3)(terser@5.39.0)(tsx@4.19.4)(yaml@2.7.1)) + debug: 4.4.0 + svelte: 5.30.2 + vite: 6.3.4(@types/node@22.13.14)(jiti@2.4.2)(lightningcss@1.29.3)(terser@5.39.0)(tsx@4.19.4)(yaml@2.7.1) + transitivePeerDependencies: + - supports-color + + '@sveltejs/vite-plugin-svelte@5.0.3(svelte@5.30.2)(vite@6.3.4(@types/node@22.13.14)(jiti@2.4.2)(lightningcss@1.29.3)(terser@5.39.0)(tsx@4.19.4)(yaml@2.7.1))': + dependencies: + '@sveltejs/vite-plugin-svelte-inspector': 4.0.1(@sveltejs/vite-plugin-svelte@5.0.3(svelte@5.30.2)(vite@6.3.4(@types/node@22.13.14)(jiti@2.4.2)(lightningcss@1.29.3)(terser@5.39.0)(tsx@4.19.4)(yaml@2.7.1)))(svelte@5.30.2)(vite@6.3.4(@types/node@22.13.14)(jiti@2.4.2)(lightningcss@1.29.3)(terser@5.39.0)(tsx@4.19.4)(yaml@2.7.1)) + debug: 4.4.0 + deepmerge: 4.3.1 + kleur: 4.1.5 + magic-string: 0.30.17 + svelte: 5.30.2 + vite: 6.3.4(@types/node@22.13.14)(jiti@2.4.2)(lightningcss@1.29.3)(terser@5.39.0)(tsx@4.19.4)(yaml@2.7.1) + vitefu: 1.0.6(vite@6.3.4(@types/node@22.13.14)(jiti@2.4.2)(lightningcss@1.29.3)(terser@5.39.0)(tsx@4.19.4)(yaml@2.7.1)) + transitivePeerDependencies: + - supports-color + '@swc/helpers@0.5.17': dependencies: tslib: 2.8.1 @@ -4323,8 +4446,12 @@ snapshots: dependencies: character-entities: 2.0.2 + dedent-js@1.0.1: {} + deepmerge-ts@7.1.5: {} + deepmerge@4.3.1: {} + defu@6.1.4: {} degenerator@5.0.1: @@ -4469,8 +4596,14 @@ snapshots: optionalDependencies: source-map: 0.6.1 + esm-env@1.2.2: {} + esprima@4.0.1: {} + esrap@1.4.6: + dependencies: + '@jridgewell/sourcemap-codec': 1.5.0 + estraverse@5.3.0: {} estree-util-attach-comments@3.0.0: @@ -4858,6 +4991,10 @@ snapshots: is-plain-obj@4.1.0: {} + is-reference@3.0.3: + dependencies: + '@types/estree': 1.0.7 + is-wsl@3.1.0: dependencies: is-inside-container: 1.0.0 @@ -4946,6 +5083,8 @@ snapshots: lite-youtube-embed@0.3.3: {} + locate-character@3.0.0: {} + lodash.castarray@4.4.0: {} lodash.isplainobject@4.0.6: {} @@ -6171,6 +6310,30 @@ snapshots: supports-preserve-symlinks-flag@1.0.0: {} + svelte2tsx@0.7.39(svelte@5.30.2)(typescript@5.8.3): + dependencies: + dedent-js: 1.0.1 + pascal-case: 3.1.2 + svelte: 5.30.2 + typescript: 5.8.3 + + svelte@5.30.2: + dependencies: + '@ampproject/remapping': 2.3.0 + '@jridgewell/sourcemap-codec': 1.5.0 + '@sveltejs/acorn-typescript': 1.0.5(acorn@8.14.1) + '@types/estree': 1.0.7 + acorn: 8.14.1 + aria-query: 5.3.2 + axobject-query: 4.1.0 + clsx: 2.1.1 + esm-env: 1.2.2 + esrap: 1.4.6 + is-reference: 3.0.3 + locate-character: 3.0.0 + magic-string: 0.30.17 + zimmerframe: 1.1.2 + svgo@3.3.2: dependencies: '@trysound/sax': 0.2.0 @@ -6624,6 +6787,8 @@ snapshots: yoctocolors@2.1.1: {} + zimmerframe@1.1.2: {} + zod-to-json-schema@3.24.5(zod@3.24.3): dependencies: zod: 3.24.3 diff --git a/src/components/island/CodeHeart.svelte b/src/components/island/CodeHeart.svelte new file mode 100644 index 000000000..971fbbce3 --- /dev/null +++ b/src/components/island/CodeHeart.svelte @@ -0,0 +1,148 @@ + + +{#if !mini} +
+ +
+{:else} +
+ {#if isFavorite} + + + + {/if} +
+{/if} + + diff --git a/src/components/schedule/session.astro b/src/components/schedule/session.astro index 7747f3e1a..fb3515a34 100644 --- a/src/components/schedule/session.astro +++ b/src/components/schedule/session.astro @@ -1,10 +1,12 @@ --- +import CodeHeart from "@components/island/CodeHeart.svelte"; import Speakers from "./speakers.astro"; import { slugify } from "@utils/content"; export interface props { style: any; session: { + code: string; slug?: string; title: string; start: Date; @@ -25,7 +27,10 @@ const hasSpeakers = session.speakers && session.speakers.length > 0; const hasFooter = true; --- -
+
-
- - {session.rooms.length === 1 && ( -
- Room: - {session.rooms && session.rooms.join(", ")} -
- )} -
- +
+ { + session.rooms.length === 1 && ( +
+ Room: + {session.rooms && session.rooms.join(", ")} +
+ ) + } +
-

{session.title}

+ +

+ {session.title} +

{ hasFooter && ( @@ -79,7 +92,6 @@ const hasFooter = true; )} - ) } @@ -91,7 +103,6 @@ const hasFooter = true; diff --git a/src/components/sessions/list-sessions.astro b/src/components/sessions/list-sessions.astro index 8d09444a7..46f2811d6 100644 --- a/src/components/sessions/list-sessions.astro +++ b/src/components/sessions/list-sessions.astro @@ -2,11 +2,13 @@ const { sessions } = Astro.props; import Prose from "@ui/Prose.astro"; import Tag from "@ui/Tag.astro"; +import CodeHeart from "@components/island/CodeHeart.svelte"; import SessionSpeakers from "./session-speakers.astro"; type Session = { data: { title: string; + code: string; speakers: Array<{ id: string; collection: "speakers"; @@ -34,6 +36,13 @@ type Session = { {session.data.title} + +

Speakers: diff --git a/src/pages/[...slug].astro b/src/pages/[...slug].astro index 4257ea540..756d5aff3 100644 --- a/src/pages/[...slug].astro +++ b/src/pages/[...slug].astro @@ -14,6 +14,7 @@ import Icon from "@ui/Icon.astro"; import IconLabel from "@components/markdown/IconLabel.astro"; import Center from "@components/markdown/Center.astro"; import EPSLogo from "@components/markdown/EPSLogo.astro"; +import CodeHeart from '@components/island/CodeHeart.svelte'; import Prose from "@ui/Prose.astro"; diff --git a/src/pages/session/[slug].astro b/src/pages/session/[slug].astro index d7f851105..e1e7dae05 100644 --- a/src/pages/session/[slug].astro +++ b/src/pages/session/[slug].astro @@ -8,11 +8,12 @@ import { YouTube } from "@astro-community/astro-embed-youtube"; import { Picture } from "astro:assets"; import Markdown from "@ui/Markdown.astro"; import Section from "@ui/Section.astro"; +import CodeHeart from "@components/island/CodeHeart.svelte"; export async function getStaticPaths() { const sessions = await getCollection("sessions"); return sessions.map((entry) => ({ - params: { slug: entry.id}, + params: { slug: entry.id }, props: { entry }, })); } @@ -26,7 +27,7 @@ const speakers = await getEntries(entry.data.speakers); // Resolve session codes to session data const resolveSessions = (codes: string[]) => - codes.map(code => sessions.find(s => s.data.code === code)!); + codes.map((code) => sessions.find((s) => s.data.code === code)!); // Filter out sessions with room "Exhibit Hall" const filteredSessions = sessions.filter( @@ -59,6 +60,13 @@ const nextSessionsOrdered = sameRoomNextSession class="relative font-title text-text font-bold mb-[0.6em] [&>a]:border-0 [&>a]:text-inherit text-pretty" > {entry.data.title} + + + @@ -149,7 +157,13 @@ const nextSessionsOrdered = sameRoomNextSession The speaker{speakers.length > 1 ? "s" : ""} {speakers.map((speaker) => ( -

+
{speaker.data.avatar && (
@@ -239,6 +253,6 @@ const nextSessionsOrdered = sameRoomNextSession const expectedUrl = `/session/${slug}`; if (currentUrl !== expectedUrl) { - window.history.replaceState({}, '', expectedUrl); + window.history.replaceState({}, "", expectedUrl); } diff --git a/src/pages/speaker/[slug].astro b/src/pages/speaker/[slug].astro index 9f9639c82..4187a8fae 100644 --- a/src/pages/speaker/[slug].astro +++ b/src/pages/speaker/[slug].astro @@ -6,6 +6,7 @@ import { Image } from "astro:assets"; import Markdown from "@ui/Markdown.astro"; import Headline from "@ui/Headline.astro"; import Section from "@ui/Section.astro" +import CodeHeart from "@components/island/CodeHeart.svelte"; export async function getStaticPaths() { @@ -249,6 +250,13 @@ function getGitHosting(url: string): string | undefined { > {session.data.title} + + )) } diff --git a/src/utils/storage.js b/src/utils/storage.js new file mode 100644 index 000000000..e04b10b1d --- /dev/null +++ b/src/utils/storage.js @@ -0,0 +1,116 @@ +const STORAGE_PREFIX = "codeheart_v1"; + +/** + * Get the full storage key with namespace + * @param {string} id - Unique identifier for the code + * @returns {string} - The full storage key + */ +export function getStorageKey(id) { + if (!id) { + throw new Error("A code ID is required for storage operations"); + } + return `${STORAGE_PREFIX}:${id}`; +} + +/** + * Save code to localStorage + * @param {string} codeId - Unique identifier for the code + * @param {string} code - The code to save + * @param {string} title - Optional title for the code + * @returns {boolean} - Success status + */ +export function saveCode(codeId, code, title = "") { + try { + const item = { + code, + title, + timestamp: new Date().toISOString(), + version: 1, // For future compatibility + }; + + localStorage.setItem(getStorageKey(codeId), JSON.stringify(item)); + return true; + } catch (error) { + console.error("Failed to save code:", error); + return false; + } +} + +/** + * Remove code from localStorage + * @param {string} codeId - Unique identifier for the code + * @returns {boolean} - Success status + */ +export function removeCode(codeId) { + try { + localStorage.removeItem(getStorageKey(codeId)); + return true; + } catch (error) { + console.error("Failed to remove code:", error); + return false; + } +} + +/** + * Check if code is saved in localStorage + * @param {string} codeId - Unique identifier for the code + * @returns {boolean} - Whether the code is saved + */ +export function isCodeSaved(codeId) { + try { + return localStorage.getItem(getStorageKey(codeId)) !== null; + } catch (error) { + console.error("Failed to check if code is saved:", error); + return false; + } +} + +/** + * Get code from localStorage + * @param {string} codeId - Unique identifier for the code + * @returns {Object|null} - The code object or null if not found + */ +export function getCode(codeId) { + try { + const data = localStorage.getItem(getStorageKey(codeId)); + return data ? JSON.parse(data) : null; + } catch (error) { + console.error("Failed to get code:", error); + return null; + } +} + +/** + * Get all saved code snippets + * @returns {Array} - Array of saved code objects + */ +export function getAllSavedCodes() { + try { + const savedCodes = []; + + for (let i = 0; i < localStorage.length; i++) { + const key = localStorage.key(i); + + if (key && key.startsWith(`${STORAGE_PREFIX}:`)) { + try { + const codeId = key.substring(STORAGE_PREFIX.length + 1); + const data = JSON.parse(localStorage.getItem(key)); + + savedCodes.push({ + id: codeId, + ...data, + }); + } catch (err) { + console.error(`Failed to parse saved code: ${key}`, err); + } + } + } + + return savedCodes.sort( + (a, b) => new Date(b.timestamp) - new Date(a.timestamp) + ); + } catch (error) { + console.error("Failed to get all saved codes:", error); + return []; + } +} diff --git a/svelte.config.js b/svelte.config.js new file mode 100644 index 000000000..cf44f387a --- /dev/null +++ b/svelte.config.js @@ -0,0 +1,5 @@ +import { vitePreprocess } from "@astrojs/svelte"; + +export default { + preprocess: vitePreprocess(), +}; From d510d69d5db3ed3dab19c7c26dcf44f734d34e44 Mon Sep 17 00:00:00 2001 From: Niko Date: Wed, 21 May 2025 17:40:45 +0200 Subject: [PATCH 2/7] Update icon. --- src/components/island/CodeHeart.svelte | 19 +++++-------------- 1 file changed, 5 insertions(+), 14 deletions(-) diff --git a/src/components/island/CodeHeart.svelte b/src/components/island/CodeHeart.svelte index 971fbbce3..7669772fa 100644 --- a/src/components/island/CodeHeart.svelte +++ b/src/components/island/CodeHeart.svelte @@ -83,24 +83,14 @@ aria-label={isFavorite ? "Remove from favorites" : "Add to favorites"} title={isFavorite ? "Remove from favorites" : "Add to favorites"} > - {#if isFavorite} - - - - {:else} - - - - {/if} +
{:else}
- {#if isFavorite} - - - - {/if} + {#if isFavorite} + + {/if}
{/if} @@ -120,6 +110,7 @@ align-items: center; justify-content: center; transition: transform 0.2s ease; + font-size: 24px; } .heart-button:hover { From d2793937a9d6f52924d52ee09298da3011b4d63b Mon Sep 17 00:00:00 2001 From: Niko Date: Wed, 21 May 2025 17:54:50 +0200 Subject: [PATCH 3/7] Clean up astro check warnings. --- astro.config.mjs | 2 -- src/components/Modal.astro | 17 ++++++++++------- src/components/SocialMediaSponsorCard.astro | 2 -- src/components/schedule/session.astro | 1 - src/components/sponsors/sponsors.astro | 1 - src/pages/media/sponsor/[slug].astro | 2 +- 6 files changed, 11 insertions(+), 14 deletions(-) diff --git a/astro.config.mjs b/astro.config.mjs index 159759957..f32f38e49 100644 --- a/astro.config.mjs +++ b/astro.config.mjs @@ -12,8 +12,6 @@ import deleteUnusedImages from "astro-delete-unused-images"; import preload from "astro-preload"; import { execSync } from "node:child_process"; -import compress from "astro-compress"; - let gitVersion = String(process.env.GIT_VERSION ?? "").slice(0, 7); if (!gitVersion) { diff --git a/src/components/Modal.astro b/src/components/Modal.astro index 09ae15f04..63b63329a 100644 --- a/src/components/Modal.astro +++ b/src/components/Modal.astro @@ -29,7 +29,7 @@ const { id = 'modal', open = false, closeOnOutsideClick = false } = Astro.props; {#if !mini} -
- -
-{:else} -
- {#if isFavorite} - - {/if} -
+ +{:else if isFavorite} +
❤️
{/if} - - diff --git a/src/components/ui/Note.astro b/src/components/ui/Note.astro index 0177cbe2b..9019c0b84 100644 --- a/src/components/ui/Note.astro +++ b/src/components/ui/Note.astro @@ -45,7 +45,7 @@ const noteIcon = icon || currentStyle.defaultIcon; const noteTitle = title || currentStyle.defaultTitle; --- -

+

{ noteIcon && } diff --git a/src/layouts/ScheduleLayout.astro b/src/layouts/ScheduleLayout.astro index ce3606d11..189b43855 100644 --- a/src/layouts/ScheduleLayout.astro +++ b/src/layouts/ScheduleLayout.astro @@ -18,6 +18,7 @@ const { title, description } = Astro.props;

This is an early version of the schedule and may still change. If you're planning travel around specific sessions, please keep that in mind. + Mark talks as favorites by opening their details and tapping the ❤️ icon. Your selections are saved locally and will only be visible on this device.
diff --git a/src/stores/favorites.js b/src/stores/favorites.js new file mode 100644 index 000000000..a6d9ab58e --- /dev/null +++ b/src/stores/favorites.js @@ -0,0 +1,10 @@ +import { persistentMap } from "@nanostores/persistent"; + +export const favorites = persistentMap( + "codeheart_v2:", + {}, + { + encode: JSON.stringify, + decode: JSON.parse, + } +); diff --git a/src/utils/storage.js b/src/utils/storage.js deleted file mode 100644 index e04b10b1d..000000000 --- a/src/utils/storage.js +++ /dev/null @@ -1,116 +0,0 @@ -const STORAGE_PREFIX = "codeheart_v1"; - -/** - * Get the full storage key with namespace - * @param {string} id - Unique identifier for the code - * @returns {string} - The full storage key - */ -export function getStorageKey(id) { - if (!id) { - throw new Error("A code ID is required for storage operations"); - } - return `${STORAGE_PREFIX}:${id}`; -} - -/** - * Save code to localStorage - * @param {string} codeId - Unique identifier for the code - * @param {string} code - The code to save - * @param {string} title - Optional title for the code - * @returns {boolean} - Success status - */ -export function saveCode(codeId, code, title = "") { - try { - const item = { - code, - title, - timestamp: new Date().toISOString(), - version: 1, // For future compatibility - }; - - localStorage.setItem(getStorageKey(codeId), JSON.stringify(item)); - return true; - } catch (error) { - console.error("Failed to save code:", error); - return false; - } -} - -/** - * Remove code from localStorage - * @param {string} codeId - Unique identifier for the code - * @returns {boolean} - Success status - */ -export function removeCode(codeId) { - try { - localStorage.removeItem(getStorageKey(codeId)); - return true; - } catch (error) { - console.error("Failed to remove code:", error); - return false; - } -} - -/** - * Check if code is saved in localStorage - * @param {string} codeId - Unique identifier for the code - * @returns {boolean} - Whether the code is saved - */ -export function isCodeSaved(codeId) { - try { - return localStorage.getItem(getStorageKey(codeId)) !== null; - } catch (error) { - console.error("Failed to check if code is saved:", error); - return false; - } -} - -/** - * Get code from localStorage - * @param {string} codeId - Unique identifier for the code - * @returns {Object|null} - The code object or null if not found - */ -export function getCode(codeId) { - try { - const data = localStorage.getItem(getStorageKey(codeId)); - return data ? JSON.parse(data) : null; - } catch (error) { - console.error("Failed to get code:", error); - return null; - } -} - -/** - * Get all saved code snippets - * @returns {Array} - Array of saved code objects - */ -export function getAllSavedCodes() { - try { - const savedCodes = []; - - for (let i = 0; i < localStorage.length; i++) { - const key = localStorage.key(i); - - if (key && key.startsWith(`${STORAGE_PREFIX}:`)) { - try { - const codeId = key.substring(STORAGE_PREFIX.length + 1); - const data = JSON.parse(localStorage.getItem(key)); - - savedCodes.push({ - id: codeId, - ...data, - }); - } catch (err) { - console.error(`Failed to parse saved code: ${key}`, err); - } - } - } - - return savedCodes.sort( - (a, b) => new Date(b.timestamp) - new Date(a.timestamp) - ); - } catch (error) { - console.error("Failed to get all saved codes:", error); - return []; - } -} diff --git a/tsconfig.json b/tsconfig.json index a8a536cb7..8adfb1771 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -18,6 +18,7 @@ "@utils/*": ["./src/utils/*"], "@data/*": ["./src/data/*"], "@components/*": ["./src/components/*"], + "@stores/*": ["./src/stores/*"], "@sections/*": ["./src/components/sections/*"], "@ui/*": ["./src/components/ui/*"], "@layouts/*": ["./src/layouts/*"], From 263a9aa42903b59542a1ec55bfad930af6dd05d8 Mon Sep 17 00:00:00 2001 From: Niko Date: Tue, 27 May 2025 13:00:37 +0200 Subject: [PATCH 7/7] Add placeholder image --- public/images/placeholder.png | Bin 0 -> 103 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 public/images/placeholder.png diff --git a/public/images/placeholder.png b/public/images/placeholder.png new file mode 100644 index 0000000000000000000000000000000000000000..f607ae0a94ac5b56cfb50a8b275a4e878a391171 GIT binary patch literal 103 zcmeAS@N?(olHy`uVBq!ia0y~yU<5K585o&?RN5XZRUpM2;1lAy>hk^bZ}xlza^*c; u978f1-yUS-1@aCp`0k&}XTAW)1cQCNia`Cd5*}U!aXnrAT-G@yGywpw9~smD literal 0 HcmV?d00001