feat: show copyright in header (#297)

Signed-off-by: 北雁 Cryolitia <Cryolitia@gmail.com>
Co-authored-by: BeiyanYunyi
<14120445+BeiyanYunyi@users.noreply.github.com>
Co-authored-by: Neko Ayaka <neko@ayaka.moe>
pull/288/head^2
北雁 Cryolitia 2024-04-10 08:53:17 +08:00 committed by 北雁 Cryolitia
parent 8cea374cb1
commit fe31b9ef6b
No known key found for this signature in database
GPG Key ID: 3E5D1772FC8A8EDD
8 changed files with 164 additions and 18 deletions

2
.gitignore vendored
View File

@ -36,3 +36,5 @@ coverage/
# Wrangler temp files # Wrangler temp files
.wrangler .wrangler
.idea

View File

@ -10,6 +10,7 @@ declare module 'vue' {
AppearanceToggle: typeof import('./theme/components/AppearanceToggle.vue')['default'] AppearanceToggle: typeof import('./theme/components/AppearanceToggle.vue')['default']
AppSBox: typeof import('./theme/components/AppSBox.vue')['default'] AppSBox: typeof import('./theme/components/AppSBox.vue')['default']
ArticlesMenu: typeof import('./theme/components/ArticlesMenu.vue')['default'] ArticlesMenu: typeof import('./theme/components/ArticlesMenu.vue')['default']
CopyrightInfo: typeof import('./theme/components/CopyrightInfo.vue')['default']
HomeContent: typeof import('./theme/components/HomeContent.vue')['default'] HomeContent: typeof import('./theme/components/HomeContent.vue')['default']
PageInfo: typeof import('./theme/components/PageInfo.vue')['default'] PageInfo: typeof import('./theme/components/PageInfo.vue')['default']
} }

View File

@ -0,0 +1,60 @@
import { ContentData, createContentLoader, defineLoader } from "vitepress";
let contentLoader = createContentLoader('/**/*.md')
export interface Node<T> {
value: T | null;
children: { [key: string]: Node<T> };
}
export interface Trie<T> {
root: Node<T>;
insert(path: string[], value: T, node: Node<T>): void;
insert(path: string[], value: T): void;
}
declare const data: Trie<Record<string, any>>
export { data };
// noinspection JSUnusedGlobalSymbols
export default defineLoader({
watch: contentLoader.watch,
async load(): Promise<Trie<Record<string, any>>> {
let raw: ContentData[] = await contentLoader.load()
let trie: Trie<Record<string, any>> = {
root: { value: null, children: {} },
insert(this: Trie<Record<string, any>>, path, value, node: Node<Record<string, any>> = this.root) {
if (path.length === 0) {
node.value = value
} else if (path.length === 1) {
if (!(path[0] in node.children)) {
node.children[path[0]] = { value: value, children: {} }
} else {
node.children[path[0]].value = value
}
} else {
if (!(path[0] in node.children)) {
let new_node = { value: null, children: {} }
this.insert(path.slice(1), value, new_node)
node.children[path[0]] = new_node
} else {
this.insert(path.slice(1), value, node.children[path[0]])
}
}
}
}
for (let v of raw) {
let frontmatter = v.frontmatter ?? null
let copyright = frontmatter?.copyright ?? null
if (copyright !== null) {
trie.insert(v.url.split('/').filter((item, _index, _array) => item !== ''), {
author: frontmatter.author ?? null,
title: frontmatter.title ?? null,
copyright: copyright
})
}
}
return trie;
}
})

View File

@ -63,7 +63,7 @@ interface Context {
} }
} }
export interface AppendMarkdownSectionOptions { export interface MarkdownSectionWrapperOptions {
/** /**
* The list of file names to exclude from the transformation * The list of file names to exclude from the transformation
* @default ['index.md'] * @default ['index.md']
@ -79,7 +79,7 @@ export interface AppendMarkdownSectionOptions {
exclude?: (id: string, context: Context) => boolean exclude?: (id: string, context: Context) => boolean
} }
export function AppendMarkdownSection(options?: AppendMarkdownSectionOptions): Plugin { export function MarkdownSectionWrapper(headerTransformers: ((origin: string) => string)[], footerTransformers: ((origin: string) => string)[], options?: MarkdownSectionWrapperOptions): Plugin {
const { const {
excludes = ['index.md'], excludes = ['index.md'],
exclude = () => false, exclude = () => false,
@ -88,7 +88,7 @@ export function AppendMarkdownSection(options?: AppendMarkdownSectionOptions): P
let root = '' let root = ''
return { return {
name: '@pjts/append-markdown-section', name: '@pjts/markdown-section-wrapper',
// May set to 'pre' since end user may use vitepress wrapped vite plugin to // May set to 'pre' since end user may use vitepress wrapped vite plugin to
// specify the plugins, which may cause this plugin to be executed after // specify the plugins, which may cause this plugin to be executed after
// vitepress or the other markdown processing plugins. // vitepress or the other markdown processing plugins.
@ -116,18 +116,28 @@ export function AppendMarkdownSection(options?: AppendMarkdownSectionOptions): P
if (exclude(id, { helpers: { idEndsWith, idEquals, idStartsWith, pathEndsWith, pathEquals, pathStartsWith } })) if (exclude(id, { helpers: { idEndsWith, idEquals, idStartsWith, pathEndsWith, pathEquals, pathStartsWith } }))
return null return null
code = TemplateAppSBox(code) let headers: string[] = headerTransformers.map(f => f(code))
let footers: string[] = footerTransformers.map(f => f(code))
return code return [...headers, code, ...footers].join("")
}, },
} }
} }
function TemplateAppSBox(code: string) { export function TemplateAppSBox(_code: string): string {
return `${code} return `
## ##
<AppSBox /> <AppSBox />
`
}
export function TemplateCopyrightInfo(_code: string): string {
return `
<CopyrightInfo />
` `
} }

View File

@ -0,0 +1,65 @@
<script setup lang="ts">
import { useData } from "vitepress";
import { Node, Trie, data } from "../../plugins/CopyrightLoader.data";
function searchClosestInTrie(
that: Trie<Record<string, any>>,
path: string[],
node: Node<Record<string, any>> = that.root
): Record<string, any> | null {
if (path.length === 0) {
return node.value;
}
if (path[0] in node.children) {
let value = searchClosestInTrie(
that,
path.slice(1),
node.children[path[0]]
);
if (value === null) {
value = node.value;
}
return value;
}
return node.value;
}
const paths = useData()
.page.value.relativePath.split('/')
.filter((item: string) => item !== '');
const attrs = searchClosestInTrie(data, paths);
const frontmatter = useData().frontmatter.value;
const originUrlExists = (attrs?.copyright?.url ?? null) != null;
const originUrl = attrs?.copyright?.url ?? 'javascript:void(0)';
const license = attrs?.copyright?.license ?? null;
const licenseExists = license != null;
const licenseUrlExists = (attrs?.copyright?.licenseUrl ?? null) != null;
const licenseUrl = attrs?.copyright?.licenseUrl ?? 'javascript:void(0)'
</script>
<template>
<div v-if="attrs?.copyright?.enable ?? false">
<br />
<hr />
<div class="tip custom-block">
<p class="custom-block-title">Copyright</p>
<p>
<span>这篇文章 </span>
<a v-if="originUrlExists" :href="originUrl">{{ frontmatter.title }}</a>
<span v-else>{{ frontmatter.title }}</span>
<span> </span>
<span v-for="author in attrs?.author" :key="author">{{ author }}</span>
<span> 创作</span>
<span v-if="licenseExists">
<span>Project Trans </span>
<a v-if="licenseUrlExists" :href="licenseUrl">{{ license }}</a>
<span v-else>{{ license }}</span>
<span> 许可下使用</span>
</span>
<span></span>
</p>
</div>
</div>
</template>

View File

@ -1,6 +1,11 @@
--- ---
title: be a girl 系列 title: be a girl 系列
author: yuzu trans author: yuzu trans
copyright:
enable: true
url: https://yuzu-trans.notion.site/efaf2995998b475b95e47aa5efce3edc
license: CC BY 4.0
licenseUrl: https://creativecommons.org/licenses/by/4.0/
--- ---
'sayo 跨性别公益' 致力于帮助跨性别女性, 和宣传跨性别信息. 'sayo 跨性别公益' 致力于帮助跨性别女性, 和宣传跨性别信息.

View File

@ -1,7 +1,7 @@
import { resolve } from 'node:path' import { resolve } from 'node:path'
import { defineConfig } from 'vite' import { defineConfig } from 'vite'
import { MarkdownTransform } from './.vitepress/plugins/markdownTransform' import { MarkdownTransform } from './.vitepress/plugins/MarkdownTransform'
import { AppendMarkdownSection } from './.vitepress/plugins/appendMarkdownSection' import { MarkdownSectionWrapper, TemplateAppSBox, TemplateCopyrightInfo } from './.vitepress/plugins/MarkdownSectionWrapper'
import Components from 'unplugin-vue-components/vite' import Components from 'unplugin-vue-components/vite'
import UnoCSS from 'unocss/vite' import UnoCSS from 'unocss/vite'
import { GitChangelog, GitChangelogMarkdownSection } from '@nolebase/vitepress-plugin-git-changelog/vite' import { GitChangelog, GitChangelogMarkdownSection } from '@nolebase/vitepress-plugin-git-changelog/vite'
@ -43,15 +43,18 @@ export default defineConfig({
return false return false
}, },
}), }),
AppendMarkdownSection({ MarkdownSectionWrapper(
excludes: [], [TemplateCopyrightInfo],
exclude: (_, { helpers }): boolean => { [TemplateAppSBox],
if (helpers.idEquals('index.md')) {
return true excludes: [],
exclude: (_, { helpers }): boolean => {
if (helpers.idEquals('index.md'))
return true
return false return false
}, },
}), }),
Components({ Components({
dirs: resolve(__dirname, '.vitepress/theme/components'), dirs: resolve(__dirname, '.vitepress/theme/components'),
include: [/\.vue$/, /\.vue\?vue/, /\.md$/], include: [/\.vue$/, /\.vue\?vue/, /\.md$/],