for-humanity 0.2.1 → 0.4.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/dist/cli.js CHANGED
@@ -2,9 +2,9 @@
2
2
 
3
3
  // src/cli.ts
4
4
  import { EventEmitter } from "node:events";
5
- import { existsSync as existsSync2, watch } from "node:fs";
5
+ import { existsSync as existsSync3, watch } from "node:fs";
6
6
  import { copyFile, mkdir, rm, writeFile } from "node:fs/promises";
7
- import { basename as basename2, dirname as dirname3, join as join4, relative as relative3, resolve as resolve2 } from "node:path";
7
+ import { basename as basename2, dirname as dirname4, extname as extname4, join as join4, relative as relative4, resolve as resolve3, sep as sep3 } from "node:path";
8
8
  import { fileURLToPath, pathToFileURL } from "node:url";
9
9
  import { serve } from "@hono/node-server";
10
10
  import { serveStatic } from "@hono/node-server/serve-static";
@@ -73,8 +73,12 @@ import clsx2 from "clsx";
73
73
  import { Fragment } from "react";
74
74
  import { jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
75
75
  var WgShellNavList = (props) => {
76
+ const currentAncestors = new Set(props.currentDoc?.ancestors);
76
77
  const entries = [
77
- ...props.group.docs.map((doc) => ({ kind: "document", key: `doc:${doc.id}`, doc, current: doc.id === props.currentDoc?.id })),
78
+ ...props.group.docs.map((doc) => {
79
+ const active = doc.id === props.currentDoc?.id;
80
+ return { kind: "document", key: `doc:${doc.id}`, doc, active, current: active || currentAncestors.has(doc.id) };
81
+ }),
78
82
  ...props.group.groups.map((group) => ({
79
83
  kind: "group",
80
84
  key: `group:${JSON.stringify(group.path)}`,
@@ -91,16 +95,25 @@ var WgShellNavList = (props) => {
91
95
  "wg_shellNavList__item--current": entry.current
92
96
  }),
93
97
  children: [
94
- entry.kind === "document" && /* @__PURE__ */ jsx2(
95
- "a",
96
- {
97
- className: clsx2("wg_shellNavList__link", { "wg_shellNavList__link--active": entry.current }),
98
- href: `/${entry.doc.id}/`,
99
- title: entry.doc.data.label,
100
- "aria-current": entry.current ? "page" : void 0,
101
- children: entry.doc.data.name
102
- }
103
- ),
98
+ entry.kind === "document" && /* @__PURE__ */ jsxs2(Fragment, { children: [
99
+ /* @__PURE__ */ jsx2(
100
+ "a",
101
+ {
102
+ className: clsx2("wg_shellNavList__link", { "wg_shellNavList__link--active": entry.active }),
103
+ href: `/${entry.doc.id}/`,
104
+ title: entry.doc.data.label,
105
+ "aria-current": entry.active ? "page" : void 0,
106
+ children: entry.doc.data.name
107
+ }
108
+ ),
109
+ entry.doc.children !== void 0 && /* @__PURE__ */ jsx2(
110
+ WgShellNavList,
111
+ {
112
+ group: { name: entry.doc.data.name, path: entry.doc.data.group, docs: entry.doc.children, groups: [] },
113
+ currentDoc: props.currentDoc
114
+ }
115
+ )
116
+ ] }),
104
117
  entry.kind === "group" && /* @__PURE__ */ jsxs2(Fragment, { children: [
105
118
  /* @__PURE__ */ jsx2("span", { className: clsx2("wg_shellNavList__branch"), children: entry.group.name }),
106
119
  /* @__PURE__ */ jsx2(WgShellNavList, { group: entry.group, currentDoc: props.currentDoc })
@@ -118,7 +131,7 @@ var copy_nav_open = "Open navigation";
118
131
  var copy_nav_close = "Close navigation";
119
132
  var copy_nav_title = "Navigation";
120
133
  var copy_home_missing = "\uBB38\uC11C \uD3F4\uB354\uC5D0 README.md\uB97C \uCD94\uAC00\uD558\uBA74 \uC774\uACF3\uC5D0 \uD648 \uD654\uBA74\uC73C\uB85C \uD45C\uC2DC\uB429\uB2C8\uB2E4.";
121
- var copy_nav_toc_label = "Contents";
134
+ var copy_nav_toc_label = "On this page";
122
135
  var copy_error_home_clash = "\uD648 README\uAC00 \uACB9\uCE5C\uB2E4";
123
136
  var copy_error_prefix = "for-humanity";
124
137
  var copy_error_font_preload = "Missing bundled font for preload";
@@ -128,16 +141,28 @@ var copy_error_frontmatter = "frontmatter\uAC00 \uC62C\uBC14\uB974\uC9C0 \uC54A\
128
141
  var copy_error_navigation_clash = "navigation\uC758 \uBB36\uC74C\uC774 \uACB9\uCE5C\uB2E4";
129
142
  var copy_error_doc_id = "\uBB38\uC11C id\uC5D0 \uC0AC\uC6A9\uD560 \uC218 \uC5C6\uB294 \uACBD\uB85C";
130
143
  var copy_error_doc_id_clash = "\uBB38\uC11C id\uAC00 \uACB9\uCE5C\uB2E4";
144
+ var copy_error_doc_parent_missing = "\uBD80\uBAA8 \uBB38\uC11C\uAC00 \uC5C6\uB2E4";
145
+ var copy_error_doc_parent_cycle = "\uBB38\uC11C parent\uAC00 \uC21C\uD658\uD55C\uB2E4";
146
+ var copy_error_doc_parent_group = "\uBD80\uBAA8 \uBB38\uC11C\uC640 group\uC774 \uB2E4\uB974\uB2E4";
131
147
  var copy_error_flow = "\uD750\uB984\uB3C4\uB97C \uADF8\uB9AC\uC9C0 \uBABB\uD588\uB2E4";
132
148
  var copy_error_unknown_directive = "\uBAA8\uB974\uB294 \uBD80\uD488\uC774\uB2E4";
133
149
  var copy_error_directive_syntax = "\uBD80\uD488 \uBB38\uBC95\uC774 \uC62C\uBC14\uB974\uC9C0 \uC54A\uB2E4";
134
150
  var copy_warn_broken_link = "\uC5C6\uB294 \uBB38\uC11C\uB85C \uAC74 \uB9C1\uD06C\uB2E4";
135
151
  var copy_warn_unknown_status = "\uC124\uC815\uC5D0 \uC5C6\uB294 \uC0C1\uD0DC \uBB38\uAD6C\uB2E4";
152
+ var copy_error_asset = "\uC790\uC6D0\uC774 \uC62C\uBC14\uB974\uC9C0 \uC54A\uB2E4";
136
153
 
137
154
  // src/constant/locale.ts
138
155
  var locale_doc_name = "en";
139
156
  var locale_doc_group = "ko";
140
157
 
158
+ // src/content/to-doc-groups/_to-doc-branches.ts
159
+ var toDocBranches = (options) => {
160
+ return options.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)).map((doc) => {
161
+ const children = options.children.get(doc.id);
162
+ return { ...doc, children: children === void 0 ? void 0 : toDocBranches({ docs: children, children: options.children }) };
163
+ });
164
+ };
165
+
141
166
  // src/content/to-doc-groups/_to-doc-group-branches.ts
142
167
  var toDocGroupBranches = (options) => {
143
168
  return [
@@ -157,8 +182,17 @@ var toDocGroupBranches = (options) => {
157
182
  return {
158
183
  name: group.name,
159
184
  path: group.path,
160
- docs: group.docs.filter((doc) => doc.data.group.length === group.path.length).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)),
161
- groups: toDocGroupBranches({ docs: group.docs, path: group.path, positions: options.positions, unlistedPosition: options.unlistedPosition })
185
+ docs: toDocBranches({
186
+ docs: group.docs.filter((doc) => doc.data.group.length === group.path.length && doc.data.parent === void 0),
187
+ children: options.children
188
+ }),
189
+ groups: toDocGroupBranches({
190
+ docs: group.docs,
191
+ children: options.children,
192
+ path: group.path,
193
+ positions: options.positions,
194
+ unlistedPosition: options.unlistedPosition
195
+ })
162
196
  };
163
197
  });
164
198
  };
@@ -166,7 +200,8 @@ var toDocGroupBranches = (options) => {
166
200
  // src/content/to-doc-groups/to-doc-groups.ts
167
201
  var toDocGroups = (options) => {
168
202
  const positions = new Map(options.navigation.flatMap((path, index) => path.map((_name, depth) => [JSON.stringify(path.slice(0, depth + 1)), index])).toReversed());
169
- return toDocGroupBranches({ docs: options.docs, path: [], positions, unlistedPosition: options.navigation.length });
203
+ const children = Map.groupBy(options.docs, (doc) => doc.data.parent);
204
+ return toDocGroupBranches({ docs: options.docs, children, path: [], positions, unlistedPosition: options.navigation.length });
170
205
  };
171
206
 
172
207
  // src/component/widget/shell/_wg-shell-nav.tsx
@@ -208,9 +243,21 @@ var asset_content_types = {
208
243
  ".css": "text/css; charset=utf-8",
209
244
  ".js": "text/javascript; charset=utf-8",
210
245
  ".woff2": "font/woff2",
211
- ".svg": "image/svg+xml"
246
+ ".svg": "image/svg+xml",
247
+ ".png": "image/png",
248
+ ".jpg": "image/jpeg",
249
+ ".jpeg": "image/jpeg",
250
+ ".gif": "image/gif",
251
+ ".webp": "image/webp",
252
+ ".avif": "image/avif",
253
+ ".pdf": "application/pdf",
254
+ ".csv": "text/csv; charset=utf-8",
255
+ ".txt": "text/plain; charset=utf-8",
256
+ ".zip": "application/zip"
212
257
  };
213
258
  var asset_content_type_default = "application/octet-stream";
259
+ var asset_media_dir = `/${asset_dir}/media`;
260
+ var asset_media_extensions = /* @__PURE__ */ new Set([".svg", ".png", ".jpg", ".jpeg", ".gif", ".webp", ".avif", ".pdf", ".csv", ".txt", ".zip"]);
214
261
 
215
262
  // src/component/widget/shell/wg-shell.tsx
216
263
  import { jsx as jsx5, jsxs as jsxs5 } from "react/jsx-runtime";
@@ -934,14 +981,67 @@ var remarkUnknownDirectives = () => (tree, file) => {
934
981
  });
935
982
  };
936
983
 
984
+ // src/content/rehype-assets.ts
985
+ import { existsSync as existsSync2, realpathSync, statSync } from "node:fs";
986
+ import { dirname as dirname2, extname, isAbsolute, relative as relative2, resolve as resolve2, sep } from "node:path";
987
+ import { visit as visit7 } from "unist-util-visit";
988
+
989
+ // src/util/file/to-asset-path.ts
990
+ import { createHash } from "node:crypto";
991
+ import { readFileSync } from "node:fs";
992
+ import { posix as posix2 } from "node:path";
993
+ var toAssetPath = (options) => {
994
+ const extension = posix2.extname(options.path);
995
+ return `${posix2.dirname(options.path)}/${posix2.basename(options.path, extension)}.${createHash("sha256").update(readFileSync(options.file)).digest("hex")}${extension}`;
996
+ };
997
+
998
+ // src/content/rehype-assets.ts
999
+ var rehypeAssets = (options) => (tree, file) => {
1000
+ visit7(tree, "element", (node) => {
1001
+ const property = node.tagName === "img" ? "src" : "href";
1002
+ const url = node.properties[property];
1003
+ if (node.tagName !== "img" && node.tagName !== "a" || typeof url !== "string" || /^(?:[a-z][a-z\d+.-]*:|\/|#)/i.test(url)) {
1004
+ return;
1005
+ }
1006
+ const match = /^([^?#]+)([?#].*)?$/.exec(url);
1007
+ if (match === null) {
1008
+ return;
1009
+ }
1010
+ const path = decodeURIComponent(match[1]);
1011
+ const extension = extname(path).toLowerCase();
1012
+ if (!asset_media_extensions.has(extension)) {
1013
+ if (node.tagName === "img") {
1014
+ file.fail(`${copy_error_asset}: \uC9C0\uC6D0\uD558\uC9C0 \uC54A\uB294 \uC774\uBBF8\uC9C0 \uD615\uC2DD \xB7 ${url}`, node);
1015
+ }
1016
+ return;
1017
+ }
1018
+ const target = resolve2(dirname2(file.path), path);
1019
+ const location = relative2(options.root, target);
1020
+ if (location === ".." || location.startsWith(`..${sep}`) || isAbsolute(location)) {
1021
+ file.fail(`${copy_error_asset}: \uBB38\uC11C \uD3F4\uB354 \uBC16 \xB7 ${url}`, node);
1022
+ }
1023
+ if (!existsSync2(target)) {
1024
+ file.fail(`${copy_error_asset}: \uD30C\uC77C\uC774 \uC5C6\uB2E4 \xB7 ${url}`, node);
1025
+ }
1026
+ const actual = realpathSync(target);
1027
+ const actualLocation = relative2(realpathSync(options.root), actual);
1028
+ if (actualLocation === ".." || actualLocation.startsWith(`..${sep}`) || isAbsolute(actualLocation) || !statSync(actual).isFile()) {
1029
+ file.fail(`${copy_error_asset}: \uBB38\uC11C \uD3F4\uB354 \uBC16\uC774\uAC70\uB098 \uC77C\uBC18 \uD30C\uC77C\uC774 \uC544\uB2C8\uB2E4 \xB7 ${url}`, node);
1030
+ }
1031
+ const assetUrl = toAssetPath({ path: `${asset_media_dir}/asset${extension}`, file: actual });
1032
+ options.files.set(assetUrl, actual);
1033
+ node.properties[property] = match[2] === void 0 ? assetUrl : `${assetUrl}${match[2]}`;
1034
+ });
1035
+ };
1036
+
937
1037
  // src/content/rehype-heading-ids.ts
938
1038
  import Slugger from "github-slugger";
939
1039
  import { toString as toPlainText3 } from "hast-util-to-string";
940
- import { visit as visit7 } from "unist-util-visit";
1040
+ import { visit as visit8 } from "unist-util-visit";
941
1041
  var rehypeHeadingIds = () => (tree, file) => {
942
1042
  const slugger = new Slugger();
943
1043
  const headings = [];
944
- visit7(tree, "element", (node) => {
1044
+ visit8(tree, "element", (node) => {
945
1045
  const depth = /^h([1-6])$/.exec(node.tagName)?.[1];
946
1046
  if (depth === void 0) {
947
1047
  return;
@@ -957,9 +1057,9 @@ var rehypeHeadingIds = () => (tree, file) => {
957
1057
  };
958
1058
 
959
1059
  // src/content/remark-report.ts
960
- import { relative as relative2 } from "node:path";
1060
+ import { relative as relative3 } from "node:path";
961
1061
  var remarkReport = (options) => (_tree, file) => {
962
- const path = relative2(options.root, file.path);
1062
+ const path = relative3(options.root, file.path);
963
1063
  for (const message of file.messages) {
964
1064
  console.warn(`${message.line === void 0 ? path : `${path}:${message.line}`} ${message.reason}`);
965
1065
  }
@@ -967,12 +1067,12 @@ var remarkReport = (options) => (_tree, file) => {
967
1067
 
968
1068
  // src/content/create-processor.ts
969
1069
  var createProcessor = (options) => {
970
- return unified().use(remarkParse).use(remarkFrontmatter, ["yaml"]).use(remarkGfm).use(remarkSmartypants).use(remarkDirective).use(remarkBlocks).use(remarkParts).use(remarkFlow).use(remarkStatus, { status: options.site.status }).use(remarkSwatch).use(remarkLinks, { root: options.root }).use(remarkUnknownDirectives).use(remarkReport, { root: options.root }).use(remarkRehype, { allowDangerousHtml: true }).use(rehypeShiki, { theme: createCssVariablesTheme({ name: "for-humanity", variablePrefix: "--app-code-" }) }).use(rehypeHead).use(rehypeHeadingIds).use(rehypeSections).use(rehypeTables).use(rehypeRaw).use(rehypeStringify, { allowDangerousHtml: true });
1070
+ return unified().use(remarkParse).use(remarkFrontmatter, ["yaml"]).use(remarkGfm).use(remarkSmartypants).use(remarkDirective).use(remarkBlocks).use(remarkParts).use(remarkFlow).use(remarkStatus, { status: options.site.status }).use(remarkSwatch).use(remarkLinks, { root: options.root }).use(remarkUnknownDirectives).use(remarkReport, { root: options.root }).use(remarkRehype, { allowDangerousHtml: true }).use(rehypeShiki, { theme: createCssVariablesTheme({ name: "for-humanity", variablePrefix: "--app-code-" }) }).use(rehypeHead).use(rehypeHeadingIds).use(rehypeSections).use(rehypeTables).use(rehypeRaw).use(rehypeAssets, { root: options.root, files: options.files ?? /* @__PURE__ */ new Map() }).use(rehypeStringify, { allowDangerousHtml: true });
971
1071
  };
972
1072
 
973
1073
  // src/content/read-docs.ts
974
1074
  import { readdir, readFile } from "node:fs/promises";
975
- import { join, sep } from "node:path";
1075
+ import { join, posix as posix3, sep as sep2 } from "node:path";
976
1076
  import { VFile } from "vfile";
977
1077
  import { parse as parseYaml } from "yaml";
978
1078
 
@@ -1001,13 +1101,17 @@ var docDataSchema = z2.object({
1001
1101
  */
1002
1102
  label: z2.string(),
1003
1103
  /**
1004
- * 문서 종류 · blueprint 전용 표현 미구현
1104
+ * 기존 type 값 호환 · 생략 권장, 렌더링 차이 없음
1005
1105
  */
1006
1106
  type: z2.enum(doc_type).default(doc_type.document),
1007
1107
  /**
1008
1108
  * 사이드바의 문서 묶음 경로 · 문자열도 배열로 정규화
1009
1109
  */
1010
1110
  group: groupPathSchema,
1111
+ /**
1112
+ * 같은 묶음 안의 부모 문서 id · 확장자 제외, ./·../ 상대 경로도 허용
1113
+ */
1114
+ parent: z2.string().trim().min(1).optional(),
1011
1115
  /**
1012
1116
  * 묶음 안의 읽는 순서 · 미지정 문서는 뒤에서 이름순
1013
1117
  */
@@ -1017,11 +1121,11 @@ var docDataSchema = z2.object({
1017
1121
  // src/content/read-docs.ts
1018
1122
  var readDocs = async (options) => {
1019
1123
  const paths = (await readdir(options.root, { recursive: true })).filter(
1020
- (path) => /\.mdx?$/i.test(path) && !/^(readme|agents|claude)\.md$/i.test(path) && !path.split(sep).includes("node_modules") && !path.startsWith(`dist${sep}`)
1124
+ (path) => /\.mdx?$/i.test(path) && !/^(readme|agents|claude)\.md$/i.test(path) && !path.split(sep2).includes("node_modules") && !path.startsWith(`dist${sep2}`)
1021
1125
  );
1022
1126
  const docs = await Promise.all(
1023
1127
  paths.map(async (path) => {
1024
- const id = path.replace(/\.mdx?$/i, "").split(sep).join("/").toLowerCase();
1128
+ const id = path.replace(/\.mdx?$/i, "").split(sep2).join("/").toLowerCase();
1025
1129
  const segments = id.split("/");
1026
1130
  if (segments.some((segment) => segment === "" || segment === "." || segment === ".." || segment === "index.html") || /[#?%*:\\]/.test(id) || /[\p{Cc}]/u.test(id) || segments[0] === asset_dir || segments[0] === asset_favicon_path.slice(1)) {
1027
1131
  throw new Error(`${copy_error_doc_id}: ${path}`);
@@ -1034,13 +1138,18 @@ var readDocs = async (options) => {
1034
1138
  if (!parsed.success) {
1035
1139
  throw new Error(`${copy_error_frontmatter}: ${path}: ${parsed.error.issues.map((issue) => `${issue.path.join(".")} ${issue.message}`).join(", ")}`);
1036
1140
  }
1037
- const fh = { frontmatter: parsed.data, headings: [], sections: [] };
1141
+ const data = {
1142
+ ...parsed.data,
1143
+ parent: parsed.data.parent !== void 0 && /^\.\.?\//.test(parsed.data.parent) ? posix3.join(posix3.dirname(id), parsed.data.parent) : parsed.data.parent
1144
+ };
1145
+ const fh = { frontmatter: data, headings: [], sections: [] };
1038
1146
  file.data.fh = fh;
1039
1147
  tree.children = tree.children.filter((node) => node.type !== "yaml");
1040
1148
  const rendered = await options.processor.run(tree, file);
1041
1149
  return {
1042
1150
  id,
1043
- data: parsed.data,
1151
+ data,
1152
+ ancestors: [],
1044
1153
  html: options.processor.stringify(rendered, file),
1045
1154
  outline: { headings: fh.headings, sections: fh.sections }
1046
1155
  };
@@ -1050,7 +1159,25 @@ var readDocs = async (options) => {
1050
1159
  if (duplicateIds.length > 0) {
1051
1160
  throw new Error(`${copy_error_doc_id_clash}: ${duplicateIds.map((entry) => entry[0]).join(", ")}`);
1052
1161
  }
1053
- return docs;
1162
+ const docsById = new Map(docs.map((doc) => [doc.id, doc]));
1163
+ return docs.map((doc) => {
1164
+ const ancestors = /* @__PURE__ */ new Set([doc.id]);
1165
+ for (let parentId = doc.data.parent; parentId !== void 0; ) {
1166
+ const parent = docsById.get(parentId);
1167
+ if (parent === void 0) {
1168
+ throw new Error(`${copy_error_doc_parent_missing}: ${doc.id} \u2192 ${parentId}`);
1169
+ }
1170
+ if (ancestors.has(parent.id)) {
1171
+ throw new Error(`${copy_error_doc_parent_cycle}: ${[...ancestors, parent.id].join(" \u2192 ")}`);
1172
+ }
1173
+ if (JSON.stringify(parent.data.group) !== JSON.stringify(doc.data.group)) {
1174
+ throw new Error(`${copy_error_doc_parent_group}: ${doc.id} \u2192 ${parent.id}`);
1175
+ }
1176
+ ancestors.add(parent.id);
1177
+ parentId = parent.data.parent;
1178
+ }
1179
+ return { ...doc, ancestors: [...ancestors].slice(1).toReversed() };
1180
+ });
1054
1181
  };
1055
1182
 
1056
1183
  // src/content/read-home.ts
@@ -1074,7 +1201,7 @@ var readHome = async (options) => {
1074
1201
 
1075
1202
  // src/dev.ts
1076
1203
  import { readFile as readFile3 } from "node:fs/promises";
1077
- import { extname } from "node:path";
1204
+ import { extname as extname2 } from "node:path";
1078
1205
  import { Hono as Hono2 } from "hono";
1079
1206
  import { streamSSE } from "hono/streaming";
1080
1207
  var createDevApp = (options) => {
@@ -1084,8 +1211,8 @@ var createDevApp = (options) => {
1084
1211
  if (file === void 0) {
1085
1212
  return c.notFound();
1086
1213
  }
1087
- c.header("content-type", asset_content_types[extname(file)] ?? asset_content_type_default);
1088
- if (extname(file) === ".woff2") {
1214
+ c.header("content-type", asset_content_types[extname2(file).toLowerCase()] ?? asset_content_type_default);
1215
+ if (extname2(file) === ".woff2") {
1089
1216
  c.header("Cache-Control", font_cache_control);
1090
1217
  }
1091
1218
  return c.body(await readFile3(file));
@@ -1169,25 +1296,16 @@ var toErrorMessage = (error) => {
1169
1296
  return error instanceof Error ? error.message : String(error);
1170
1297
  };
1171
1298
 
1172
- // src/util/file/to-asset-path.ts
1173
- import { createHash } from "node:crypto";
1174
- import { readFileSync } from "node:fs";
1175
- import { posix as posix2 } from "node:path";
1176
- var toAssetPath = (options) => {
1177
- const extension = posix2.extname(options.path);
1178
- return `${posix2.dirname(options.path)}/${posix2.basename(options.path, extension)}.${createHash("sha256").update(readFileSync(options.file)).digest("hex")}${extension}`;
1179
- };
1180
-
1181
1299
  // src/util/font/to-font-css.ts
1182
1300
  import { createHash as createHash2 } from "node:crypto";
1183
1301
  import { readFileSync as readFileSync2 } from "node:fs";
1184
- import { basename, dirname as dirname2, extname as extname2, join as join3 } from "node:path";
1302
+ import { basename, dirname as dirname3, extname as extname3, join as join3 } from "node:path";
1185
1303
  var toFontCss = (options) => {
1186
1304
  const files = /* @__PURE__ */ new Map();
1187
1305
  const css = [...readFileSync2(options.css, "utf8").matchAll(/@font-face\s*\{[^}]*\}/g)].map(
1188
1306
  (match) => match[0].replace(/font-display:\s*[^;]+;/g, `font-display: ${font_display_strategy};`).replace(/url\(([^)]+)\)/g, (_match, relativeUrl) => {
1189
- const file = join3(dirname2(options.css), relativeUrl);
1190
- const extension = extname2(file);
1307
+ const file = join3(dirname3(options.css), relativeUrl);
1308
+ const extension = extname3(file);
1191
1309
  const hash = createHash2("sha256").update(readFileSync2(file)).digest("hex").slice(0, font_hash_length);
1192
1310
  const url = `${options.fontDir}/${basename(file, extension)}.${hash}${extension}`;
1193
1311
  files.set(url, file);
@@ -1205,14 +1323,14 @@ var main = async () => {
1205
1323
  throw new Error(`${copy_error_unknown_command}: ${command} (${commands.join(", ")})`);
1206
1324
  }
1207
1325
  const kitRoot = fileURLToPath(new URL("../", import.meta.url));
1208
- const docsRoot = resolve2(docsDir);
1326
+ const docsRoot = resolve3(docsDir);
1209
1327
  const outDir = join4(docsRoot, "dist");
1210
1328
  if (command === "preview") {
1211
1329
  const app2 = new Hono3();
1212
1330
  app2.use(
1213
1331
  "/*",
1214
1332
  serveStatic({
1215
- root: relative3(process.cwd(), outDir),
1333
+ root: relative4(process.cwd(), outDir),
1216
1334
  onFound: (_path, c) => {
1217
1335
  if (c.req.path.startsWith(`${asset_font_dir}/`)) {
1218
1336
  c.header("Cache-Control", font_cache_control);
@@ -1224,7 +1342,7 @@ var main = async () => {
1224
1342
  return;
1225
1343
  }
1226
1344
  const configFile = join4(docsRoot, cli_config_file_name);
1227
- const parsedConfig = siteConfigSchema.safeParse(existsSync2(configFile) ? (await import(pathToFileURL(configFile).href)).default : site_config_absent);
1345
+ const parsedConfig = siteConfigSchema.safeParse(existsSync3(configFile) ? (await import(pathToFileURL(configFile).href)).default : site_config_absent);
1228
1346
  if (!parsedConfig.success) {
1229
1347
  throw new Error(`${copy_error_config}: ${parsedConfig.error.issues.map((issue) => `${issue.path.join(".")} ${issue.message}`).join(", ")}`);
1230
1348
  }
@@ -1239,7 +1357,7 @@ var main = async () => {
1239
1357
  const fontCss = [sans.css, mono.css, brand.css].join("\n");
1240
1358
  const stylePath = toAssetPath({ path: asset_style_path, file: join4(kitRoot, "dist/cli.css") });
1241
1359
  const clientPath = toAssetPath({ path: asset_client_path, file: join4(kitRoot, "dist/client.js") });
1242
- const files = new Map([
1360
+ const kitFiles = new Map([
1243
1361
  [stylePath, join4(kitRoot, "dist/cli.css")],
1244
1362
  [clientPath, join4(kitRoot, "dist/client.js")],
1245
1363
  [asset_favicon_path, join4(kitRoot, "src/asset/favicon.svg")],
@@ -1247,7 +1365,8 @@ var main = async () => {
1247
1365
  ...mono.files,
1248
1366
  ...brand.files
1249
1367
  ]);
1250
- const processor = createProcessor({ site: siteConfig, root: docsRoot });
1368
+ const files = new Map(kitFiles);
1369
+ const processor = createProcessor({ site: siteConfig, root: docsRoot, files });
1251
1370
  const [docs, home] = await Promise.all([readDocs({ root: docsRoot, processor }), readHome({ root: docsRoot, processor })]);
1252
1371
  const store = { docs, home };
1253
1372
  const app = createApp({
@@ -1262,25 +1381,34 @@ var main = async () => {
1262
1381
  throw result.error;
1263
1382
  }
1264
1383
  for (const [url, file] of files) {
1265
- await mkdir(dirname3(join4(outDir, url)), { recursive: true });
1384
+ await mkdir(dirname4(join4(outDir, url)), { recursive: true });
1266
1385
  await copyFile(file, join4(outDir, url));
1267
1386
  }
1268
- console.log(`${result.files.length} pages, ${files.size} assets \u2192 ${relative3(process.cwd(), outDir)}`);
1387
+ console.log(`${result.files.length} pages, ${files.size} assets \u2192 ${relative4(process.cwd(), outDir)}`);
1269
1388
  return;
1270
1389
  }
1271
1390
  const reload = new EventEmitter();
1272
1391
  let generation = 0;
1273
1392
  serve({ fetch: createDevApp({ pages: app, files, reload }).fetch, port: cli_dev_port }, (info) => console.log(`http://localhost:${info.port}/`));
1274
1393
  const handleDocsChange = async (_event, filename) => {
1275
- if (filename !== null && !/\.mdx?$/i.test(filename)) {
1276
- return;
1394
+ if (filename !== null) {
1395
+ const segments = filename.split(sep3);
1396
+ if (segments.includes("dist") || segments.includes("node_modules") || !/\.mdx?$/i.test(filename) && !asset_media_extensions.has(extname4(filename).toLowerCase())) {
1397
+ return;
1398
+ }
1277
1399
  }
1278
1400
  const currentGeneration = ++generation;
1279
1401
  try {
1280
- const [docs2, home2] = await Promise.all([readDocs({ root: docsRoot, processor }), readHome({ root: docsRoot, processor })]);
1402
+ const nextFiles = new Map(kitFiles);
1403
+ const nextProcessor = createProcessor({ site: siteConfig, root: docsRoot, files: nextFiles });
1404
+ const [docs2, home2] = await Promise.all([readDocs({ root: docsRoot, processor: nextProcessor }), readHome({ root: docsRoot, processor: nextProcessor })]);
1281
1405
  if (currentGeneration !== generation) {
1282
1406
  return;
1283
1407
  }
1408
+ files.clear();
1409
+ for (const entry of nextFiles) {
1410
+ files.set(...entry);
1411
+ }
1284
1412
  store.docs = docs2;
1285
1413
  store.home = home2;
1286
1414
  reload.emit("change");
package/dist/client.js CHANGED
@@ -1,5 +1,5 @@
1
- var L="fh-cursor-face";var S="(width < 1024px)";var p=e=>{if(!e)return null;try{return document.getElementById(decodeURIComponent(e.slice(1)))}catch(t){if(t instanceof URIError)return null;throw t}};var m=e=>{let t=p(e);for(let n=t?.closest("details");n;n=n.parentElement?.closest("details"))n.open=!0;return t};var k=()=>{let e=document.querySelector("[data-navigation]"),t=document.querySelector("[data-navigation-drawer]"),n=document.querySelector("[data-navigation-content]"),r=document.querySelector("[data-navigation-open]"),u=document.querySelector("[data-navigation-close]"),d=e?.parentElement;if(!e||!t||!n||!r||!u||!d)return;let s=matchMedia(S),l=()=>{t.open||(document.body.classList.remove("wg_shell__root--drawerOpen"),r.setAttribute("aria-expanded","false"))},h=()=>{t.close(),l()},a=()=>{!s.matches||t.open||(t.showModal(),document.body.classList.add("wg_shell__root--drawerOpen"),r.setAttribute("aria-expanded","true"))},c=o=>{let i=t.getBoundingClientRect();o.target===t&&(o.clientX<i.left||o.clientX>i.right||o.clientY<i.top||o.clientY>i.bottom)&&h()},q=o=>{if(!t.open||o.defaultPrevented||o.button!==0||o.metaKey||o.ctrlKey||o.shiftKey||o.altKey||!(o.target instanceof Element))return;let i=o.target.closest("a[href]");if(!(!(i instanceof HTMLAnchorElement)||i.target!==""&&i.target!=="_self"||i.hasAttribute("download"))&&(h(),i.origin===location.origin&&i.pathname===location.pathname&&i.search===location.search&&i.hash)){let T=m(i.hash);T&&(T.tabIndex=-1,T.focus({preventScroll:!0}))}},H=()=>{let o=document.activeElement;if(s.matches){r.classList.add("wg_shell__menuToggle--visible"),n.append(e),o instanceof HTMLElement&&e.contains(o)&&r.focus({preventScroll:!0});return}h(),d.append(e),o instanceof HTMLElement&&e.contains(o)?o.focus({preventScroll:!0}):(o===r||o instanceof HTMLElement&&t.contains(o))&&d.querySelector(".wg_shell__brand")?.focus({preventScroll:!0}),r.classList.remove("wg_shell__menuToggle--visible")};r.addEventListener("click",a),u.addEventListener("click",h),t.addEventListener("close",l),t.addEventListener("click",c),e.addEventListener("click",q),s.addEventListener("change",H),H(),d.classList.add("wg_shell__sidebar--navigationReady")};k();var g=document.querySelector("[data-cursor-face]");if(g){let e=matchMedia("(hover: hover) and (pointer: fine) and (prefers-reduced-motion: no-preference)"),t={x:0,y:0,frame:0},n=()=>{cancelAnimationFrame(t.frame),t.frame=0,g.classList.remove("wg_shell__cursorFace--visible")},r=()=>{e.matches||n()},u=()=>{t.frame===0&&(t.frame=requestAnimationFrame(()=>{t.frame=0,g.style.transform=`translate3d(
1
+ var L="fh-cursor-face";var k="(width < 1024px)";var p=e=>{if(!e)return null;try{return document.getElementById(decodeURIComponent(e.slice(1)))}catch(t){if(t instanceof URIError)return null;throw t}};var m=e=>{let t=p(e);for(let n=t?.closest("details");n;n=n.parentElement?.closest("details"))n.open=!0;return t};var S=()=>{let e=document.querySelector("[data-navigation]"),t=document.querySelector("[data-navigation-drawer]"),n=document.querySelector("[data-navigation-content]"),i=document.querySelector("[data-navigation-open]"),d=document.querySelector("[data-navigation-close]"),c=e?.parentElement;if(!e||!t||!n||!i||!d||!c)return;let l=matchMedia(k),a=()=>{t.open||(document.body.classList.remove("wg_shell__root--drawerOpen"),i.setAttribute("aria-expanded","false"))},u=()=>{t.close(),a()},h=()=>{!l.matches||t.open||(t.showModal(),document.body.classList.add("wg_shell__root--drawerOpen"),i.setAttribute("aria-expanded","true"))},r=o=>{let s=t.getBoundingClientRect();o.target===t&&(o.clientX<s.left||o.clientX>s.right||o.clientY<s.top||o.clientY>s.bottom)&&u()},q=o=>{if(!t.open||o.defaultPrevented||o.button!==0||o.metaKey||o.ctrlKey||o.shiftKey||o.altKey||!(o.target instanceof Element))return;let s=o.target.closest("a[href]");if(!(!(s instanceof HTMLAnchorElement)||s.target!==""&&s.target!=="_self"||s.hasAttribute("download"))&&(u(),s.origin===location.origin&&s.pathname===location.pathname&&s.search===location.search&&s.hash)){let T=m(s.hash);T&&(T.tabIndex=-1,T.focus({preventScroll:!0}))}},H=()=>{let o=document.activeElement;if(l.matches){i.classList.add("wg_shell__menuToggle--visible"),n.append(e),o instanceof HTMLElement&&e.contains(o)&&i.focus({preventScroll:!0});return}u(),c.append(e),o instanceof HTMLElement&&e.contains(o)?o.focus({preventScroll:!0}):(o===i||o instanceof HTMLElement&&t.contains(o))&&c.querySelector(".wg_shell__brand")?.focus({preventScroll:!0}),i.classList.remove("wg_shell__menuToggle--visible")};i.addEventListener("click",h),d.addEventListener("click",u),t.addEventListener("close",a),t.addEventListener("click",r),e.addEventListener("click",q),l.addEventListener("change",H),H(),c.classList.add("wg_shell__sidebar--navigationReady")};S();var g=document.querySelector("[data-cursor-face]");if(g){let e=matchMedia("(hover: hover) and (pointer: fine) and (prefers-reduced-motion: no-preference)"),t={x:0,y:0,frame:0},n=()=>{cancelAnimationFrame(t.frame),t.frame=0,g.classList.remove("wg_shell__cursorFace--visible")},i=()=>{e.matches||n()},d=()=>{t.frame===0&&(t.frame=requestAnimationFrame(()=>{t.frame=0,g.style.transform=`translate3d(
2
2
  ${Math.max(0,Math.min(t.x+16,document.documentElement.clientWidth-g.width-16))}px,
3
3
  ${Math.max(0,Math.min(t.y+16,document.documentElement.clientHeight-g.height-16))}px,
4
4
  0
5
- )`,g.classList.add("wg_shell__cursorFace--visible")}))},d=a=>{if(!(e.matches&&a.pointerType==="mouse")){n();return}t.x=a.clientX,t.y=a.clientY,u()},s=()=>{g.classList.contains("wg_shell__cursorFace--visible")&&u()},l=()=>{if(g.classList.contains("wg_shell__cursorFace--visible"))try{sessionStorage.setItem(L,JSON.stringify({x:t.x,y:t.y}))}catch{}},h=()=>{try{let a=sessionStorage.getItem(L);if(sessionStorage.removeItem(L),a!==null&&e.matches){let c=JSON.parse(a);typeof c=="object"&&c!==null&&"x"in c&&"y"in c&&typeof c.x=="number"&&typeof c.y=="number"&&Number.isFinite(c.x)&&Number.isFinite(c.y)&&(t.x=c.x,t.y=c.y,u())}}catch{}};h(),document.addEventListener("pointermove",d,{passive:!0}),document.addEventListener("pointerdown",d,{passive:!0}),document.documentElement.addEventListener("pointerleave",n),addEventListener("blur",n),addEventListener("resize",s),addEventListener("pagehide",l),addEventListener("pageshow",h),e.addEventListener("change",r)}var v=m(location.hash),_=null,b=0,f=null,x=()=>{clearTimeout(b),f?.abort(),f=null,_?.classList.remove("wg_prose__headingText--highlighted"),_=null},M=e=>{x(),_=e===null?null:e.querySelector(".wg_prose__headingText"),_!==null&&(_.classList.add("wg_prose__headingText--highlighted"),b=window.setTimeout(x,3e3),f=new AbortController,document.addEventListener("scrollend",B,{signal:f.signal}))},B=e=>{e.target===document&&(clearTimeout(b),f?.abort(),f=null,b=window.setTimeout(x,3e3))},N=e=>{if(!(e.defaultPrevented||e.button!==0||e.metaKey||e.ctrlKey||e.shiftKey||e.altKey)&&e.currentTarget instanceof HTMLAnchorElement&&(e.currentTarget.target===""||e.currentTarget.target==="_self")&&!e.currentTarget.hasAttribute("download")&&e.currentTarget.origin===location.origin&&e.currentTarget.pathname===location.pathname&&e.currentTarget.search===location.search&&e.currentTarget.hash){let t=m(e.currentTarget.hash);e.currentTarget.hash===location.hash&&M(t)}},K=()=>{let e=m(location.hash);e?.scrollIntoView(),M(e)};for(let e of document.querySelectorAll("a[href]"))e.addEventListener("click",N);addEventListener("hashchange",K);var y=[...document.querySelectorAll("[data-toc-link]")].flatMap(e=>{let t=p(e.hash),n=e.parentElement;return!t||!n?[]:[{heading:t,link:e,item:n,list:n.parentElement,subs:[...n.querySelectorAll("[data-toc-sub-link]")].flatMap(r=>{let u=p(r.hash),d=r.parentElement;return u&&d?[{heading:u,link:r,item:d}]:[]})}]}),w=y.at(0);if(w){let e=()=>{let t=Number.parseFloat(getComputedStyle(w.heading).scrollMarginTop)+1,n=y.findLast(s=>s.heading.checkVisibility()&&s.heading.getBoundingClientRect().top<=t)??w,r=n.subs.findLast(s=>s.heading.checkVisibility()&&s.heading.getBoundingClientRect().top<=t),u=y.indexOf(n),d=r===void 0?-1:n.subs.indexOf(r);for(let[s,l]of y.entries()){l.link.classList.toggle("wg_shellToc__link--active",l===n),l.item.classList.toggle("wg_shellToc__item--trail",l.list===n.list&&s<u),l.item.classList.toggle("wg_shellToc__item--current",l===n);for(let[h,a]of l.subs.entries())a.link.classList.toggle("wg_shellToc__subLink--active",a===r),a.item.classList.toggle("wg_shellToc__subItem--trail",l===n&&h<d),a.item.classList.toggle("wg_shellToc__subItem--current",a===r)}};addEventListener("scroll",e,{passive:!0}),addEventListener("resize",e),addEventListener("toggle",e,!0),e()}var I=new AbortController;for(let e of["wheel","touchmove","keydown","mousedown"])addEventListener(e,()=>I.abort(),{once:!0,passive:!0});var A=async()=>{await document.fonts.ready,v&&!I.signal.aborted&&(v.scrollIntoView({behavior:"instant"}),M(v)),document.documentElement.style.removeProperty("scroll-behavior")};document.readyState==="complete"?A():addEventListener("load",A,{once:!0});
5
+ )`,g.classList.add("wg_shell__cursorFace--visible")}))},c=h=>{if(!(e.matches&&h.pointerType==="mouse")){n();return}t.x=h.clientX,t.y=h.clientY,d()},l=()=>{g.classList.contains("wg_shell__cursorFace--visible")&&d()},a=()=>{if(g.classList.contains("wg_shell__cursorFace--visible"))try{sessionStorage.setItem(L,JSON.stringify({x:t.x,y:t.y}))}catch{}},u=()=>{try{let h=sessionStorage.getItem(L);if(sessionStorage.removeItem(L),h!==null&&e.matches){let r=JSON.parse(h);typeof r=="object"&&r!==null&&"x"in r&&"y"in r&&typeof r.x=="number"&&typeof r.y=="number"&&Number.isFinite(r.x)&&Number.isFinite(r.y)&&(t.x=r.x,t.y=r.y,d())}}catch{}};u(),document.addEventListener("pointermove",c,{passive:!0}),document.addEventListener("pointerdown",c,{passive:!0}),document.documentElement.addEventListener("pointerleave",n),addEventListener("blur",n),addEventListener("resize",l),addEventListener("pagehide",a),addEventListener("pageshow",u),e.addEventListener("change",i)}var v=m(location.hash),_=null,y=0,f=null,x=()=>{clearTimeout(y),f?.abort(),f=null,_?.classList.remove("wg_prose__headingText--highlighted"),_=null},M=e=>{x(),_=e===null?null:e.querySelector(".wg_prose__headingText"),_!==null&&(_.classList.add("wg_prose__headingText--highlighted"),y=window.setTimeout(x,3e3),f=new AbortController,document.addEventListener("scrollend",B,{signal:f.signal}))},B=e=>{e.target===document&&(clearTimeout(y),f?.abort(),f=null,y=window.setTimeout(x,3e3))},N=e=>{if(!(e.defaultPrevented||e.button!==0||e.metaKey||e.ctrlKey||e.shiftKey||e.altKey)&&e.currentTarget instanceof HTMLAnchorElement&&(e.currentTarget.target===""||e.currentTarget.target==="_self")&&!e.currentTarget.hasAttribute("download")&&e.currentTarget.origin===location.origin&&e.currentTarget.pathname===location.pathname&&e.currentTarget.search===location.search&&e.currentTarget.hash){let t=m(e.currentTarget.hash);e.currentTarget.hash===location.hash&&M(t)}},K=()=>{let e=m(location.hash);e?.scrollIntoView(),M(e)};for(let e of document.querySelectorAll("a[href]"))e.addEventListener("click",N);addEventListener("hashchange",K);var E=[...document.querySelectorAll("[data-toc-link]")].flatMap(e=>{let t=p(e.hash),n=e.parentElement;return!t||!n?[]:[{heading:t,link:e,item:n,list:n.parentElement,subs:[...n.querySelectorAll("[data-toc-sub-link]")].flatMap(i=>{let d=p(i.hash),c=i.parentElement;return d&&c?[{heading:d,link:i,item:c}]:[]})}]}),w=E.at(0);if(w){let e=()=>{let t=Number.parseFloat(getComputedStyle(w.heading).scrollMarginTop)+1,n=E.findLast(l=>l.heading.checkVisibility()&&l.heading.getBoundingClientRect().top<=t)??w,i=n.subs.findLast(l=>l.heading.checkVisibility()&&l.heading.getBoundingClientRect().top<=t),d=E.indexOf(n),c=i===void 0?-1:n.subs.indexOf(i);for(let[l,a]of E.entries()){let u=a===n&&i===void 0;a.link.classList.toggle("wg_shellToc__link--active",u),u?a.link.setAttribute("aria-current","location"):a.link.removeAttribute("aria-current"),a.item.classList.toggle("wg_shellToc__item--trail",a.list===n.list&&l<d),a.item.classList.toggle("wg_shellToc__item--current",a===n);for(let[h,r]of a.subs.entries())r.link.classList.toggle("wg_shellToc__subLink--active",r===i),r===i?r.link.setAttribute("aria-current","location"):r.link.removeAttribute("aria-current"),r.item.classList.toggle("wg_shellToc__subItem--trail",a===n&&h<c),r.item.classList.toggle("wg_shellToc__subItem--current",r===i)}};addEventListener("scroll",e,{passive:!0}),addEventListener("resize",e),addEventListener("toggle",e,!0),e()}var I=new AbortController;for(let e of["wheel","touchmove","keydown","mousedown"])addEventListener(e,()=>I.abort(),{once:!0,passive:!0});var A=async()=>{await document.fonts.ready,v&&!I.signal.aborted&&(v.scrollIntoView({behavior:"instant"}),M(v)),document.documentElement.style.removeProperty("scroll-behavior")};document.readyState==="complete"?A():addEventListener("load",A,{once:!0});
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "for-humanity",
3
- "version": "0.2.1",
3
+ "version": "0.4.0",
4
4
  "description": "Documents written for humans to read: a docs kit with a sidebar, parts, sections and flowcharts, built on Hono and React",
5
5
  "keywords": [
6
6
  "docs",
package/src/cli.ts CHANGED
@@ -7,14 +7,14 @@
7
7
  import {EventEmitter} from "node:events";
8
8
  import {existsSync, watch} from "node:fs";
9
9
  import {copyFile, mkdir, rm, writeFile} from "node:fs/promises";
10
- import {basename, dirname, join, relative, resolve} from "node:path";
10
+ import {basename, dirname, extname, join, relative, resolve, sep} from "node:path";
11
11
  import {fileURLToPath, pathToFileURL} from "node:url";
12
12
  import {serve} from "@hono/node-server";
13
13
  import {serveStatic} from "@hono/node-server/serve-static";
14
14
  import {Hono} from "hono";
15
15
  import {toSSG} from "hono/ssg";
16
16
  import {createApp} from "@/app";
17
- import {asset_client_path, asset_favicon_path, asset_font_dir, asset_style_path} from "@/constant/asset";
17
+ import {asset_client_path, asset_favicon_path, asset_font_dir, asset_media_extensions, asset_style_path} from "@/constant/asset";
18
18
  import {cli_config_file_name, cli_default_command, cli_default_docs_dir, cli_dev_port} from "@/constant/cli";
19
19
  import {copy_error_config, copy_error_font_preload, copy_error_prefix, copy_error_unknown_command} from "@/constant/copy";
20
20
  import {font_brand_css, font_cache_control, font_mono_css, font_sans_css, font_sans_preload_file} from "@/constant/font";
@@ -85,7 +85,7 @@ const main = async () => {
85
85
  const fontCss = [sans.css, mono.css, brand.css].join("\n");
86
86
  const stylePath = toAssetPath({path: asset_style_path, file: join(kitRoot, "dist/cli.css")});
87
87
  const clientPath = toAssetPath({path: asset_client_path, file: join(kitRoot, "dist/client.js")});
88
- const files = new Map([
88
+ const kitFiles = new Map([
89
89
  [stylePath, join(kitRoot, "dist/cli.css")],
90
90
  [clientPath, join(kitRoot, "dist/client.js")],
91
91
  [asset_favicon_path, join(kitRoot, "src/asset/favicon.svg")],
@@ -93,7 +93,8 @@ const main = async () => {
93
93
  ...mono.files,
94
94
  ...brand.files,
95
95
  ]);
96
- const processor = createProcessor({site: siteConfig, root: docsRoot});
96
+ const files = new Map(kitFiles);
97
+ const processor = createProcessor({site: siteConfig, root: docsRoot, files});
97
98
  const [docs, home] = await Promise.all([readDocs({root: docsRoot, processor}), readHome({root: docsRoot, processor})]);
98
99
  const store = {docs, home};
99
100
  const app = createApp({
@@ -132,20 +133,30 @@ const main = async () => {
132
133
  * 재처리 실패 시 직전 정상 문서 유지
133
134
  */
134
135
  const handleDocsChange = async (_event: string, filename: string | null) => {
135
- if (filename !== null && !/\.mdx?$/i.test(filename)) {
136
- return;
136
+ if (filename !== null) {
137
+ const segments = filename.split(sep);
138
+ // 출력·설치 폴더는 제외 · 실패한 참조의 파일이 뒤늦게 생겨도 다시 처리
139
+ if (segments.includes("dist") || segments.includes("node_modules") || (!/\.mdx?$/i.test(filename) && !asset_media_extensions.has(extname(filename).toLowerCase()))) {
140
+ return;
141
+ }
137
142
  }
138
143
 
139
144
  const currentGeneration = ++generation;
140
145
 
141
146
  try {
142
- const [docs, home] = await Promise.all([readDocs({root: docsRoot, processor}), readHome({root: docsRoot, processor})]);
147
+ const nextFiles = new Map(kitFiles);
148
+ const nextProcessor = createProcessor({site: siteConfig, root: docsRoot, files: nextFiles});
149
+ const [docs, home] = await Promise.all([readDocs({root: docsRoot, processor: nextProcessor}), readHome({root: docsRoot, processor: nextProcessor})]);
143
150
 
144
151
  // 최신 변경의 처리 결과만 반영
145
152
  if (currentGeneration !== generation) {
146
153
  return;
147
154
  }
148
155
 
156
+ files.clear();
157
+ for (const entry of nextFiles) {
158
+ files.set(...entry);
159
+ }
149
160
  store.docs = docs;
150
161
  store.home = home;
151
162
  reload.emit("change");
package/src/client.ts CHANGED
@@ -275,12 +275,23 @@ if (firstSection) {
275
275
  const currentSubIndex = sub === undefined ? -1 : current.subs.indexOf(sub);
276
276
 
277
277
  for (const [index, section] of tocSections.entries()) {
278
- section.link.classList.toggle("wg_shellToc__link--active", section === current);
278
+ const active = section === current && sub === undefined;
279
+ section.link.classList.toggle("wg_shellToc__link--active", active);
280
+ if (active) {
281
+ section.link.setAttribute("aria-current", "location");
282
+ } else {
283
+ section.link.removeAttribute("aria-current");
284
+ }
279
285
  section.item.classList.toggle("wg_shellToc__item--trail", section.list === current.list && index < currentIndex);
280
286
  section.item.classList.toggle("wg_shellToc__item--current", section === current);
281
287
 
282
288
  for (const [subIndex, item] of section.subs.entries()) {
283
289
  item.link.classList.toggle("wg_shellToc__subLink--active", item === sub);
290
+ if (item === sub) {
291
+ item.link.setAttribute("aria-current", "location");
292
+ } else {
293
+ item.link.removeAttribute("aria-current");
294
+ }
284
295
  item.item.classList.toggle("wg_shellToc__subItem--trail", section === current && subIndex < currentSubIndex);
285
296
  item.item.classList.toggle("wg_shellToc__subItem--current", item === sub);
286
297
  }