create-lumfall 1.0.0
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 +44 -0
- package/cli.js +218 -0
- package/package.json +29 -0
- package/templates/basic/README.md +113 -0
- package/templates/basic/app/controller/demo.js +28 -0
- package/templates/basic/app/extend/README.md +3 -0
- package/templates/basic/app/middleware/README.md +2 -0
- package/templates/basic/app/middleware.js +13 -0
- package/templates/basic/app/pages/home/entry.home.js +4 -0
- package/templates/basic/app/pages/home/home.vue +164 -0
- package/templates/basic/app/router/demo.js +17 -0
- package/templates/basic/app/router-schema/demo.js +29 -0
- package/templates/basic/app/service/demo.js +51 -0
- package/templates/basic/app/webpack.config.js +4 -0
- package/templates/basic/build.js +7 -0
- package/templates/basic/config/config.beta.js +4 -0
- package/templates/basic/config/config.default.js +32 -0
- package/templates/basic/config/config.local.js +4 -0
- package/templates/basic/config/config.prod.js +6 -0
- package/templates/basic/package.json +31 -0
- package/templates/basic/server.js +26 -0
- package/templates/document/README.md +145 -0
- package/templates/document/app/extend/README.md +6 -0
- package/templates/document/app/middleware/README.md +2 -0
- package/templates/document/app/middleware.js +2 -0
- package/templates/document/app/pages/docs/assets/docs-logo.svg +5 -0
- package/templates/document/app/pages/docs/components/doc-layout.vue +144 -0
- package/templates/document/app/pages/docs/components/doc-navbar.vue +103 -0
- package/templates/document/app/pages/docs/components/doc-search.vue +131 -0
- package/templates/document/app/pages/docs/components/doc-sidebar.vue +29 -0
- package/templates/document/app/pages/docs/components/doc-toc.vue +21 -0
- package/templates/document/app/pages/docs/content.js +28 -0
- package/templates/document/app/pages/docs/docs-config.js +154 -0
- package/templates/document/app/pages/docs/docs.vue +24 -0
- package/templates/document/app/pages/docs/entry.docs.js +26 -0
- package/templates/document/app/pages/docs/markdown/highlight.js +30 -0
- package/templates/document/app/pages/docs/markdown/index.js +129 -0
- package/templates/document/app/pages/docs/search.js +142 -0
- package/templates/document/app/pages/docs/styles/docs.less +1091 -0
- package/templates/document/app/pages/docs/styles/vars.less +87 -0
- package/templates/document/app/pages/docs/theme.js +47 -0
- package/templates/document/app/pages/docs/utils.js +58 -0
- package/templates/document/app/pages/docs/views/doc-home.vue +56 -0
- package/templates/document/app/pages/docs/views/doc-page.vue +147 -0
- package/templates/document/app/webpack.config.js +15 -0
- package/templates/document/build.js +6 -0
- package/templates/document/config/config.beta.js +2 -0
- package/templates/document/config/config.default.js +9 -0
- package/templates/document/config/config.local.js +2 -0
- package/templates/document/config/config.prod.js +2 -0
- package/templates/document/docs/advanced/dashboard.md +66 -0
- package/templates/document/docs/advanced/health.md +72 -0
- package/templates/document/docs/advanced/monitoring.md +60 -0
- package/templates/document/docs/advanced/security.md +88 -0
- package/templates/document/docs/core/app-instance.md +114 -0
- package/templates/document/docs/core/controller-service.md +113 -0
- package/templates/document/docs/core/lifecycle.md +60 -0
- package/templates/document/docs/core/middleware.md +83 -0
- package/templates/document/docs/core/plugins.md +77 -0
- package/templates/document/docs/core/router-schema.md +102 -0
- package/templates/document/docs/dsl/api-contract.md +88 -0
- package/templates/document/docs/dsl/extend.md +311 -0
- package/templates/document/docs/dsl/menu.md +101 -0
- package/templates/document/docs/dsl/model-project.md +122 -0
- package/templates/document/docs/dsl/overview.md +116 -0
- package/templates/document/docs/dsl/reference.md +175 -0
- package/templates/document/docs/dsl/schema-actions.md +135 -0
- package/templates/document/docs/dsl/schema.md +121 -0
- package/templates/document/docs/frontend/build.md +118 -0
- package/templates/document/docs/frontend/curl.md +75 -0
- package/templates/document/docs/frontend/page.md +99 -0
- package/templates/document/docs/frontend/widgets.md +151 -0
- package/templates/document/docs/guide/config.md +108 -0
- package/templates/document/docs/guide/deployment.md +115 -0
- package/templates/document/docs/guide/getting-started.md +199 -0
- package/templates/document/docs/guide/introduction.md +55 -0
- package/templates/document/docs/guide/structure.md +98 -0
- package/templates/document/docs/reference/commands.md +70 -0
- package/templates/document/docs/reference/faq.md +94 -0
- package/templates/document/package.json +36 -0
- package/templates/document/scripts/build-static.js +129 -0
- package/templates/document/server.js +13 -0
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
// 站内搜索:对全部文档做轻量内存索引(标题 / 小节标题 / 正文),
|
|
2
|
+
// 返回带高亮摘要的结果。文档规模在几百页以内时体验足够,无需后端参与。
|
|
3
|
+
import { getAllDocs } from "./content";
|
|
4
|
+
import { slugify, escapeHtml } from "./utils";
|
|
5
|
+
|
|
6
|
+
let index = null;
|
|
7
|
+
|
|
8
|
+
/** 懒构建索引:文档内容在构建时已打进 bundle,这里只做文本预处理 */
|
|
9
|
+
function buildIndex() {
|
|
10
|
+
if (index) {
|
|
11
|
+
return index;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
const all = getAllDocs();
|
|
15
|
+
index = Object.entries(all).map(([docPath, source]) => {
|
|
16
|
+
// 去掉代码块再提取文本,避免把代码当正文搜索
|
|
17
|
+
const withoutCode = String(source).replace(/```[\s\S]*?```/g, " ");
|
|
18
|
+
|
|
19
|
+
const headings = [];
|
|
20
|
+
const headingRe = /^(#{2,3})\s+(.+?)\s*#*\s*$/gm;
|
|
21
|
+
let match;
|
|
22
|
+
while ((match = headingRe.exec(withoutCode))) {
|
|
23
|
+
headings.push({
|
|
24
|
+
level: match[1].length,
|
|
25
|
+
text: match[2].replace(/[#*`]/g, "").trim(),
|
|
26
|
+
});
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const text = withoutCode
|
|
30
|
+
.replace(/<[^>]+>/g, " ")
|
|
31
|
+
.replace(/[#>*`~[\]()!_|:-]/g, " ")
|
|
32
|
+
.replace(/\s+/g, " ")
|
|
33
|
+
.trim()
|
|
34
|
+
.toLowerCase();
|
|
35
|
+
|
|
36
|
+
return { docPath, headings, text };
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
return index;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function buildTitleMap(sidebar) {
|
|
43
|
+
const map = {};
|
|
44
|
+
(sidebar || []).forEach((group) => {
|
|
45
|
+
(group.items || []).forEach((item) => {
|
|
46
|
+
map[item.path.replace(/^\/view\/docs\//, "")] = item.text;
|
|
47
|
+
});
|
|
48
|
+
});
|
|
49
|
+
return map;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/** 把关键词片段包上 <mark>,先转义再替换,保证安全 */
|
|
53
|
+
function highlight(text, keyword) {
|
|
54
|
+
if (!keyword) {
|
|
55
|
+
return escapeHtml(text);
|
|
56
|
+
}
|
|
57
|
+
const escaped = escapeHtml(text);
|
|
58
|
+
const pattern = keyword
|
|
59
|
+
.split(/\s+/)
|
|
60
|
+
.filter(Boolean)
|
|
61
|
+
.map((term) => term.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"))
|
|
62
|
+
.join("|");
|
|
63
|
+
if (!pattern) {
|
|
64
|
+
return escaped;
|
|
65
|
+
}
|
|
66
|
+
return escaped.replace(new RegExp(`(${pattern})`, "gi"), "<mark>$1</mark>");
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* 搜索文档
|
|
71
|
+
* @param {string} keyword 关键词,支持空格分词
|
|
72
|
+
* @param {object} docsConfig 文档站配置(取侧边栏标题)
|
|
73
|
+
* @returns {Array<{docPath, title, titleHtml, heading, anchor, snippet}>}
|
|
74
|
+
*/
|
|
75
|
+
export function searchDocs(keyword, docsConfig) {
|
|
76
|
+
const terms = String(keyword || "")
|
|
77
|
+
.trim()
|
|
78
|
+
.toLowerCase()
|
|
79
|
+
.split(/\s+/)
|
|
80
|
+
.filter(Boolean);
|
|
81
|
+
if (!terms.length) {
|
|
82
|
+
return [];
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
const titleMap = buildTitleMap(docsConfig.sidebar);
|
|
86
|
+
const results = [];
|
|
87
|
+
|
|
88
|
+
buildIndex().forEach(({ docPath, headings, text }) => {
|
|
89
|
+
const title = titleMap[docPath] || docPath;
|
|
90
|
+
const titleLower = title.toLowerCase();
|
|
91
|
+
|
|
92
|
+
let score = 0;
|
|
93
|
+
let matchedHeading = null;
|
|
94
|
+
|
|
95
|
+
terms.forEach((term) => {
|
|
96
|
+
if (titleLower.includes(term)) {
|
|
97
|
+
score += 100;
|
|
98
|
+
}
|
|
99
|
+
// 中文场景:标题无空格分词,按整串 contains;正文按出现次数计分
|
|
100
|
+
const occurrences = text.split(term).length - 1;
|
|
101
|
+
score += occurrences;
|
|
102
|
+
|
|
103
|
+
if (!matchedHeading) {
|
|
104
|
+
const heading = headings.find((h) => h.text.toLowerCase().includes(term));
|
|
105
|
+
if (heading) {
|
|
106
|
+
matchedHeading = heading;
|
|
107
|
+
score += 30;
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
if (score <= 0) {
|
|
113
|
+
return;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
// 摘要:取第一个命中的关键词前后各 70 字符
|
|
117
|
+
let snippet = "";
|
|
118
|
+
const firstTerm = terms[0];
|
|
119
|
+
const pos = text.indexOf(firstTerm);
|
|
120
|
+
if (pos >= 0) {
|
|
121
|
+
const start = Math.max(0, pos - 70);
|
|
122
|
+
const end = Math.min(text.length, pos + firstTerm.length + 70);
|
|
123
|
+
snippet = `${start > 0 ? "…" : ""}${text.slice(start, end).trim()}${
|
|
124
|
+
end < text.length ? "…" : ""
|
|
125
|
+
}`;
|
|
126
|
+
} else {
|
|
127
|
+
snippet = text.slice(0, 140);
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
results.push({
|
|
131
|
+
docPath,
|
|
132
|
+
title,
|
|
133
|
+
titleHtml: highlight(title, keyword),
|
|
134
|
+
heading: matchedHeading ? matchedHeading.text : "",
|
|
135
|
+
anchor: matchedHeading ? slugify(matchedHeading.text) : "",
|
|
136
|
+
snippet: highlight(snippet, keyword),
|
|
137
|
+
score,
|
|
138
|
+
});
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
return results.sort((a, b) => b.score - a.score).slice(0, 10);
|
|
142
|
+
}
|