for-humanity 0.1.2 → 0.2.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/CHANGELOG.md +11 -0
- package/README.md +4 -3
- package/dist/cli.css +52 -18
- package/dist/cli.js +166 -109
- package/package.json +1 -1
- package/src/cli.ts +6 -3
- package/src/component/widget/shell/_wg-shell-nav-list.css +158 -0
- package/src/component/widget/shell/_wg-shell-nav-list.tsx +72 -0
- package/src/component/widget/shell/_wg-shell-nav.css +0 -114
- package/src/component/widget/shell/_wg-shell-nav.tsx +9 -35
- package/src/component/widget/shell/wg-shell.tsx +3 -3
- package/src/constant/asset.ts +2 -2
- package/src/content/read-docs.test.ts +96 -3
- package/src/content/to-doc-groups/_to-doc-group-branches.ts +58 -0
- package/src/content/to-doc-groups/to-doc-groups.ts +14 -0
- package/src/type/doc-data.ts +3 -2
- package/src/type/doc-group.ts +23 -0
- package/src/type/group-path.ts +7 -0
- package/src/type/site-assets.ts +8 -0
- package/src/type/site-config.ts +5 -4
- package/src/util/file/to-asset-path.ts +26 -0
- package/src/content/to-doc-groups.ts +0 -18
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import {locale_doc_group, locale_doc_name} from "@/constant/locale";
|
|
2
|
+
import type {Doc} from "@/type/doc";
|
|
3
|
+
import type {DocGroup} from "@/type/doc-group";
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* 같은 부모의 문서를 다음 경로 단계로 묶는 재귀 입력
|
|
7
|
+
*/
|
|
8
|
+
interface DocGroupBranchesOptions {
|
|
9
|
+
/**
|
|
10
|
+
* 부모 경로에 속한 문서
|
|
11
|
+
*/
|
|
12
|
+
docs: readonly Doc[];
|
|
13
|
+
/**
|
|
14
|
+
* 부모까지의 경로 · 최상위는 빈 배열
|
|
15
|
+
*/
|
|
16
|
+
path: DocGroup["path"];
|
|
17
|
+
/**
|
|
18
|
+
* 전체 경로의 JSON 키와 navigation 위치
|
|
19
|
+
*/
|
|
20
|
+
positions: ReadonlyMap<string, number>;
|
|
21
|
+
/**
|
|
22
|
+
* 미지정 경로를 지정한 묶음 뒤에 놓는 위치
|
|
23
|
+
*/
|
|
24
|
+
unlistedPosition: number;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* 각 단계의 직접 문서와 하위 묶음을 분리 · 고정된 깊이 제한 없이 구성
|
|
29
|
+
*/
|
|
30
|
+
export const toDocGroupBranches = (options: DocGroupBranchesOptions): DocGroup[] => {
|
|
31
|
+
return [
|
|
32
|
+
...Map.groupBy(
|
|
33
|
+
options.docs.filter((doc) => doc.data.group.length > options.path.length),
|
|
34
|
+
(doc) => doc.data.group[options.path.length],
|
|
35
|
+
),
|
|
36
|
+
]
|
|
37
|
+
.map(([name, docs]) => {
|
|
38
|
+
const path = [...options.path, name];
|
|
39
|
+
|
|
40
|
+
return {
|
|
41
|
+
name,
|
|
42
|
+
path,
|
|
43
|
+
docs,
|
|
44
|
+
position: options.positions.get(JSON.stringify(path)) ?? options.unlistedPosition,
|
|
45
|
+
};
|
|
46
|
+
})
|
|
47
|
+
.toSorted((a, b) => a.position - b.position || a.name.localeCompare(b.name, locale_doc_group))
|
|
48
|
+
.map((group) => {
|
|
49
|
+
return {
|
|
50
|
+
name: group.name,
|
|
51
|
+
path: group.path,
|
|
52
|
+
docs: group.docs
|
|
53
|
+
.filter((doc) => doc.data.group.length === group.path.length)
|
|
54
|
+
.toSorted((a, b) => a.data.order - b.data.order || a.data.name.localeCompare(b.data.name, locale_doc_name) || a.id.localeCompare(b.id, locale_doc_name)),
|
|
55
|
+
groups: toDocGroupBranches({docs: group.docs, path: group.path, positions: options.positions, unlistedPosition: options.unlistedPosition}),
|
|
56
|
+
};
|
|
57
|
+
});
|
|
58
|
+
};
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import {toDocGroupBranches} from "@/content/to-doc-groups/_to-doc-group-branches";
|
|
2
|
+
import type {Doc} from "@/type/doc";
|
|
3
|
+
import type {SiteConfig} from "@/type/site-config";
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* 메타데이터 경로로 읽는 순서를 가진 문서 계층 구성
|
|
7
|
+
* navigation의 첫 지정을 조상에도 적용 · 미지정 형제 묶음은 이름순
|
|
8
|
+
*/
|
|
9
|
+
export const toDocGroups = (options: {docs: readonly Doc[]; navigation: SiteConfig["navigation"]}) => {
|
|
10
|
+
// 같은 조상을 지정한 경로는 최초 navigation 위치를 우선
|
|
11
|
+
const positions = new Map(options.navigation.flatMap((path, index) => path.map((_name, depth) => [JSON.stringify(path.slice(0, depth + 1)), index] as const)).toReversed());
|
|
12
|
+
|
|
13
|
+
return toDocGroupBranches({docs: options.docs, path: [], positions, unlistedPosition: options.navigation.length});
|
|
14
|
+
};
|
package/src/type/doc-data.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import {z} from "zod";
|
|
2
2
|
import {doc_order_default, doc_type} from "@/constant/doc";
|
|
3
|
+
import {groupPathSchema} from "@/type/group-path";
|
|
3
4
|
|
|
4
5
|
/**
|
|
5
6
|
* 문서 frontmatter 스키마 · 검증 실패 시 빌드 중단
|
|
@@ -18,9 +19,9 @@ export const docDataSchema = z.object({
|
|
|
18
19
|
*/
|
|
19
20
|
type: z.enum(doc_type).default(doc_type.document),
|
|
20
21
|
/**
|
|
21
|
-
* 사이드바의 문서 묶음
|
|
22
|
+
* 사이드바의 문서 묶음 경로 · 문자열도 배열로 정규화
|
|
22
23
|
*/
|
|
23
|
-
group:
|
|
24
|
+
group: groupPathSchema,
|
|
24
25
|
/**
|
|
25
26
|
* 묶음 안의 읽는 순서 · 미지정 문서는 뒤에서 이름순
|
|
26
27
|
*/
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import type {Doc} from "@/type/doc";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* 사이드바의 문서 묶음 · 같은 이름도 전체 경로로 구분
|
|
5
|
+
*/
|
|
6
|
+
export interface DocGroup {
|
|
7
|
+
/**
|
|
8
|
+
* 현재 단계의 표시 이름
|
|
9
|
+
*/
|
|
10
|
+
name: string;
|
|
11
|
+
/**
|
|
12
|
+
* 최상위부터 현재 단계까지의 경로
|
|
13
|
+
*/
|
|
14
|
+
path: Doc["data"]["group"];
|
|
15
|
+
/**
|
|
16
|
+
* 현재 묶음에 직접 속한 문서 · order·제목·id 순
|
|
17
|
+
*/
|
|
18
|
+
docs: Doc[];
|
|
19
|
+
/**
|
|
20
|
+
* 현재 묶음의 하위 묶음 · navigation·이름 순
|
|
21
|
+
*/
|
|
22
|
+
groups: DocGroup[];
|
|
23
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import {z} from "zod";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* 탐색 묶음 경로 · 기존 단일 이름과 중첩 배열을 한 번만 정규화
|
|
5
|
+
* 쉼표·슬래시는 이름 그대로 유지하며 빈 경로·단계는 거부
|
|
6
|
+
*/
|
|
7
|
+
export const groupPathSchema = z.union([z.string().trim().min(1), z.array(z.string().trim().min(1)).min(1)]).transform((path) => (typeof path === "string" ? [path] : path));
|
package/src/type/site-assets.ts
CHANGED
package/src/type/site-config.ts
CHANGED
|
@@ -2,6 +2,7 @@ import {z} from "zod";
|
|
|
2
2
|
import {copy_error_navigation_clash, copy_site_title_default} from "@/constant/copy";
|
|
3
3
|
import {site_navigation_default} from "@/constant/site";
|
|
4
4
|
import {status_default_phrases, status_kind} from "@/constant/status";
|
|
5
|
+
import {groupPathSchema} from "@/type/group-path";
|
|
5
6
|
|
|
6
7
|
/**
|
|
7
8
|
* for-humanity.config.mjs 설정 스키마
|
|
@@ -17,12 +18,12 @@ export const siteConfigSchema = z.object({
|
|
|
17
18
|
*/
|
|
18
19
|
description: z.string().optional(),
|
|
19
20
|
/**
|
|
20
|
-
* 탐색
|
|
21
|
+
* 탐색 묶음 경로의 읽는 순서 · 미지정 형제 묶음은 뒤에서 이름순
|
|
21
22
|
*/
|
|
22
23
|
navigation: z
|
|
23
|
-
.array(
|
|
24
|
-
.refine((groups) => new Set(groups).size === groups.length, copy_error_navigation_clash)
|
|
25
|
-
.default([
|
|
24
|
+
.array(groupPathSchema)
|
|
25
|
+
.refine((groups) => new Set(groups.map((path) => JSON.stringify(path))).size === groups.length, copy_error_navigation_clash)
|
|
26
|
+
.default(site_navigation_default.map((name) => [name])),
|
|
26
27
|
/**
|
|
27
28
|
* 본문에서 상태 표지로 표시할 문구
|
|
28
29
|
*/
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import {createHash} from "node:crypto";
|
|
2
|
+
import {readFileSync} from "node:fs";
|
|
3
|
+
import {posix} from "node:path";
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* 파일 내용으로 자원 URL을 구분하는 입력
|
|
7
|
+
*/
|
|
8
|
+
export interface AssetPathOptions {
|
|
9
|
+
/**
|
|
10
|
+
* 사이트 루트 기준 기본 자원 경로
|
|
11
|
+
*/
|
|
12
|
+
path: string;
|
|
13
|
+
/**
|
|
14
|
+
* 배포할 실제 파일 경로
|
|
15
|
+
*/
|
|
16
|
+
file: string;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* 파일명에 내용 지문 추가 · 같은 내용은 같은 URL, 변경한 내용은 새 캐시 키 사용
|
|
21
|
+
*/
|
|
22
|
+
export const toAssetPath = (options: AssetPathOptions) => {
|
|
23
|
+
const extension = posix.extname(options.path);
|
|
24
|
+
|
|
25
|
+
return `${posix.dirname(options.path)}/${posix.basename(options.path, extension)}.${createHash("sha256").update(readFileSync(options.file)).digest("hex")}${extension}`;
|
|
26
|
+
};
|
|
@@ -1,18 +0,0 @@
|
|
|
1
|
-
import {locale_doc_group, locale_doc_name} from "@/constant/locale";
|
|
2
|
-
import type {Doc} from "@/type/doc";
|
|
3
|
-
import type {SiteConfig} from "@/type/site-config";
|
|
4
|
-
|
|
5
|
-
/**
|
|
6
|
-
* 사이드바의 읽는 순서 · 설정 밖 묶음은 뒤에서 이름순
|
|
7
|
-
* 같은 문서 이름·순서도 파일 id로 구분해 탐색 순서를 고정
|
|
8
|
-
*/
|
|
9
|
-
export const toDocGroups = (options: {docs: readonly Doc[]; navigation: SiteConfig["navigation"]}) => {
|
|
10
|
-
const positions = new Map(options.navigation.map((name, index) => [name, index]));
|
|
11
|
-
|
|
12
|
-
return [...Map.groupBy(options.docs, (doc) => doc.data.group)]
|
|
13
|
-
.toSorted((a, b) => (positions.get(a[0]) ?? options.navigation.length) - (positions.get(b[0]) ?? options.navigation.length) || a[0].localeCompare(b[0], locale_doc_group))
|
|
14
|
-
.map(([name, docs]) => ({
|
|
15
|
-
name,
|
|
16
|
-
docs: docs.toSorted((a, b) => a.data.order - b.data.order || a.data.name.localeCompare(b.data.name, locale_doc_name) || a.id.localeCompare(b.id, locale_doc_name)),
|
|
17
|
-
}));
|
|
18
|
-
};
|