Show page contributors from git history, linked to Gitea

This commit is contained in:
Djeex
2026-08-30 17:01:34 +02:00
parent ab8671e0fb
commit 630e8b9c84
3 changed files with 62 additions and 0 deletions
+36
View File
@@ -82,6 +82,23 @@ const editLink = computed(() => {
].filter(Boolean).join('/')
})
const contributors = computed(() => (page.value as unknown as Record<string, unknown>)?.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`)
</script>
@@ -149,6 +166,25 @@ addPrerenderPath(`/raw${route.path}.md`)
</template>
</div>
</USeparator>
<div
v-if="contributors?.length"
class="flex items-center gap-2 text-sm text-muted"
>
<UIcon
name="i-lucide-users"
class="size-4 shrink-0"
/>
<span>{{ locale === 'fr' ? (contributors.length > 1 ? 'Contributeurs' : 'Contributeur') : (contributors.length > 1 ? 'Contributors' : 'Contributor') }}:</span>
<ULink
v-if="historyLink"
:to="historyLink"
target="_blank"
class="text-highlighted hover:underline"
>
{{ contributors.join(', ') }}
</ULink>
<span v-else>{{ contributors.join(', ') }}</span>
</div>
<UContentSurround :surround="surround" />
</UPageBody>
+1
View File
@@ -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<string, DefinedCollection>
+25
View File
@@ -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<string, unknown> }) => {
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.