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

4
.gitignore vendored
View File

@ -35,4 +35,6 @@ coverage/
.vscode/settings.json
# Wrangler temp files
.wrangler
.wrangler
.idea

View File

@ -10,6 +10,7 @@ declare module 'vue' {
AppearanceToggle: typeof import('./theme/components/AppearanceToggle.vue')['default']
AppSBox: typeof import('./theme/components/AppSBox.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']
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
* @default ['index.md']
@ -79,7 +79,7 @@ export interface AppendMarkdownSectionOptions {
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 {
excludes = ['index.md'],
exclude = () => false,
@ -88,7 +88,7 @@ export function AppendMarkdownSection(options?: AppendMarkdownSectionOptions): P
let root = ''
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
// specify the plugins, which may cause this plugin to be executed after
// 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 } }))
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) {
return `${code}
export function TemplateAppSBox(_code: string): string {
return `
##
<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 系列
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 跨性别公益' 致力于帮助跨性别女性, 和宣传跨性别信息.

View File

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