Skip to content

Commit 6a42ec9

Browse files
authored
Merge pull request #25 from easyops-cn/steve/multi-folders
feat: support multiple docs and blog folders #24
2 parents 4bb0db4 + d90f4ad commit 6a42ec9

File tree

10 files changed

+140
-58
lines changed

10 files changed

+140
-58
lines changed

README.md

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -72,12 +72,12 @@ yarn add nodejieba
7272
| indexDocs | boolean | `true` | Whether to index docs. |
7373
| indexBlog | boolean | `true` | Whether to index blog. |
7474
| indexPages | boolean | `false` | Whether to index pages. |
75-
| docsRouteBasePath | string | `"/docs"` | Base route path of docs. Slash at beginning is not required. |
76-
| blogRouteBasePath | string | `"/blog"` | Base route path of blog. Slash at beginning is not required. |
75+
| docsRouteBasePath | string \| string[] | `"/docs"` | Base route path of docs. Slash at beginning is not required. |
76+
| blogRouteBasePath | string \| string[] | `"/blog"` | Base route path of blog. Slash at beginning is not required. |
7777
| language | string \| string[] | `"en"` | All [lunr-languages](https://github.com/MihaiValentin/lunr-languages) supported languages, + `zh` 🔥. |
7878
| hashed | boolean | `false` | Whether to add a hashed query when fetching index (based on the content hash of all `*.md`) |
79-
| docsDir | string | `"docs"` | The dir of docs to get the content hash, it's relative to the dir of your project. |
80-
| blogDir | string | `"blog"` | Just like the `docsDir` but applied to blog. |
79+
| docsDir | string \| string[] | `"docs"` | The dir of docs to get the content hash, it's relative to the dir of your project. |
80+
| blogDir | string \| string[] | `"blog"` | Just like the `docsDir` but applied to blog. |
8181
| removeDefaultStopWordFilter | boolean | `false` | Sometimes people (E.g., us) want to keep the English stop words as indexed, since they maybe are relevant in programming docs. |
8282
| searchResultLimits | number | `8` | Limit the search results. |
8383
| searchResultContextMaxLength | number | `50` | Set the max length of characters of each search result to show. results. |

src/server/utils/getIndexHash.spec.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -57,14 +57,14 @@ jest.mock(
5757
describe("getIndexHash", () => {
5858
test.each<[Partial<ProcessedPluginOptions>, string | null, number]>([
5959
[{ hashed: false }, null, 0],
60-
[{ hashed: true, indexDocs: true, docsDir: "/tmp/docs" }, "87def35c", 0],
61-
[{ hashed: true, indexBlog: true, blogDir: "/tmp/blog" }, null, 0],
60+
[{ hashed: true, indexDocs: true, docsDir: ["/tmp/docs"] }, "87def35c", 0],
61+
[{ hashed: true, indexBlog: true, blogDir: ["/tmp/blog"] }, null, 0],
6262
[
63-
{ hashed: true, indexDocs: true, docsDir: "/does-not-exist/docs" },
63+
{ hashed: true, indexDocs: true, docsDir: ["/does-not-exist/docs"] },
6464
null,
6565
1,
6666
],
67-
[{ hashed: true, indexDocs: true, docsDir: "/tmp/index.js" }, null, 1],
67+
[{ hashed: true, indexDocs: true, docsDir: ["/tmp/index.js"] }, null, 1],
6868
])("getIndexHash(%j) should return '%s'", (config, hash, warnCount) => {
6969
expect(getIndexHash(config as ProcessedPluginOptions)).toBe(hash);
7070
expect(mockConsoleWarn).toBeCalledTimes(warnCount);

src/server/utils/getIndexHash.ts

Lines changed: 8 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -16,18 +16,14 @@ export function getIndexHash(config: ProcessedPluginOptions): string | null {
1616
dirField: "docsDir" | "blogDir"
1717
): void => {
1818
if (config[flagField]) {
19-
if (!fs.existsSync(config[dirField])) {
20-
console.warn(
21-
`Warn: \`${dirField}\` doesn't exist: "${config[dirField]}".`
22-
);
23-
} else if (!fs.lstatSync(config[dirField]).isDirectory()) {
24-
console.warn(
25-
`Warn: \`${dirField}\` is not a directory: "${config[dirField]}".`
26-
);
27-
} else {
28-
files.push(
29-
...klawSync(config[dirField], { nodir: true, filter: markdownFilter })
30-
);
19+
for (const dir of config[dirField]) {
20+
if (!fs.existsSync(dir)) {
21+
console.warn(`Warn: \`${dirField}\` doesn't exist: "${dir}".`);
22+
} else if (!fs.lstatSync(dir).isDirectory()) {
23+
console.warn(`Warn: \`${dirField}\` is not a directory: "${dir}".`);
24+
} else {
25+
files.push(...klawSync(dir, { nodir: true, filter: markdownFilter }));
26+
}
3127
}
3228
}
3329
};

src/server/utils/processDocInfos.spec.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -34,8 +34,8 @@ describe("processDocInfos", () => {
3434
indexDocs: true,
3535
indexBlog: true,
3636
indexPages: true,
37-
docsRouteBasePath: "docs",
38-
blogRouteBasePath: "blog",
37+
docsRouteBasePath: ["docs"],
38+
blogRouteBasePath: ["blog"],
3939
},
4040
[
4141
{

src/server/utils/processDocInfos.ts

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -29,17 +29,25 @@ export function processDocInfos(
2929
// Do not index error page and search page.
3030
return;
3131
}
32-
if (indexBlog && urlMatchesPrefix(route, blogRouteBasePath)) {
32+
if (
33+
indexBlog &&
34+
blogRouteBasePath.some((basePath) => urlMatchesPrefix(route, basePath))
35+
) {
3336
if (
34-
route === blogRouteBasePath ||
35-
urlMatchesPrefix(route, `${blogRouteBasePath}/tags`)
37+
blogRouteBasePath.some(
38+
(basePath) =>
39+
route === basePath || urlMatchesPrefix(route, `${basePath}/tags`)
40+
)
3641
) {
3742
// Do not index list of blog posts and tags filter pages
3843
return;
3944
}
4045
return { route, url, type: "blog" };
4146
}
42-
if (indexDocs && urlMatchesPrefix(route, docsRouteBasePath)) {
47+
if (
48+
indexDocs &&
49+
docsRouteBasePath.some((basePath) => urlMatchesPrefix(route, basePath))
50+
) {
4351
return { route, url, type: "docs" };
4452
}
4553
if (indexPages) {

src/server/utils/processPluginOptions.spec.ts

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,25 +7,33 @@ describe("processPluginOptions", () => {
77
test.each<[PluginOptions, Partial<ProcessedPluginOptions>]>([
88
[
99
{
10+
docsRouteBasePath: "docs",
11+
blogRouteBasePath: "/blog",
1012
docsDir: "docs",
1113
blogDir: "blog",
1214
language: "en",
1315
},
1416
{
15-
blogDir: "/tmp/blog",
16-
docsDir: "/tmp/docs",
17+
docsRouteBasePath: ["docs"],
18+
blogRouteBasePath: ["blog"],
19+
blogDir: ["/tmp/blog"],
20+
docsDir: ["/tmp/docs"],
1721
language: ["en"],
1822
},
1923
],
2024
[
2125
{
26+
docsRouteBasePath: ["docs"],
27+
blogRouteBasePath: ["/blog"],
2228
docsDir: "docs",
2329
blogDir: "blog",
2430
language: ["en", "zh"],
2531
},
2632
{
27-
blogDir: "/tmp/blog",
28-
docsDir: "/tmp/docs",
33+
docsRouteBasePath: ["docs"],
34+
blogRouteBasePath: ["blog"],
35+
blogDir: ["/tmp/blog"],
36+
docsDir: ["/tmp/docs"],
2937
language: ["en", "zh"],
3038
},
3139
],

src/server/utils/processPluginOptions.ts

Lines changed: 19 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,24 @@ export function processPluginOptions(
66
siteDir: string
77
): ProcessedPluginOptions {
88
const config = { ...options } as ProcessedPluginOptions;
9-
config.docsDir = path.resolve(siteDir, config.docsDir);
10-
config.blogDir = path.resolve(siteDir, config.blogDir);
11-
if (!Array.isArray(config.language)) {
12-
config.language = [config.language];
13-
}
9+
ensureArray(config, "docsRouteBasePath");
10+
ensureArray(config, "blogRouteBasePath");
11+
ensureArray(config, "language");
12+
ensureArray(config, "docsDir");
13+
ensureArray(config, "blogDir");
14+
config.docsRouteBasePath = config.docsRouteBasePath.map((basePath) =>
15+
basePath.replace(/^\//, "")
16+
);
17+
config.blogRouteBasePath = config.blogRouteBasePath.map((basePath) =>
18+
basePath.replace(/^\//, "")
19+
);
20+
config.docsDir = config.docsDir.map((dir) => path.resolve(siteDir, dir));
21+
config.blogDir = config.blogDir.map((dir) => path.resolve(siteDir, dir));
1422
return config;
1523
}
24+
25+
function ensureArray<T>(object: T, key: keyof T): void {
26+
if (!Array.isArray(object[key])) {
27+
(object as any)[key] = [object[key]];
28+
}
29+
}

src/server/utils/validateOptions.spec.ts

Lines changed: 53 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,9 @@ describe("validateOptions", () => {
77
schema: Joi.Schema,
88
options: PluginOptions | undefined
99
): Required<PluginOptions> {
10-
const { error, value } = schema.validate(options);
10+
const { error, value } = schema.validate(options, {
11+
convert: false,
12+
});
1113
if (error) {
1214
throw error;
1315
}
@@ -18,10 +20,10 @@ describe("validateOptions", () => {
1820
[
1921
undefined,
2022
{
21-
blogRouteBasePath: "blog",
22-
blogDir: "blog",
23-
docsRouteBasePath: "docs",
24-
docsDir: "docs",
23+
blogRouteBasePath: ["blog"],
24+
blogDir: ["blog"],
25+
docsRouteBasePath: ["docs"],
26+
docsDir: ["docs"],
2527
hashed: false,
2628
indexBlog: true,
2729
indexDocs: true,
@@ -35,10 +37,10 @@ describe("validateOptions", () => {
3537
[
3638
{ language: ["en", "zh"] },
3739
{
38-
blogRouteBasePath: "blog",
39-
blogDir: "blog",
40-
docsRouteBasePath: "docs",
41-
docsDir: "docs",
40+
blogRouteBasePath: ["blog"],
41+
blogDir: ["blog"],
42+
docsRouteBasePath: ["docs"],
43+
docsDir: ["docs"],
4244
hashed: false,
4345
indexBlog: true,
4446
indexDocs: true,
@@ -58,9 +60,9 @@ describe("validateOptions", () => {
5860
searchResultContextMaxLength: 30,
5961
},
6062
{
61-
blogRouteBasePath: "blog",
63+
blogRouteBasePath: ["blog"],
6264
blogDir: "src/blog",
63-
docsRouteBasePath: "docs",
65+
docsRouteBasePath: ["docs"],
6466
docsDir: "src/docs",
6567
hashed: false,
6668
indexBlog: true,
@@ -72,6 +74,46 @@ describe("validateOptions", () => {
7274
searchResultContextMaxLength: 30,
7375
},
7476
],
77+
[
78+
{
79+
docsRouteBasePath: "/dev/docs",
80+
blogRouteBasePath: "/dev/blog",
81+
},
82+
{
83+
blogRouteBasePath: "/dev/blog",
84+
blogDir: ["blog"],
85+
docsRouteBasePath: "/dev/docs",
86+
docsDir: ["docs"],
87+
hashed: false,
88+
indexBlog: true,
89+
indexDocs: true,
90+
indexPages: false,
91+
language: ["en"],
92+
removeDefaultStopWordFilter: false,
93+
searchResultLimits: 8,
94+
searchResultContextMaxLength: 50,
95+
},
96+
],
97+
[
98+
{
99+
docsRouteBasePath: ["/dev/docs"],
100+
blogRouteBasePath: ["/dev/blog"],
101+
},
102+
{
103+
blogRouteBasePath: ["/dev/blog"],
104+
blogDir: ["blog"],
105+
docsRouteBasePath: ["/dev/docs"],
106+
docsDir: ["docs"],
107+
hashed: false,
108+
indexBlog: true,
109+
indexDocs: true,
110+
indexPages: false,
111+
language: ["en"],
112+
removeDefaultStopWordFilter: false,
113+
searchResultLimits: 8,
114+
searchResultContextMaxLength: 50,
115+
},
116+
],
75117
])("validateOptions(...) should work", (options, config) => {
76118
expect(validateOptions({ options, validate })).toEqual(config);
77119
});

src/server/utils/validateOptions.ts

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -6,18 +6,21 @@ type ValidateFn = (
66
options: PluginOptions | undefined
77
) => Required<PluginOptions>;
88

9+
const isStringOrArrayOfStrings = Joi.alternatives().try(
10+
Joi.string(),
11+
Joi.array().items(Joi.string())
12+
);
13+
914
const schema = Joi.object({
1015
indexDocs: Joi.boolean().default(true),
1116
indexBlog: Joi.boolean().default(true),
1217
indexPages: Joi.boolean().default(false),
13-
docsRouteBasePath: Joi.string().replace(/^\//, "").default("docs"),
14-
blogRouteBasePath: Joi.string().replace(/^\//, "").default("blog"),
15-
language: Joi.alternatives()
16-
.try(Joi.string(), Joi.array().items(Joi.string()))
17-
.default(["en"]),
18+
docsRouteBasePath: isStringOrArrayOfStrings.default(["docs"]),
19+
blogRouteBasePath: isStringOrArrayOfStrings.default(["blog"]),
20+
language: isStringOrArrayOfStrings.default(["en"]),
1821
hashed: Joi.boolean().default(false),
19-
docsDir: Joi.string().default("docs"),
20-
blogDir: Joi.string().default("blog"),
22+
docsDir: isStringOrArrayOfStrings.default(["docs"]),
23+
blogDir: isStringOrArrayOfStrings.default(["blog"]),
2124
removeDefaultStopWordFilter: Joi.boolean().default(false),
2225
searchResultLimits: Joi.number().default(8),
2326
searchResultContextMaxLength: Joi.number().default(50),

src/shared/interfaces.ts

Lines changed: 16 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -125,12 +125,12 @@ export interface PluginOptions {
125125
indexDocs?: boolean;
126126
indexBlog?: boolean;
127127
indexPages?: boolean;
128-
docsRouteBasePath?: string;
129-
blogRouteBasePath?: string;
128+
docsRouteBasePath?: string | string[];
129+
blogRouteBasePath?: string | string[];
130130
language?: string | string[];
131131
hashed?: boolean;
132-
docsDir?: string;
133-
blogDir?: string;
132+
docsDir?: string | string[];
133+
blogDir?: string | string[];
134134
removeDefaultStopWordFilter?: boolean;
135135

136136
searchResultLimits?: number;
@@ -151,9 +151,20 @@ export interface PluginOptions {
151151
}
152152

153153
export type ProcessedPluginOptions = Required<
154-
Omit<PluginOptions, "language">
154+
Omit<
155+
PluginOptions,
156+
| "language"
157+
| "docsRouteBasePath"
158+
| "blogRouteBasePath"
159+
| "docsDir"
160+
| "blogDir"
161+
>
155162
> & {
163+
docsRouteBasePath: string[];
164+
blogRouteBasePath: string[];
156165
language: string[];
166+
docsDir: string[];
167+
blogDir: string[];
157168
};
158169

159170
export interface PostBuildData {

0 commit comments

Comments
 (0)