create-lumfall 1.0.1 → 1.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +24 -17
- package/cli.js +177 -22
- package/package.json +5 -7
- package/templates/basic/README.md +0 -113
- package/templates/basic/app/controller/demo.js +0 -28
- package/templates/basic/app/extend/README.md +0 -3
- package/templates/basic/app/middleware/README.md +0 -2
- package/templates/basic/app/middleware.js +0 -13
- package/templates/basic/app/pages/home/entry.home.js +0 -4
- package/templates/basic/app/pages/home/home.vue +0 -164
- package/templates/basic/app/router/demo.js +0 -17
- package/templates/basic/app/router-schema/demo.js +0 -29
- package/templates/basic/app/service/demo.js +0 -51
- package/templates/basic/app/webpack.config.js +0 -4
- package/templates/basic/build.js +0 -7
- package/templates/basic/config/config.beta.js +0 -4
- package/templates/basic/config/config.default.js +0 -32
- package/templates/basic/config/config.local.js +0 -4
- package/templates/basic/config/config.prod.js +0 -6
- package/templates/basic/package.json +0 -31
- package/templates/basic/server.js +0 -26
- package/templates/document/README.md +0 -58
- package/templates/document/app/extend/README.md +0 -6
- package/templates/document/app/middleware/README.md +0 -2
- package/templates/document/app/middleware.js +0 -2
- package/templates/document/app/pages/docs/assets/docs-logo.svg +0 -5
- package/templates/document/app/pages/docs/components/doc-layout.vue +0 -144
- package/templates/document/app/pages/docs/components/doc-navbar.vue +0 -103
- package/templates/document/app/pages/docs/components/doc-search.vue +0 -131
- package/templates/document/app/pages/docs/components/doc-sidebar.vue +0 -29
- package/templates/document/app/pages/docs/components/doc-toc.vue +0 -21
- package/templates/document/app/pages/docs/content.js +0 -28
- package/templates/document/app/pages/docs/docs-config.js +0 -68
- package/templates/document/app/pages/docs/docs.vue +0 -24
- package/templates/document/app/pages/docs/entry.docs.js +0 -26
- package/templates/document/app/pages/docs/markdown/highlight.js +0 -30
- package/templates/document/app/pages/docs/markdown/index.js +0 -129
- package/templates/document/app/pages/docs/search.js +0 -142
- package/templates/document/app/pages/docs/styles/docs.less +0 -1091
- package/templates/document/app/pages/docs/styles/vars.less +0 -87
- package/templates/document/app/pages/docs/theme.js +0 -47
- package/templates/document/app/pages/docs/utils.js +0 -58
- package/templates/document/app/pages/docs/views/doc-home.vue +0 -56
- package/templates/document/app/pages/docs/views/doc-page.vue +0 -147
- package/templates/document/app/webpack.config.js +0 -15
- package/templates/document/build.js +0 -6
- package/templates/document/config/config.beta.js +0 -2
- package/templates/document/config/config.default.js +0 -9
- package/templates/document/config/config.local.js +0 -2
- package/templates/document/config/config.prod.js +0 -2
- package/templates/document/docs/guide/example.md +0 -41
- package/templates/document/docs/guide/introduction.md +0 -31
- package/templates/document/package.json +0 -36
- package/templates/document/scripts/build-static.js +0 -129
- package/templates/document/server.js +0 -13
|
@@ -1,103 +0,0 @@
|
|
|
1
|
-
<template>
|
|
2
|
-
<header class="doc-navbar">
|
|
3
|
-
<div class="doc-navbar-left">
|
|
4
|
-
<button
|
|
5
|
-
v-if="showSidebarToggle"
|
|
6
|
-
class="doc-navbar-burger"
|
|
7
|
-
type="button"
|
|
8
|
-
aria-label="打开目录"
|
|
9
|
-
@click="$emit('toggle-sidebar')"
|
|
10
|
-
>
|
|
11
|
-
<icon-menu />
|
|
12
|
-
</button>
|
|
13
|
-
|
|
14
|
-
<router-link class="doc-navbar-brand" to="/view/docs">
|
|
15
|
-
<img class="doc-navbar-logo" :src="logo" alt="logo" />
|
|
16
|
-
<span class="doc-navbar-title">{{ site.title }}</span>
|
|
17
|
-
</router-link>
|
|
18
|
-
|
|
19
|
-
<nav class="doc-navbar-nav">
|
|
20
|
-
<template v-for="item in nav" :key="item.text">
|
|
21
|
-
<router-link
|
|
22
|
-
v-if="item.path"
|
|
23
|
-
:to="item.path"
|
|
24
|
-
class="doc-navbar-link"
|
|
25
|
-
:class="{ active: isActive(item) }"
|
|
26
|
-
>
|
|
27
|
-
{{ item.text }}
|
|
28
|
-
</router-link>
|
|
29
|
-
<a
|
|
30
|
-
v-else
|
|
31
|
-
:href="item.link"
|
|
32
|
-
target="_blank"
|
|
33
|
-
rel="noopener noreferrer"
|
|
34
|
-
class="doc-navbar-link"
|
|
35
|
-
>
|
|
36
|
-
{{ item.text }}
|
|
37
|
-
</a>
|
|
38
|
-
</template>
|
|
39
|
-
</nav>
|
|
40
|
-
</div>
|
|
41
|
-
|
|
42
|
-
<div class="doc-navbar-right">
|
|
43
|
-
<button class="doc-search-trigger" type="button" @click="$emit('open-search')">
|
|
44
|
-
<icon-search />
|
|
45
|
-
<span class="doc-search-trigger-text">搜索文档</span>
|
|
46
|
-
<kbd>Ctrl K</kbd>
|
|
47
|
-
</button>
|
|
48
|
-
|
|
49
|
-
<button
|
|
50
|
-
class="doc-icon-btn"
|
|
51
|
-
type="button"
|
|
52
|
-
:aria-label="isDark ? '切换到亮色' : '切换到暗色'"
|
|
53
|
-
@click="handleToggleTheme"
|
|
54
|
-
>
|
|
55
|
-
<icon-sun-fill v-if="isDark" />
|
|
56
|
-
<icon-moon-fill v-else />
|
|
57
|
-
</button>
|
|
58
|
-
|
|
59
|
-
<a
|
|
60
|
-
v-if="site.repo"
|
|
61
|
-
class="doc-icon-btn"
|
|
62
|
-
:href="site.repo"
|
|
63
|
-
target="_blank"
|
|
64
|
-
rel="noopener noreferrer"
|
|
65
|
-
aria-label="GitHub 仓库"
|
|
66
|
-
>
|
|
67
|
-
<icon-github />
|
|
68
|
-
</a>
|
|
69
|
-
</div>
|
|
70
|
-
</header>
|
|
71
|
-
</template>
|
|
72
|
-
|
|
73
|
-
<script setup>
|
|
74
|
-
import { computed, ref } from "vue";
|
|
75
|
-
import { useRoute } from "vue-router";
|
|
76
|
-
import docsConfig from "../docs-config";
|
|
77
|
-
import { toggleTheme } from "../theme";
|
|
78
|
-
import logo from "../assets/docs-logo.svg";
|
|
79
|
-
|
|
80
|
-
defineProps({
|
|
81
|
-
showSidebarToggle: { type: Boolean, default: false },
|
|
82
|
-
});
|
|
83
|
-
|
|
84
|
-
defineEmits(["toggle-sidebar", "open-search"]);
|
|
85
|
-
|
|
86
|
-
const route = useRoute();
|
|
87
|
-
const site = docsConfig.site;
|
|
88
|
-
const nav = docsConfig.nav;
|
|
89
|
-
const isDark = ref(document.body.getAttribute("arco-theme") === "dark");
|
|
90
|
-
|
|
91
|
-
const handleToggleTheme = () => {
|
|
92
|
-
isDark.value = toggleTheme() === "dark";
|
|
93
|
-
};
|
|
94
|
-
|
|
95
|
-
// 导航高亮:按 path 的前三级(/view/docs/<section>)匹配当前路由
|
|
96
|
-
const isActive = (item) => {
|
|
97
|
-
if (!item.path) {
|
|
98
|
-
return false;
|
|
99
|
-
}
|
|
100
|
-
const section = item.path.split("/").slice(0, 4).join("/");
|
|
101
|
-
return route.path === section || route.path.startsWith(`${section}/`);
|
|
102
|
-
};
|
|
103
|
-
</script>
|
|
@@ -1,131 +0,0 @@
|
|
|
1
|
-
<template>
|
|
2
|
-
<Teleport to="body">
|
|
3
|
-
<Transition name="doc-fade">
|
|
4
|
-
<div v-if="modelValue" class="doc-search-mask" @click.self="close">
|
|
5
|
-
<div class="doc-search-panel">
|
|
6
|
-
<div class="doc-search-input-row">
|
|
7
|
-
<icon-search class="doc-search-icon" />
|
|
8
|
-
<input
|
|
9
|
-
ref="inputRef"
|
|
10
|
-
v-model="keyword"
|
|
11
|
-
class="doc-search-input"
|
|
12
|
-
type="text"
|
|
13
|
-
placeholder="搜索文档,如:配置 / 路由 / 插件"
|
|
14
|
-
@keydown="onKeydown"
|
|
15
|
-
/>
|
|
16
|
-
<kbd class="doc-search-kbd">ESC</kbd>
|
|
17
|
-
</div>
|
|
18
|
-
|
|
19
|
-
<div class="doc-search-results">
|
|
20
|
-
<template v-if="keyword.trim()">
|
|
21
|
-
<div v-if="!results.length" class="doc-search-empty">
|
|
22
|
-
没有找到与「{{ keyword }}」相关的内容
|
|
23
|
-
</div>
|
|
24
|
-
<a
|
|
25
|
-
v-for="(item, i) in results"
|
|
26
|
-
:key="`${item.docPath}-${item.anchor}-${i}`"
|
|
27
|
-
class="doc-search-item"
|
|
28
|
-
:class="{ active: i === activeIndex }"
|
|
29
|
-
@click="go(item)"
|
|
30
|
-
@mouseenter="activeIndex = i"
|
|
31
|
-
>
|
|
32
|
-
<div class="doc-search-item-title" v-html="item.titleHtml"></div>
|
|
33
|
-
<div v-if="item.heading" class="doc-search-item-heading">
|
|
34
|
-
<icon-right /> {{ item.heading }}
|
|
35
|
-
</div>
|
|
36
|
-
<div
|
|
37
|
-
v-if="item.snippet"
|
|
38
|
-
class="doc-search-item-snippet"
|
|
39
|
-
v-html="item.snippet"
|
|
40
|
-
></div>
|
|
41
|
-
</a>
|
|
42
|
-
</template>
|
|
43
|
-
<div v-else class="doc-search-empty">
|
|
44
|
-
输入关键词开始搜索,支持标题与正文匹配
|
|
45
|
-
</div>
|
|
46
|
-
</div>
|
|
47
|
-
|
|
48
|
-
<div class="doc-search-footer">
|
|
49
|
-
<span><kbd>↑</kbd><kbd>↓</kbd> 选择</span>
|
|
50
|
-
<span><kbd>Enter</kbd> 打开</span>
|
|
51
|
-
<span><kbd>ESC</kbd> 关闭</span>
|
|
52
|
-
</div>
|
|
53
|
-
</div>
|
|
54
|
-
</div>
|
|
55
|
-
</Transition>
|
|
56
|
-
</Teleport>
|
|
57
|
-
</template>
|
|
58
|
-
|
|
59
|
-
<script setup>
|
|
60
|
-
import { computed, nextTick, ref, watch } from "vue";
|
|
61
|
-
import { useRouter } from "vue-router";
|
|
62
|
-
import docsConfig from "../docs-config";
|
|
63
|
-
import { searchDocs } from "../search";
|
|
64
|
-
|
|
65
|
-
const props = defineProps({
|
|
66
|
-
modelValue: { type: Boolean, default: false },
|
|
67
|
-
});
|
|
68
|
-
const emit = defineEmits(["update:modelValue"]);
|
|
69
|
-
|
|
70
|
-
const router = useRouter();
|
|
71
|
-
const keyword = ref("");
|
|
72
|
-
const activeIndex = ref(0);
|
|
73
|
-
const inputRef = ref(null);
|
|
74
|
-
|
|
75
|
-
const results = computed(() => {
|
|
76
|
-
const list = searchDocs(keyword.value, docsConfig);
|
|
77
|
-
if (activeIndex.value >= list.length) {
|
|
78
|
-
activeIndex.value = 0;
|
|
79
|
-
}
|
|
80
|
-
return list;
|
|
81
|
-
});
|
|
82
|
-
|
|
83
|
-
watch(
|
|
84
|
-
() => props.modelValue,
|
|
85
|
-
(open) => {
|
|
86
|
-
if (open) {
|
|
87
|
-
keyword.value = "";
|
|
88
|
-
activeIndex.value = 0;
|
|
89
|
-
nextTick(() => inputRef.value && inputRef.value.focus());
|
|
90
|
-
}
|
|
91
|
-
}
|
|
92
|
-
);
|
|
93
|
-
|
|
94
|
-
watch(keyword, () => {
|
|
95
|
-
activeIndex.value = 0;
|
|
96
|
-
});
|
|
97
|
-
|
|
98
|
-
const close = () => emit("update:modelValue", false);
|
|
99
|
-
|
|
100
|
-
const go = (item) => {
|
|
101
|
-
close();
|
|
102
|
-
const path = `/view/docs/${item.docPath}`;
|
|
103
|
-
if (item.anchor) {
|
|
104
|
-
router.push({ path, hash: `#${item.anchor}` });
|
|
105
|
-
} else {
|
|
106
|
-
router.push(path);
|
|
107
|
-
}
|
|
108
|
-
};
|
|
109
|
-
|
|
110
|
-
const onKeydown = (e) => {
|
|
111
|
-
if (e.key === "Escape") {
|
|
112
|
-
close();
|
|
113
|
-
} else if (e.key === "ArrowDown") {
|
|
114
|
-
e.preventDefault();
|
|
115
|
-
activeIndex.value = (activeIndex.value + 1) % Math.max(results.value.length, 1);
|
|
116
|
-
} else if (e.key === "ArrowUp") {
|
|
117
|
-
e.preventDefault();
|
|
118
|
-
activeIndex.value =
|
|
119
|
-
(activeIndex.value - 1 + results.value.length) %
|
|
120
|
-
Math.max(results.value.length, 1);
|
|
121
|
-
} else if (e.key === "Enter") {
|
|
122
|
-
const item = results.value[activeIndex.value];
|
|
123
|
-
if (item) {
|
|
124
|
-
go(item);
|
|
125
|
-
}
|
|
126
|
-
}
|
|
127
|
-
};
|
|
128
|
-
|
|
129
|
-
// 暴露给全局快捷键:layout 或页面可通过 ref 调 open()(本模板用 v-model 控制即可)
|
|
130
|
-
defineExpose({ close });
|
|
131
|
-
</script>
|
|
@@ -1,29 +0,0 @@
|
|
|
1
|
-
<template>
|
|
2
|
-
<aside class="doc-sidebar">
|
|
3
|
-
<div v-for="group in sidebar" :key="group.text" class="doc-sidebar-group">
|
|
4
|
-
<div class="doc-sidebar-group-title">{{ group.text }}</div>
|
|
5
|
-
<router-link
|
|
6
|
-
v-for="item in group.items"
|
|
7
|
-
:key="item.path"
|
|
8
|
-
:to="item.path"
|
|
9
|
-
class="doc-sidebar-link"
|
|
10
|
-
:class="{ active: isActive(item.path) }"
|
|
11
|
-
@click="$emit('navigate')"
|
|
12
|
-
>
|
|
13
|
-
{{ item.text }}
|
|
14
|
-
</router-link>
|
|
15
|
-
</div>
|
|
16
|
-
</aside>
|
|
17
|
-
</template>
|
|
18
|
-
|
|
19
|
-
<script setup>
|
|
20
|
-
import { useRoute } from "vue-router";
|
|
21
|
-
import docsConfig from "../docs-config";
|
|
22
|
-
|
|
23
|
-
defineEmits(["navigate"]);
|
|
24
|
-
|
|
25
|
-
const route = useRoute();
|
|
26
|
-
const sidebar = docsConfig.sidebar;
|
|
27
|
-
|
|
28
|
-
const isActive = (path) => route.path === path || route.path === `${path}/`;
|
|
29
|
-
</script>
|
|
@@ -1,21 +0,0 @@
|
|
|
1
|
-
<template>
|
|
2
|
-
<div v-if="items.length" class="doc-toc">
|
|
3
|
-
<div class="doc-toc-title">页面内容</div>
|
|
4
|
-
<a
|
|
5
|
-
v-for="item in items"
|
|
6
|
-
:key="item.id"
|
|
7
|
-
:href="`#${item.id}`"
|
|
8
|
-
class="doc-toc-link"
|
|
9
|
-
:class="[`level-${item.level}`, { active: item.id === activeId }]"
|
|
10
|
-
>
|
|
11
|
-
{{ item.text }}
|
|
12
|
-
</a>
|
|
13
|
-
</div>
|
|
14
|
-
</template>
|
|
15
|
-
|
|
16
|
-
<script setup>
|
|
17
|
-
defineProps({
|
|
18
|
-
items: { type: Array, default: () => [] },
|
|
19
|
-
activeId: { type: String, default: "" },
|
|
20
|
-
});
|
|
21
|
-
</script>
|
|
@@ -1,28 +0,0 @@
|
|
|
1
|
-
// markdown 文档内容加载器。
|
|
2
|
-
// 约定:文档放在项目根目录 docs/ 下(相对本文件 ../../../docs),
|
|
3
|
-
// 构建时全部按原始文本打进产物,页面按路径读取并渲染。
|
|
4
|
-
// 要调整内容目录位置时,只需要改 require.context 的第一个参数。
|
|
5
|
-
const context = require.context("../../../docs", true, /\.md$/);
|
|
6
|
-
|
|
7
|
-
const moduleMap = {};
|
|
8
|
-
|
|
9
|
-
context.keys().forEach((key) => {
|
|
10
|
-
// key 形如 "./guide/introduction.md" → "guide/introduction"
|
|
11
|
-
const docPath = key.replace(/^\.\//, "").replace(/\.md$/, "");
|
|
12
|
-
const mod = context(key);
|
|
13
|
-
moduleMap[docPath] = typeof mod === "string" ? mod : mod.default ?? mod;
|
|
14
|
-
});
|
|
15
|
-
|
|
16
|
-
/**
|
|
17
|
-
* 按 docs/ 相对路径取 markdown 原文
|
|
18
|
-
* @param {string} docPath 例如 "guide/introduction"
|
|
19
|
-
* @returns {string|undefined}
|
|
20
|
-
*/
|
|
21
|
-
export function getDocSource(docPath) {
|
|
22
|
-
return moduleMap[docPath];
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
/** 全部文档:{ "guide/introduction": "# ...", ... } */
|
|
26
|
-
export function getAllDocs() {
|
|
27
|
-
return moduleMap;
|
|
28
|
-
}
|
|
@@ -1,68 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* 文档站配置 —— 使用本模板时主要改这个文件。
|
|
3
|
-
*
|
|
4
|
-
* 换成自己的站点只需两步:
|
|
5
|
-
* 1. 改本文件的 site / nav / sidebar / hero / footer
|
|
6
|
-
* 2. 把 docs/ 目录下的 markdown 换成自己的内容(目录结构随意,两级以内最佳)
|
|
7
|
-
*/
|
|
8
|
-
|
|
9
|
-
// 站内文档的 URL 前缀 = /view/<页面目录名>
|
|
10
|
-
const DOCS_BASE = "/view/docs";
|
|
11
|
-
|
|
12
|
-
const doc = (path) => `${DOCS_BASE}/${path}`;
|
|
13
|
-
|
|
14
|
-
export default {
|
|
15
|
-
// 站点信息
|
|
16
|
-
site: {
|
|
17
|
-
title: "Your Docs",
|
|
18
|
-
description: "基于 lumfall 的技术文档站",
|
|
19
|
-
// 右上角仓库链接,不需要就置空
|
|
20
|
-
repo: "",
|
|
21
|
-
},
|
|
22
|
-
|
|
23
|
-
// 顶部导航:path 为站内路由(高亮按第一级 section 匹配),link 为外链
|
|
24
|
-
nav: [{ text: "文档", path: doc("guide/introduction") }],
|
|
25
|
-
|
|
26
|
-
// 侧边栏分组。items 的 path 必须对应 docs/ 下真实存在的 markdown 文件
|
|
27
|
-
// (例如 doc("guide/introduction") 对应 docs/guide/introduction.md)
|
|
28
|
-
sidebar: [
|
|
29
|
-
{
|
|
30
|
-
text: "开始",
|
|
31
|
-
items: [
|
|
32
|
-
{ text: "介绍", path: doc("guide/introduction") },
|
|
33
|
-
{ text: "示例页面", path: doc("guide/example") },
|
|
34
|
-
],
|
|
35
|
-
},
|
|
36
|
-
],
|
|
37
|
-
|
|
38
|
-
// 首页 hero
|
|
39
|
-
hero: {
|
|
40
|
-
name: "Your Docs",
|
|
41
|
-
tagline: "基于 lumfall 的技术文档站模板",
|
|
42
|
-
text: "把 docs/ 目录下的 markdown 换成你的内容,再修改本文件(app/pages/docs/docs-config.js),即可拥有完整的文档站点。",
|
|
43
|
-
actions: [{ text: "开始使用", path: doc("guide/introduction"), theme: "brand" }],
|
|
44
|
-
features: [
|
|
45
|
-
{
|
|
46
|
-
icon: "📝",
|
|
47
|
-
title: "Markdown 驱动",
|
|
48
|
-
details:
|
|
49
|
-
"docs/ 目录即内容,支持相对链接、标题锚点、提示块、代码高亮与一键复制。",
|
|
50
|
-
},
|
|
51
|
-
{
|
|
52
|
-
icon: "🔍",
|
|
53
|
-
title: "站内搜索",
|
|
54
|
-
details: "Ctrl/Cmd + K 唤起,标题与正文加权匹配,纯前端实现无需后端。",
|
|
55
|
-
},
|
|
56
|
-
{
|
|
57
|
-
icon: "🌗",
|
|
58
|
-
title: "亮暗主题",
|
|
59
|
-
details: "跟随系统并支持手动切换,样式由 CSS 变量驱动,改一个变量即换主题色。",
|
|
60
|
-
},
|
|
61
|
-
],
|
|
62
|
-
},
|
|
63
|
-
|
|
64
|
-
// 首页与文档页底部
|
|
65
|
-
footer: {
|
|
66
|
-
text: "基于 lumfall-document 模板构建",
|
|
67
|
-
},
|
|
68
|
-
};
|
|
@@ -1,24 +0,0 @@
|
|
|
1
|
-
<template>
|
|
2
|
-
<a-config-provider :locale="zhCN">
|
|
3
|
-
<router-view />
|
|
4
|
-
</a-config-provider>
|
|
5
|
-
</template>
|
|
6
|
-
|
|
7
|
-
<script setup>
|
|
8
|
-
import { onMounted } from "vue";
|
|
9
|
-
import zhCN from "@arco-design/web-vue/es/locale/lang/zh-cn";
|
|
10
|
-
import favicon from "./assets/docs-logo.svg";
|
|
11
|
-
import "./styles/vars.less";
|
|
12
|
-
import "./styles/docs.less";
|
|
13
|
-
|
|
14
|
-
onMounted(() => {
|
|
15
|
-
// 框架通用模板没有 favicon 位,这里在运行时注入
|
|
16
|
-
if (!document.querySelector('link[rel="icon"]')) {
|
|
17
|
-
const link = document.createElement("link");
|
|
18
|
-
link.rel = "icon";
|
|
19
|
-
link.type = "image/svg+xml";
|
|
20
|
-
link.href = favicon;
|
|
21
|
-
document.head.appendChild(link);
|
|
22
|
-
}
|
|
23
|
-
});
|
|
24
|
-
</script>
|
|
@@ -1,26 +0,0 @@
|
|
|
1
|
-
import boot from "$lumfallBoot";
|
|
2
|
-
import Docs from "./docs.vue";
|
|
3
|
-
import { initTheme } from "./theme";
|
|
4
|
-
|
|
5
|
-
// 尽早应用主题(localStorage / 系统偏好),避免首屏闪白
|
|
6
|
-
initTheme();
|
|
7
|
-
|
|
8
|
-
// 文档站是单个页面(app/pages/docs),站内路由:
|
|
9
|
-
// /view/docs 首页(hero + 特性卡片)
|
|
10
|
-
// /view/docs/<path> 文档页(path 对应 docs/<path>.md)
|
|
11
|
-
// 页面目录名决定挂载路径(本页是 docs);要改名时同步调整 docs-config.js 里的
|
|
12
|
-
// DOCS_BASE 与上面两个路由 path。
|
|
13
|
-
boot(Docs, {
|
|
14
|
-
routes: [
|
|
15
|
-
{
|
|
16
|
-
path: "/view/docs",
|
|
17
|
-
name: "doc-home",
|
|
18
|
-
component: () => import("./views/doc-home.vue"),
|
|
19
|
-
},
|
|
20
|
-
{
|
|
21
|
-
path: "/view/docs/:path(.*)",
|
|
22
|
-
name: "doc-page",
|
|
23
|
-
component: () => import("./views/doc-page.vue"),
|
|
24
|
-
},
|
|
25
|
-
],
|
|
26
|
-
});
|
|
@@ -1,30 +0,0 @@
|
|
|
1
|
-
// highlight.js 按需注册:只打包文档站实际用到的语言,控制 vendor 体积。
|
|
2
|
-
import hljs from "highlight.js/lib/core";
|
|
3
|
-
import javascript from "highlight.js/lib/languages/javascript";
|
|
4
|
-
import typescript from "highlight.js/lib/languages/typescript";
|
|
5
|
-
import xml from "highlight.js/lib/languages/xml";
|
|
6
|
-
import css from "highlight.js/lib/languages/css";
|
|
7
|
-
import less from "highlight.js/lib/languages/less";
|
|
8
|
-
import json from "highlight.js/lib/languages/json";
|
|
9
|
-
import bash from "highlight.js/lib/languages/bash";
|
|
10
|
-
import yaml from "highlight.js/lib/languages/yaml";
|
|
11
|
-
import markdown from "highlight.js/lib/languages/markdown";
|
|
12
|
-
import plaintext from "highlight.js/lib/languages/plaintext";
|
|
13
|
-
|
|
14
|
-
[
|
|
15
|
-
["javascript", javascript],
|
|
16
|
-
["typescript", typescript],
|
|
17
|
-
["xml", xml],
|
|
18
|
-
["css", css],
|
|
19
|
-
["less", less],
|
|
20
|
-
["json", json],
|
|
21
|
-
["bash", bash],
|
|
22
|
-
["yaml", yaml],
|
|
23
|
-
["markdown", markdown],
|
|
24
|
-
["plaintext", plaintext],
|
|
25
|
-
].forEach(([name, language]) => hljs.registerLanguage(name, language));
|
|
26
|
-
|
|
27
|
-
// highlight.js 11 不内置 vue 语法,用 xml 规则近似高亮 .vue / 模板块
|
|
28
|
-
hljs.registerAliases(["vue", "html"], { languageName: "xml" });
|
|
29
|
-
|
|
30
|
-
export default hljs;
|
|
@@ -1,129 +0,0 @@
|
|
|
1
|
-
// markdown-it 渲染器:标题锚点 + TOC 收集、代码块外壳(语言标签 + 复制按钮)、
|
|
2
|
-
// 外链新窗口打开、文档内 .md 相对链接转站内路由、::: tip/warning/danger 提示块。
|
|
3
|
-
import MarkdownIt from "markdown-it";
|
|
4
|
-
import MarkdownItContainer from "markdown-it-container";
|
|
5
|
-
import hljs from "./highlight";
|
|
6
|
-
import { slugify, resolveDocHref } from "../utils";
|
|
7
|
-
|
|
8
|
-
const md = new MarkdownIt({
|
|
9
|
-
html: true,
|
|
10
|
-
linkify: true,
|
|
11
|
-
breaks: false,
|
|
12
|
-
typographer: false,
|
|
13
|
-
});
|
|
14
|
-
|
|
15
|
-
// ::: <type> [自定义标题] 提示块(tip / info / warning / danger)
|
|
16
|
-
const CONTAINER_DEFAULT_TITLES = {
|
|
17
|
-
tip: "提示",
|
|
18
|
-
info: "说明",
|
|
19
|
-
warning: "注意",
|
|
20
|
-
danger: "危险",
|
|
21
|
-
};
|
|
22
|
-
|
|
23
|
-
Object.keys(CONTAINER_DEFAULT_TITLES).forEach((type) => {
|
|
24
|
-
md.use(MarkdownItContainer, type, {
|
|
25
|
-
render(tokens, idx) {
|
|
26
|
-
if (tokens[idx].nesting === 1) {
|
|
27
|
-
const info = tokens[idx].info.trim().slice(type.length).trim();
|
|
28
|
-
const title = info || CONTAINER_DEFAULT_TITLES[type];
|
|
29
|
-
return (
|
|
30
|
-
`<div class="doc-container doc-container-${type}">` +
|
|
31
|
-
`<p class="doc-container-title">${md.utils.escapeHtml(title)}</p>`
|
|
32
|
-
);
|
|
33
|
-
}
|
|
34
|
-
return "</div>";
|
|
35
|
-
},
|
|
36
|
-
});
|
|
37
|
-
});
|
|
38
|
-
|
|
39
|
-
// 代码块:highlight.js 高亮(未识别语言则原样转义),外层加语言标签与复制按钮
|
|
40
|
-
md.renderer.rules.fence = (tokens, idx) => {
|
|
41
|
-
const token = tokens[idx];
|
|
42
|
-
const lang = (token.info || "").trim().split(/\s+/)[0];
|
|
43
|
-
|
|
44
|
-
let codeHtml;
|
|
45
|
-
if (lang && hljs.getLanguage(lang)) {
|
|
46
|
-
try {
|
|
47
|
-
codeHtml = hljs.highlight(token.content, {
|
|
48
|
-
language: lang,
|
|
49
|
-
ignoreIllegals: true,
|
|
50
|
-
}).value;
|
|
51
|
-
} catch (e) {
|
|
52
|
-
codeHtml = md.utils.escapeHtml(token.content);
|
|
53
|
-
}
|
|
54
|
-
} else {
|
|
55
|
-
codeHtml = md.utils.escapeHtml(token.content);
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
const langLabel = md.utils.escapeHtml(lang || "text");
|
|
59
|
-
return (
|
|
60
|
-
`<div class="doc-code-block">` +
|
|
61
|
-
`<div class="doc-code-bar"><span class="doc-code-lang">${langLabel}</span>` +
|
|
62
|
-
`<button class="doc-code-copy" type="button">复制</button></div>` +
|
|
63
|
-
`<pre><code class="hljs">${codeHtml}</code></pre></div>\n`
|
|
64
|
-
);
|
|
65
|
-
};
|
|
66
|
-
|
|
67
|
-
// 标题开标签:生成唯一 id(供 TOC / 锚点 / 搜索跳转),并收集 h2/h3 进 TOC
|
|
68
|
-
md.renderer.rules.heading_open = (tokens, idx, options, env, self) => {
|
|
69
|
-
const token = tokens[idx];
|
|
70
|
-
const level = Number(token.tag.slice(1));
|
|
71
|
-
const inline = tokens[idx + 1];
|
|
72
|
-
const text = (inline ? inline.content : "").trim();
|
|
73
|
-
|
|
74
|
-
const base = slugify(text) || `heading-${idx}`;
|
|
75
|
-
const count = env.slugCount[base] || 0;
|
|
76
|
-
env.slugCount[base] = count + 1;
|
|
77
|
-
const id = count ? `${base}-${count + 1}` : base;
|
|
78
|
-
|
|
79
|
-
token.attrSet("id", id);
|
|
80
|
-
env.currentHeadingId = id;
|
|
81
|
-
if (level >= 2 && level <= 3) {
|
|
82
|
-
env.toc.push({ level, text, id });
|
|
83
|
-
}
|
|
84
|
-
return self.renderToken(tokens, idx, options);
|
|
85
|
-
};
|
|
86
|
-
|
|
87
|
-
// 标题闭标签:在标题内部追加悬停可见的 # 锚点链接
|
|
88
|
-
md.renderer.rules.heading_close = (tokens, idx, options, env, self) => {
|
|
89
|
-
const id = env.currentHeadingId || "";
|
|
90
|
-
const anchor = id
|
|
91
|
-
? `<a class="doc-header-anchor" href="#${id}" aria-hidden="true">#</a>`
|
|
92
|
-
: "";
|
|
93
|
-
return anchor + self.renderToken(tokens, idx, options);
|
|
94
|
-
};
|
|
95
|
-
|
|
96
|
-
// 链接开标签:外链新窗口;文档内相对 .md 链接(./xx.md、../xx.md)转站内路由
|
|
97
|
-
const defaultLinkOpen =
|
|
98
|
-
md.renderer.rules.link_open ||
|
|
99
|
-
((tokens, idx, options, env, self) => self.renderToken(tokens, idx, options));
|
|
100
|
-
|
|
101
|
-
md.renderer.rules.link_open = (tokens, idx, options, env, self) => {
|
|
102
|
-
const token = tokens[idx];
|
|
103
|
-
const href = token.attrGet("href") || "";
|
|
104
|
-
|
|
105
|
-
if (/^https?:\/\//i.test(href)) {
|
|
106
|
-
token.attrSet("target", "_blank");
|
|
107
|
-
token.attrSet("rel", "noopener noreferrer");
|
|
108
|
-
} else if (!href.startsWith("#") && env.docPath) {
|
|
109
|
-
const resolved = resolveDocHref(env.docPath, href);
|
|
110
|
-
if (resolved) {
|
|
111
|
-
token.attrSet("href", resolved.path + resolved.hash);
|
|
112
|
-
}
|
|
113
|
-
}
|
|
114
|
-
return defaultLinkOpen(tokens, idx, options, env, self);
|
|
115
|
-
};
|
|
116
|
-
|
|
117
|
-
/**
|
|
118
|
-
* 渲染 markdown
|
|
119
|
-
* @param {string} source markdown 原文
|
|
120
|
-
* @param {string} docPath 当前文档路径(相对 docs/),用于解析相对链接
|
|
121
|
-
* @returns {{ html: string, toc: Array<{level:number, text:string, id:string}> }}
|
|
122
|
-
*/
|
|
123
|
-
export function renderMarkdown(source, docPath) {
|
|
124
|
-
const env = { toc: [], slugCount: {}, docPath };
|
|
125
|
-
const html = md.render(source, env);
|
|
126
|
-
return { html, toc: env.toc };
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
export default md;
|