Skip to content

Commit 319e3a6

Browse files
tmp
1 parent 49a14b3 commit 319e3a6

24 files changed

+969
-72
lines changed

docs/.vitepress/config.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import { defineConfig } from 'vitepress';
22
import footnote from 'markdown-it-footnote';
33
import UnoCSS from 'unocss/vite';
44
import TypstRender from './typst_render';
5+
import { MarkdownTransform } from './plugins/markdown_transform';
56

67
// https://vitepress.dev/reference/site-config
78
export default defineConfig({
@@ -120,6 +121,6 @@ gtag('config', 'G-NL1RYQ4PW7');`,
120121
},
121122

122123
vite: {
123-
plugins: [UnoCSS()],
124+
plugins: [UnoCSS(), MarkdownTransform()],
124125
},
125126
});
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
import { normalizePath, type Plugin } from 'vite';
2+
3+
/**
4+
* A Vite plugin that transforms append contents to markdown sources.
5+
*
6+
* Inspired by Zotero Chinese, licensed under MIT.
7+
* https://github.com/zotero-chinese/website/blob/138ce84ceb8f31b8457babe9119f8e3d35363ed7/src/.vitepress/plugins/markdownTransform.ts
8+
*/
9+
export function MarkdownTransform(): Plugin {
10+
return {
11+
name: 'zhtyp-guide-markdown-transform',
12+
enforce: 'pre',
13+
async transform(src: string, id: string): Promise<string | null> {
14+
const filepath: string = normalizePath(id);
15+
16+
const faqPattern = /\/FAQ\/[^.]+\.md$/;
17+
if (!faqPattern.test(filepath)) {
18+
return null;
19+
}
20+
21+
// There is no layout slot in VitePress's default theme that can add contents within `<main>`.
22+
// As a result, we have to transform the markdown source.
23+
src += `
24+
<script setup>
25+
import SeeAlso from "@theme/SeeAlso.vue"
26+
</script>
27+
<SeeAlso />
28+
`;
29+
30+
return src;
31+
},
32+
};
33+
}

docs/.vitepress/theme/Layout.vue

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,13 @@ const typstVersion = computed(() => {
5858
</div>
5959
</template>
6060
<template #doc-footer-before>
61+
<!-- <hr> -->
62+
<!-- <section class="vp-doc" style="font-size: 0.8em; color: var(--vp-c-text-2);"> -->
63+
<!-- <h2>另请参见</h2> -->
64+
<!-- <ul> -->
65+
<!-- <li><a href="https://example.com" target="_blank">AA</a></li> -->
66+
<!-- </ul> -->
67+
<!-- </section> -->
6168
<!-- 与 <VPDocFooterLastUpdated> 并列 -->
6269
<p
6370
v-if="typstVersion !== null"

docs/.vitepress/theme/SeeAlso.vue

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
<script setup lang="ts">
2+
import { useData, useRoute } from 'vitepress';
3+
import { computed } from 'vue';
4+
import { removePrefix } from '../util';
5+
import { data, type Link, type RelativePath } from './see_also.data';
6+
7+
const route = useRoute();
8+
const { site, frontmatter } = useData();
9+
10+
const links = computed<Link[] | null>(() => {
11+
const path: RelativePath = `/${removePrefix(route.path, site.value.base)}`;
12+
return data.linksIndex[path] ?? null;
13+
});
14+
</script>
15+
<template>
16+
<section v-if="links">
17+
<h2>另请参见</h2>
18+
<pre>{{ JSON.stringify(frontmatter.value, null, 2) }}</pre>
19+
<ul>
20+
<li v-for="{ url, title } in links">
21+
<a :href="url" target="_blank">{{ title }}</a>
22+
</li>
23+
</ul>
24+
</section>
25+
</template>
Lines changed: 232 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,232 @@
1+
/**
2+
* Generate data for `<SeeAlso>`.
3+
*
4+
* # Data sources
5+
*
6+
* - the index for clreq-gap for typst
7+
* - `links: (string | { url: string, title?: string })[]` in page frontmatter
8+
*
9+
* # Implementation details
10+
*
11+
* When modifying the page frontmatter, the loader may be called repeatedly. Therefore, network requests are cached.
12+
*
13+
* For maximal compatibility, features requires network or GitHub CLI are disabled granularly if not available.
14+
*
15+
* @module
16+
*/
17+
18+
import { createContentLoader, type ContentData } from 'vitepress';
19+
import { removePrefix } from '../util.ts';
20+
21+
type LinkInFrontmatter = string | { url: string; title?: string };
22+
23+
export type Link = {
24+
url: string;
25+
title: string;
26+
};
27+
/** The route URL path relative to the site base, e.g., `/FAQ/cite-flying.html`. */
28+
export type RelativePath = `/${string}`;
29+
30+
export interface Data {
31+
linksIndex: Record<RelativePath, Link[]>;
32+
}
33+
34+
declare const data: Data;
35+
export { data };
36+
37+
export default createContentLoader('FAQ/*.md', {
38+
async transform(rawData): Promise<Data> {
39+
const linksIndex: Record<RelativePath, Link[]> = {};
40+
41+
// Build the links index from data sources
42+
for (const data of [
43+
parseGapIndex(await fetchGapIndex()),
44+
parseFrontmatter(rawData),
45+
]) {
46+
for (const [path, links] of data) {
47+
if (!linksIndex[path]) {
48+
linksIndex[path] = [];
49+
}
50+
linksIndex[path].push(...links);
51+
}
52+
}
53+
54+
await resolveAutoTitle(linksIndex);
55+
56+
return { linksIndex };
57+
},
58+
});
59+
60+
const _AUTO_TITLE = '⟨AUTO_TITLE⟩';
61+
62+
/** Resolve `AUTO_TITLE` in place. */
63+
async function resolveAutoTitle(
64+
linksIndex: Record<RelativePath, Link[]>,
65+
): Promise<void> {
66+
const targets = Object.values(linksIndex)
67+
.flat()
68+
.filter(({ title }) => title === _AUTO_TITLE);
69+
70+
// TODO: Improve titles
71+
// Example: wrong alignment of cases · Issue #2562 · typst/typst (Not planned)
72+
73+
// Last resort (without network access)
74+
for (const link of targets) {
75+
if (link.title !== _AUTO_TITLE) {
76+
continue;
77+
}
78+
79+
if (link.url.startsWith('https://github.com/')) {
80+
const match = removePrefix(link.url, 'https://github.com/').match(
81+
/^([^/]+\/[^/]+)\/(pull|issues)\/(\d+)\/?$/i,
82+
);
83+
if (match) {
84+
const [, repo, kind, num] = match;
85+
const type = kind === 'pull' ? 'pull request' : 'issue';
86+
link.title = `${removePrefix(repo, 'typst/')}#${num} (${type})`;
87+
continue;
88+
}
89+
}
90+
91+
link.title = removePrefix(link.url, 'https://');
92+
}
93+
}
94+
95+
// TODO: Replace this with https://typst-doc-cn.github.io/clreq/
96+
const GAP_BASE: `https://${string}/` =
97+
'https://deploy-preview-68--clreq-gap-typst.netlify.app/';
98+
99+
// Simplified types for the gap index
100+
101+
type GapIndex = {
102+
version: '2025-11-24';
103+
sections: Section[];
104+
};
105+
type Section = {
106+
title: Babel;
107+
level: number;
108+
id?: string;
109+
priority: GeneralPriority;
110+
links: (
111+
| ({ type: 'issue' | 'pull' } & RepoNum)
112+
| ({ type: 'workaround' } & WorkaroundMeta)
113+
)[];
114+
};
115+
116+
type Priority = 'ok' | 'advanced' | 'basic' | 'broken' | 'tbd' | 'na';
117+
type GeneralPriority = Priority | '(inherited)';
118+
119+
type Babel = { en: string; 'zh-Hans': string };
120+
121+
type WorkaroundMeta = {
122+
dest: string;
123+
note: string | null;
124+
};
125+
type RepoNum = {
126+
repo: string;
127+
num: string;
128+
};
129+
130+
function formatPriority(priority: GeneralPriority): string {
131+
switch (priority) {
132+
case 'ok':
133+
return 'OK';
134+
case 'tbd':
135+
return 'To be done';
136+
case 'na':
137+
return 'Not applicable';
138+
case '(inherited)':
139+
return priority;
140+
default:
141+
return priority[0].toUpperCase() + priority.slice(1);
142+
}
143+
}
144+
145+
function formatWorkaround({ dest, note }: WorkaroundMeta): string {
146+
// Adapted from https://github.com/typst-doc-cn/clreq/blob/2669b82d465927e560b9125e698c32e9d2c2a213/typ/util.typ#L84
147+
148+
const humanDest = dest.startsWith('https://typst.app/universe/package/')
149+
? `universe/${removePrefix(dest, 'https://typst.app/universe/package/')}`
150+
: removePrefix(removePrefix(dest, 'https://').split('.')[0], 'typst-');
151+
152+
return note ? `${note} (${humanDest})` : humanDest;
153+
}
154+
155+
let _GAP_INDEX_CACHE: GapIndex | null = null;
156+
157+
/** Fetch the index for clreq-gap for typst. */
158+
async function fetchGapIndex(): Promise<GapIndex> {
159+
if (_GAP_INDEX_CACHE !== null) {
160+
return _GAP_INDEX_CACHE;
161+
}
162+
163+
const gapIndex = await (await fetch(`${GAP_BASE}index.json`)).json();
164+
_GAP_INDEX_CACHE = gapIndex;
165+
return gapIndex;
166+
}
167+
168+
/** Parse the index for clreq-gap for typst. */
169+
function* parseGapIndex(gapIndex: GapIndex): Generator<[RelativePath, Link[]]> {
170+
/** The canonical site base used in the gap index. Might be different from the deployed base. */
171+
const siteBase: `https://${string}/` =
172+
'https://typst-doc-cn.github.io/guide/';
173+
174+
for (const section of gapIndex.sections) {
175+
const relevant = section.links.filter(
176+
(l) => l.type === 'workaround' && l.dest.startsWith(siteBase),
177+
) as ({ type: 'workaround' } & WorkaroundMeta)[];
178+
179+
for (const workaround of relevant) {
180+
// Drop base and anchor hash
181+
const path: RelativePath = `/${removePrefix(workaround.dest, siteBase).replace(/#.+$/, '')}`;
182+
183+
yield [
184+
path,
185+
[
186+
// Link to the section itself
187+
{
188+
title: `差距分析:${section.title['zh-Hans']}(级别:${formatPriority(section.priority)})`,
189+
url: `${GAP_BASE}#${section.id}`,
190+
},
191+
192+
// Links in the section
193+
...section.links.flatMap((l) => {
194+
if (l.type === 'workaround') {
195+
return l.dest === workaround.dest
196+
? [] // Skip the workaround itself
197+
: {
198+
url: l.dest,
199+
title: `相关解决方案:${formatWorkaround(l)}`,
200+
};
201+
}
202+
return {
203+
url: `https://github.com/${l.repo}/${l.type === 'pull' ? 'pull' : 'issues'}/${l.num}`,
204+
title: _AUTO_TITLE,
205+
};
206+
}),
207+
],
208+
];
209+
}
210+
}
211+
}
212+
213+
/** Parse `links` in page frontmatter. */
214+
function* parseFrontmatter(
215+
rawData: ContentData[],
216+
): Generator<[RelativePath, Link[]]> {
217+
for (const { url, frontmatter } of rawData) {
218+
if (frontmatter.links) {
219+
const path = url as RelativePath;
220+
const links = (frontmatter.links as LinkInFrontmatter[]).map((l) =>
221+
typeof l === 'string'
222+
? { url: l, title: _AUTO_TITLE }
223+
: {
224+
url: l.url,
225+
title: l.title || _AUTO_TITLE,
226+
},
227+
);
228+
229+
yield [path, links];
230+
}
231+
}
232+
}

docs/.vitepress/typst_render.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,8 @@ import { env } from 'node:process';
77
import _which from 'which';
88

99
import TEMPLATE from './typst_template';
10-
import { prettify, readToString, removePrefix } from './util';
10+
import { prettify, removePrefix } from './util';
11+
import { readToString } from './util_node';
1112

1213
const which = (cmd: string): Promise<string | null> =>
1314
_which(cmd)
@@ -283,7 +284,7 @@ function TypstRender(md: MarkdownIt) {
283284
compiling,
284285
{
285286
path: `docs/${env.relativePath}`,
286-
// 加四是因为 front matter
287+
// TODO: 目前加四是因为 front matter,最好改成 front matter 实际行数
287288
line_begin: token.map ? token.map[0] + 4 : undefined,
288289
},
289290
executable,

docs/.vitepress/util.ts

Lines changed: 0 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,3 @@
1-
import { readFileSync } from 'node:fs';
2-
3-
/**
4-
* Read a file to string, return `undefined` if not existed
5-
*/
6-
export function readToString(file: string): string | undefined {
7-
try {
8-
return readFileSync(file, { encoding: 'utf-8' });
9-
} catch (err) {
10-
// If not existed
11-
if (err.code === 'ENOENT') {
12-
return;
13-
} else {
14-
throw err;
15-
}
16-
}
17-
}
18-
191
/**
202
* 格式化`doc`
213
* - 每行前加`indent`

docs/.vitepress/util_node.ts

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
import { readFileSync } from 'fs';
2+
3+
/**
4+
* Read a file to string, return `undefined` if not existed
5+
*/
6+
7+
export function readToString(file: string): string | undefined {
8+
try {
9+
return readFileSync(file, { encoding: 'utf-8' });
10+
} catch (err) {
11+
// If not existed
12+
if (err.code === 'ENOENT') {
13+
return;
14+
} else {
15+
throw err;
16+
}
17+
}
18+
}

docs/FAQ/bib-markup.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,12 @@
11
---
22
tags: bib
3+
links:
4+
- https://github.com/typst/typst/issues/1975
35
---
46

57
# 参考文献 title 里如何加上下标等样式?
68

7-
目前没有内置支持,因为没设计好接口。[#1975](https://github.com/typst/typst/issues/1975)
9+
目前没有内置支持,因为没设计好接口。
810

911
## 法一:自己定义标记,`show regex`
1012

0 commit comments

Comments
 (0)