Show page contributors from git history, linked to Gitea
This commit is contained in:
@@ -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>
|
||||
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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.
|
||||
|
||||
Reference in New Issue
Block a user