diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 5710157..b2f2bb8 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -35,6 +35,7 @@ jobs: for svc in api:backend web:frontend; do docker buildx build --platform linux/amd64,linux/arm64 -f "${svc#*:}/Dockerfile" \ --label org.opencontainers.image.source=https://github.com/Wjouter/InTheKnow \ + --build-arg INTHEKNOW_VERSION="$VERSION" \ -t "$IMAGE-${svc%%:*}:$VERSION" --push . done - name: Render the stack definition diff --git a/frontend/Dockerfile b/frontend/Dockerfile index 45a1d73..ec34fb4 100644 --- a/frontend/Dockerfile +++ b/frontend/Dockerfile @@ -5,6 +5,8 @@ WORKDIR /src COPY frontend/package.json frontend/package-lock.json ./ RUN npm ci COPY frontend/ ./ +# Shown in the footer; set by the release workflow, "dev" for any other build. +ARG INTHEKNOW_VERSION=dev RUN npm run build RUN mkdir /prod && cp package.json package-lock.json /prod/ && npm ci --omit=dev --ignore-scripts --prefix /prod diff --git a/frontend/src/app.d.ts b/frontend/src/app.d.ts index 7512120..0478f80 100644 --- a/frontend/src/app.d.ts +++ b/frontend/src/app.d.ts @@ -1,6 +1,9 @@ import type { Dict, Lang } from '$lib/i18n'; declare global { + /** The release version (vite.config.ts), "dev" outside a release build. */ + const __VERSION__: string; + type Me = { email: string; role: 'user' | 'admin'; diff --git a/frontend/src/lib/i18n/en.ts b/frontend/src/lib/i18n/en.ts index eca1ad1..9f63888 100644 --- a/frontend/src/lib/i18n/en.ts +++ b/frontend/src/lib/i18n/en.ts @@ -692,6 +692,18 @@ export const en = { preferences: 'Preferences', notifications: 'Notifications' }, + about: { + link: 'About', + title: 'About InTheKnow', + intro: 'Free and open-source software.', + version: (v: string) => `Version ${v}`, + licenseTitle: 'License', + license: 'InTheKnow is licensed under the GNU General Public License, version 3. You may use, study, share and change it under that license.', + source: 'Source code', + licenseText: 'License text', + creditsTitle: 'Built with', + credits: 'InTheKnow uses these open-source projects. Thanks to everyone who makes them.' + }, errors: { generic: 'Something went wrong.', genericBody: 'Try again, or go back to the home page.', diff --git a/frontend/src/lib/i18n/nl.ts b/frontend/src/lib/i18n/nl.ts index 527fbe2..9abb939 100644 --- a/frontend/src/lib/i18n/nl.ts +++ b/frontend/src/lib/i18n/nl.ts @@ -696,6 +696,18 @@ export const nl: Dict = { preferences: 'Voorkeuren', notifications: 'Meldingen' }, + about: { + link: 'Over', + title: 'Over InTheKnow', + intro: 'Vrije en open-source software.', + version: (v: string) => `Versie ${v}`, + licenseTitle: 'Licentie', + license: 'InTheKnow valt onder de GNU General Public License, versie 3. Je mag het onder die licentie gebruiken, bestuderen, delen en aanpassen.', + source: 'Broncode', + licenseText: 'Licentietekst', + creditsTitle: 'Gemaakt met', + credits: 'InTheKnow gebruikt deze open-sourceprojecten. Dank aan iedereen die ze maakt.' + }, errors: { generic: 'Er ging iets mis.', genericBody: 'Probeer het opnieuw, of ga terug naar de startpagina.', diff --git a/frontend/src/lib/paths.ts b/frontend/src/lib/paths.ts index 22422f7..0787d1b 100644 --- a/frontend/src/lib/paths.ts +++ b/frontend/src/lib/paths.ts @@ -6,4 +6,5 @@ export const isPublic = (path: string) => path === '/account' || path.startsWith('/account/') || path === '/language' || + path === '/about' || path.startsWith('/_app/'); diff --git a/frontend/src/routes/+layout.svelte b/frontend/src/routes/+layout.svelte index 1f5048c..92755ad 100644 --- a/frontend/src/routes/+layout.svelte +++ b/frontend/src/routes/+layout.svelte @@ -18,6 +18,7 @@ // Svelte sets document.title from only when the value changes, and pages set their own. So this reruns // only when the unavailable view comes or goes, not on every navigation. const unavailable = $derived(Boolean(data.unavailable)); + const release = __VERSION__ === 'dev' ? '' : `/releases/tag/v${__VERSION__}`; // A notice handed over by a redirecting action (enhance.svelte.ts). Taken once and cleared from the history entry, // so Back doesn't show it again; a new object each time, so the same text twice shows twice. @@ -43,6 +44,11 @@ {:else} {@render children()} {/if} + {#if data.me}{/if} {#key notice}{/key} @@ -54,6 +60,18 @@ h1 { font: var(--type-h3); } + footer { + display: flex; + justify-content: center; + gap: var(--space-2); + padding: var(--space-4); + border-top: 1px solid var(--border-subtle); + font: var(--type-small); + color: var(--text-muted); + } + footer a { + color: inherit; + } @media (min-width: 768px) { main.with-phone-nav { padding-bottom: 0; diff --git a/frontend/src/routes/about/+page.svelte b/frontend/src/routes/about/+page.svelte new file mode 100644 index 0000000..22ac75c --- /dev/null +++ b/frontend/src/routes/about/+page.svelte @@ -0,0 +1,71 @@ + + +{t.title} · InTheKnow + + +
+

{t.version(__VERSION__)}

+
+

{t.licenseTitle}

+

{t.license}

+ +
+
+

{t.creditsTitle}

+

{t.credits}

+
    + {#each credits as c (c.name)} +
  • {c.name} · {c.license}
  • + {/each} +
+
+
+ + diff --git a/frontend/vite.config.ts b/frontend/vite.config.ts index 66641e1..e25cff4 100644 --- a/frontend/vite.config.ts +++ b/frontend/vite.config.ts @@ -1,10 +1,12 @@ import { sveltekit } from '@sveltejs/kit/vite'; +import { loadEnv } from 'vite'; import { defineConfig } from 'vitest/config'; -export default defineConfig({ +export default defineConfig(({ mode }) => ({ plugins: [sveltekit()], + define: { __VERSION__: JSON.stringify(loadEnv(mode, '.', 'INTHEKNOW_').INTHEKNOW_VERSION || 'dev') }, test: { environment: 'node', include: ['src/**/*.test.ts'] } -}); +}));