mirror of
https://github.com/moeru-ai/airi.git
synced 2026-08-14 08:52:42 +00:00
feat(docs): authors region, published at, more tweaks on ui, @assets() fix
This commit is contained in:
@@ -1,8 +1,13 @@
|
||||
<script setup lang="ts">
|
||||
import type { DefaultTheme } from 'vitepress/theme'
|
||||
|
||||
import type { Author } from '../functions/authors.data'
|
||||
|
||||
import { intlFormat } from 'date-fns'
|
||||
import { AvatarFallback, AvatarImage, AvatarRoot } from 'reka-ui'
|
||||
import { Content, useData, useRoute } from 'vitepress'
|
||||
import { computed, toRefs } from 'vue'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
|
||||
// import DocCarbonAds from '../components/DocCarbonAds.vue'
|
||||
import DocCommunity from '../components/DocCommunity.vue'
|
||||
@@ -14,6 +19,9 @@ import DocTopbar from '../components/DocTopbar.vue'
|
||||
import { isBetweenHalloweenAndHalfOfNovember } from '../composables/date'
|
||||
import { flatten } from '../utils/flatten'
|
||||
|
||||
import * as authorsData from '../functions/authors.data'
|
||||
|
||||
const { t } = useI18n()
|
||||
const { theme, frontmatter } = useData()
|
||||
const { path } = toRefs(useRoute())
|
||||
|
||||
@@ -43,6 +51,25 @@ const isCommunityEnabled = computed(() => {
|
||||
})
|
||||
|
||||
const isCharactersPage = computed(() => path.value.includes('characters'))
|
||||
|
||||
const publishedAt = computed(() => {
|
||||
if (frontmatter.value.publishedAtOverride) {
|
||||
return frontmatter.value.publishedAtOverride
|
||||
}
|
||||
if (frontmatter.value.publishedAt) {
|
||||
return intlFormat(new Date(frontmatter.value.publishedAt), { dateStyle: 'long' })
|
||||
}
|
||||
if (frontmatter.value.date) {
|
||||
return intlFormat(new Date(frontmatter.value.data), { dateStyle: 'long' })
|
||||
}
|
||||
|
||||
return undefined
|
||||
})
|
||||
|
||||
const authors = computed(() => {
|
||||
const data = (authorsData as unknown as { data: Array<{ url: string, authors: Author[] }> }).data
|
||||
return data.find(item => item.url === path.value)?.authors || []
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
@@ -89,6 +116,50 @@ const isCharactersPage = computed(() => path.value.includes('characters'))
|
||||
{{ frontmatter.title || '' }}
|
||||
</h1>
|
||||
|
||||
<div v-if="publishedAt || authors && authors.length" class="mb-10 mt-5 flex flex-col gap-3 sm:gap-5">
|
||||
<div v-if="publishedAt" class="text-neutral-400 dark:text-neutral-500">
|
||||
<span>
|
||||
{{ t('docs.theme.doc.published-at', { date: publishedAt }) }}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-row gap-2 sm:gap-4">
|
||||
<!-- Authors -->
|
||||
<div v-for="(author, index) of authors" :key="index" class="flex flex-row items-center gap-2.5">
|
||||
<AvatarRoot class="size-10 inline-flex select-none items-center justify-center overflow-hidden rounded-full bg-neutral-100 align-middle dark:bg-neutral-800">
|
||||
<AvatarImage
|
||||
class="h-full w-full rounded-[inherit] object-cover"
|
||||
:src="author.avatar || author.avatarFallback"
|
||||
:alt="`${author.displayName}'s avatar`"
|
||||
/>
|
||||
<AvatarFallback
|
||||
class="h-full w-full flex items-center justify-center bg-white text-sm text-primary font-medium leading-1 dark:bg-neutral-800 dark:text-neutral-300"
|
||||
:delay-ms="600"
|
||||
as-child
|
||||
>
|
||||
{{
|
||||
[
|
||||
author.displayName.charAt(0).toUpperCase(),
|
||||
author.displayName.charAt(1).toUpperCase(),
|
||||
].join('')
|
||||
}}
|
||||
</AvatarFallback>
|
||||
</AvatarRoot>
|
||||
|
||||
<div class="flex flex-col">
|
||||
<div>
|
||||
<span>{{ author.displayName }}</span>
|
||||
</div>
|
||||
<div v-if="author.githubUsername">
|
||||
<a :href="`https://github.com/${author.githubUsername}`" target="_blank" rel="noopener noreferrer" class="text-sm text-primary hover:underline">
|
||||
<span>{{ author.githubUsername }}</span>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<Content />
|
||||
</article>
|
||||
|
||||
|
||||
@@ -0,0 +1,90 @@
|
||||
import { webcrypto } from 'node:crypto'
|
||||
|
||||
import { createContentLoader } from 'vitepress'
|
||||
|
||||
export interface Author {
|
||||
role: string
|
||||
kind: 'person' | 'team'
|
||||
|
||||
displayName: string
|
||||
|
||||
githubUsername?: string
|
||||
githubEmail?: string
|
||||
|
||||
avatar?: string
|
||||
avatarFallback: string
|
||||
}
|
||||
|
||||
interface MarkdownAuthor {
|
||||
name?: string
|
||||
role?: string
|
||||
kind?: 'person' | 'team'
|
||||
avatar?: string
|
||||
|
||||
githubUsername?: string
|
||||
githubEmail?: string
|
||||
}
|
||||
|
||||
/**
|
||||
* Hashes a string using SHA-256
|
||||
*
|
||||
* Official example by MDN: https://developer.mozilla.org/en-US/docs/Web/API/SubtleCrypto/digest
|
||||
* @param {string} message - The message to be hashed
|
||||
* @returns {Promise<string>} - The SHA-256 hash of the message
|
||||
*/
|
||||
async function digestStringAsSHA256(message: string) {
|
||||
const msgUint8 = new TextEncoder().encode(message) // encode as (utf-8) Uint8Array
|
||||
const hashBuffer = await webcrypto.subtle.digest('SHA-256', msgUint8) // hash the message
|
||||
const hashArray = Array.from(new Uint8Array(hashBuffer)) // convert buffer to byte array
|
||||
const hashHex = hashArray
|
||||
.map(b => b.toString(16).padStart(2, '0'))
|
||||
.join('') // convert bytes to hex string
|
||||
return hashHex
|
||||
}
|
||||
|
||||
async function newAvatarForAuthor(mappedAuthor?: { overrideAvatar?: string, githubUsername?: string, displayName?: string } | null, email?: string | null): Promise<string> {
|
||||
if (mappedAuthor) {
|
||||
if (mappedAuthor.overrideAvatar)
|
||||
return mappedAuthor.overrideAvatar
|
||||
if (mappedAuthor.githubUsername)
|
||||
return `https://github.com/${mappedAuthor.githubUsername}.png`
|
||||
}
|
||||
|
||||
return `https://gravatar.com/avatar/${await digestStringAsSHA256(email || mappedAuthor?.githubUsername || mappedAuthor?.displayName || 'unknown')}?d=retro`
|
||||
}
|
||||
|
||||
export default createContentLoader('**/*.md', {
|
||||
async transform(raw): Promise<Array<{ url: string, authors: Author[] }>> {
|
||||
return (await Promise.all(
|
||||
raw
|
||||
.map(async ({ url, frontmatter }) => {
|
||||
const authors: MarkdownAuthor[] = frontmatter.authors
|
||||
if (!authors || !Array.isArray(authors)) {
|
||||
return
|
||||
}
|
||||
|
||||
const authorsTransformed = await Promise.all(authors.map(async (author): Promise<Author> => {
|
||||
const displayName = author.name || author.githubUsername || author.githubEmail || 'Unknown Author'
|
||||
|
||||
return {
|
||||
role: author.role || 'Contributor',
|
||||
kind: author.kind || 'person',
|
||||
|
||||
displayName,
|
||||
|
||||
githubUsername: author.githubUsername,
|
||||
githubEmail: author.githubEmail,
|
||||
|
||||
avatar: author.avatar || await newAvatarForAuthor({ githubUsername: author.githubUsername, displayName }, author.githubEmail),
|
||||
avatarFallback: `https://gravatar.com/avatar/${await digestStringAsSHA256(displayName)}?d=retro`,
|
||||
}
|
||||
}))
|
||||
|
||||
return {
|
||||
url,
|
||||
authors: authorsTransformed,
|
||||
}
|
||||
}),
|
||||
)).filter(item => item != null)
|
||||
},
|
||||
})
|
||||
@@ -10,7 +10,7 @@ import matter from 'gray-matter'
|
||||
import { glob } from 'tinyglobby'
|
||||
|
||||
function fromAtAssets(url: string): string {
|
||||
const reg = /^@assets\(('\S+')|("\S+")|(\S+)\)$/
|
||||
const reg = /^@assets\((?:'(\S+)'|"(\S+)"|(\S+))\)$/
|
||||
if (reg.test(url)) {
|
||||
const res = url
|
||||
.trim()
|
||||
@@ -35,7 +35,7 @@ interface VitePressConfig extends ResolvedConfig {
|
||||
function recursivelyFindAtAssets(propertyMaybeObjectOrScalar: unknown, fn: (value: string) => string | undefined) {
|
||||
if (typeof propertyMaybeObjectOrScalar === 'string') {
|
||||
// eslint-disable-next-line regexp/no-unused-capturing-group
|
||||
if (/^@assets\(('\S+')|("\S+")|(\S+)\)$/.test(propertyMaybeObjectOrScalar)) {
|
||||
if (/^@assets\((?:'(\S+)'|"(\S+)"|(\S+))\)$/.test(propertyMaybeObjectOrScalar)) {
|
||||
// If the string matches the @assets(...) pattern, we replace it with the result of the function
|
||||
const match = fromAtAssets(propertyMaybeObjectOrScalar)
|
||||
const modified = fn(match)
|
||||
|
||||
@@ -103,7 +103,7 @@
|
||||
}
|
||||
|
||||
&> :not(p:nth-child(1)) {
|
||||
@apply text-slate-950 dark:text-slate-50 leading-snug;
|
||||
@apply text-slate-950 dark:text-slate-50 leading-normal;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -123,7 +123,7 @@
|
||||
}
|
||||
|
||||
&> :not(p:nth-child(1)) {
|
||||
@apply text-violet-950 dark:text-violet-50 leading-snug;
|
||||
@apply text-violet-950 dark:text-violet-50 leading-normal;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -143,7 +143,7 @@
|
||||
}
|
||||
|
||||
&> :not(p:nth-child(1)) {
|
||||
@apply text-yellow-950 dark:text-yellow-50 leading-snug;
|
||||
@apply text-yellow-950 dark:text-yellow-50 leading-normal;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -163,7 +163,7 @@
|
||||
}
|
||||
|
||||
&> :not(p:nth-child(1)) {
|
||||
@apply text-red-950 dark:text-red-50 leading-snug;
|
||||
@apply text-red-950 dark:text-red-50 leading-normal;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,15 @@
|
||||
# Project AIRI Manual
|
||||
|
||||
Writing time: (UTC+8) April 2, 2026 – evening
|
||||
---
|
||||
title: Project AIRI Manual
|
||||
authors:
|
||||
- name: MuGewRayce
|
||||
role: Lead writing team
|
||||
kind: person
|
||||
- name: JhIcefair
|
||||
role: Contributing editor (primary)
|
||||
kind: person
|
||||
publishedAt: 2026-04-02
|
||||
publishedAtOverride: April 2, 2026 – evening (UTC+8)
|
||||
---
|
||||
|
||||
Corresponding version: AIRI-0.9.0-beta.4-windows-x64
|
||||
|
||||
|
||||
@@ -1,6 +1,18 @@
|
||||
# Project AIRI 说明书
|
||||
|
||||
编写时间:(北京时间)2026 年 4 月 2 日 - 晚上
|
||||
---
|
||||
title: Project AIRI 说明书
|
||||
authors:
|
||||
- name: 沐玖芸萱
|
||||
aliases:
|
||||
- 沐玖芸萱
|
||||
- MuGewRayce
|
||||
role: Lead writing team
|
||||
kind: person
|
||||
- name: JhIcefair
|
||||
role: Contributing editor (primary)
|
||||
kind: person
|
||||
publishedAt: 2026-04-02
|
||||
publishedAtOverride: 2026 年 4 月 2 日 - 晚上(北京时间)
|
||||
---
|
||||
|
||||
对应版本:AIRI-0.9.0-beta.4-windows-x64
|
||||
|
||||
|
||||
@@ -7,6 +7,9 @@ export default defineConfig({
|
||||
presetAttributify(),
|
||||
presetTypography({
|
||||
cssExtend: {
|
||||
'h1': {
|
||||
'margin-bottom': '1rem',
|
||||
},
|
||||
'a': {
|
||||
'color': '#223f5dff',
|
||||
'text-decoration': 'underline',
|
||||
|
||||
@@ -15,6 +15,7 @@ doc:
|
||||
title: Next page
|
||||
previous-page:
|
||||
title: Previous page
|
||||
published-at: Published at {date}
|
||||
home:
|
||||
subtitle: >-
|
||||
Re-creating Neuro-sama, a container of souls of AI waifu / virtual
|
||||
|
||||
Reference in New Issue
Block a user