From 630e8b9c84e9c1ae801bce56fea069fee33a097d Mon Sep 17 00:00:00 2001 From: Djeex Date: Sun, 30 Aug 2026 17:01:34 +0200 Subject: [PATCH] Show page contributors from git history, linked to Gitea --- app/pages/[[lang]]/[...slug].vue | 36 ++++++++++++++++++++++++++++++++ content.config.ts | 1 + nuxt.config.ts | 25 ++++++++++++++++++++++ 3 files changed, 62 insertions(+) diff --git a/app/pages/[[lang]]/[...slug].vue b/app/pages/[[lang]]/[...slug].vue index a2ea6e2..3c6c35a 100644 --- a/app/pages/[[lang]]/[...slug].vue +++ b/app/pages/[[lang]]/[...slug].vue @@ -82,6 +82,23 @@ const editLink = computed(() => { ].filter(Boolean).join('/') }) +const contributors = computed(() => (page.value as unknown as Record)?.contributors as string[] | undefined) + +const historyLink = computed(() => { + if (!giteaUrl.value) { + return + } + + return [ + giteaUrl.value, + 'commits', + 'branch', + 'main', + 'content', + `${page.value?.stem}.${page.value?.extension}`, + ].filter(Boolean).join('/') +}) + // Add the page path to the prerender list addPrerenderPath(`/raw${route.path}.md`) @@ -149,6 +166,25 @@ addPrerenderPath(`/raw${route.path}.md`) +
+ + {{ locale === 'fr' ? (contributors.length > 1 ? 'Contributeurs' : 'Contributeur') : (contributors.length > 1 ? 'Contributors' : 'Contributor') }}: + + {{ contributors.join(', ') }} + + {{ contributors.join(', ') }} +
diff --git a/content.config.ts b/content.config.ts index 5331bce..b90d86c 100644 --- a/content.config.ts +++ b/content.config.ts @@ -35,6 +35,7 @@ const createDocsSchema = () => z.object({ hideHeader: z.boolean().optional(), hideCopyPage: z.boolean().optional(), hideToc: z.boolean().optional(), + contributors: z.array(z.string()).optional(), }) let collections: Record diff --git a/nuxt.config.ts b/nuxt.config.ts index 92a8cfc..556d251 100644 --- a/nuxt.config.ts +++ b/nuxt.config.ts @@ -1,8 +1,27 @@ import { fileURLToPath } from 'node:url' +import { execFileSync } from 'node:child_process' import { createResolver } from '@nuxt/kit' const { resolve } = createResolver(import.meta.url) +// Contributors per page: read straight from git history rather than an +// API, so it needs no token and no network call, but the CI checkout must +// fetch full history (not a shallow clone) or every file will only show +// its most recent author. +function getContributors(absoluteFilePath: string): string[] { + try { + const output = execFileSync( + 'git', + ['log', '--format=%an', '--follow', '--', absoluteFilePath], + { cwd: resolve('.'), encoding: 'utf8' }, + ) + return [...new Set(output.split('\n').map(line => line.trim()).filter(Boolean))] + } + catch { + return [] + } +} + // @nuxt/image resolves its file-storage root differently in `nuxi dev` // (needs an absolute path) vs a production build (needs the plain // `public/` string — an absolute path there breaks the SVG content-type @@ -60,6 +79,12 @@ export default defineNuxtConfig({ name: 'Français', }], }, + hooks: { + 'content:file:afterParse': (ctx: { file: { path?: string }, content: Record }) => { + if (!ctx.file.path?.endsWith('.md')) return + ctx.content.contributors = getContributors(ctx.file.path) + }, + }, // Permanent (301) redirects from the old site's French URLs (root-level, // no locale prefix) to their new /fr/... equivalents, so bookmarks and // search engine rankings carry over.