for-humanity 0.13.0 → 0.13.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.
@@ -1,4 +1,4 @@
1
- import { S as copy_nav_toc_label, T as copy_warn_unknown_status, _ as copy_home_missing, a as copy_error_doc_id_clash, b as copy_nav_open, c as copy_error_doc_parent_missing, g as copy_error_unknown_directive, h as copy_error_snippet, i as copy_error_doc_id, l as copy_error_frontmatter, o as copy_error_doc_parent_cycle, r as copy_error_directive_syntax, s as copy_error_doc_parent_group, t as copy_error_asset, u as copy_error_home_clash, v as copy_nav_aria_label, w as copy_warn_broken_link, x as copy_nav_title, y as copy_nav_close } from "./copy-C1uzvEnr.js";
1
+ import { S as copy_nav_toc_label, T as copy_warn_unknown_status, a as copy_error_doc_id_clash, b as copy_nav_open, c as copy_error_doc_parent_missing, g as copy_error_unknown_directive, h as copy_error_snippet, i as copy_error_doc_id, l as copy_error_frontmatter, o as copy_error_doc_parent_cycle, r as copy_error_directive_syntax, s as copy_error_doc_parent_group, t as copy_error_asset, u as copy_error_home_clash, v as copy_nav_aria_label, w as copy_warn_broken_link, x as copy_nav_title, y as copy_nav_close } from "./copy-C1uzvEnr.js";
2
2
  import { t as logger } from "./logger-Dv1i9xSV.js";
3
3
  import { t as groupPathSchema } from "./group-path-XAThmzvC.js";
4
4
  import { t as toChangeQueue } from "./to-change-queue-DbEnZGNK.js";
@@ -21,10 +21,12 @@ import { SKIP, visit } from "unist-util-visit";
21
21
  import { createHash } from "node:crypto";
22
22
  import Slugger from "github-slugger";
23
23
  import { toString } from "hast-util-to-string";
24
+ import { dropRightWhile, dropWhile, minBy, takeWhile, uniqBy } from "es-toolkit/array";
24
25
  import { transformerMetaHighlight, transformerNotationDiff, transformerNotationErrorLevel, transformerNotationHighlight, transformerNotationWordHighlight } from "@shikijs/transformers";
25
26
  import { toString as toString$1 } from "mdast-util-to-string";
26
27
  import { escape, escapeRegExp } from "es-toolkit/string";
27
28
  import { findAndReplace } from "mdast-util-find-and-replace";
29
+ import { isEqual } from "es-toolkit/predicate";
28
30
  import { VFile } from "vfile";
29
31
  import { parse } from "yaml";
30
32
  import { Hono } from "hono";
@@ -33,7 +35,6 @@ import { ssgParams } from "hono/ssg";
33
35
  import clsx from "clsx";
34
36
  import { jsx, jsxs } from "hono/jsx/jsx-runtime";
35
37
  import { createHead, escapeHtml } from "unhead/server";
36
- import { takeWhile } from "es-toolkit/array";
37
38
  import { Fragment, useEffect, useLayoutEffect, useRef, useState } from "hono/jsx";
38
39
  import { flushSync } from "hono/jsx/dom";
39
40
  //#region src/app/_constant/crawling.ts
@@ -212,11 +213,12 @@ var snippet_language_by_extension = {
212
213
  ".yml": "yaml"
213
214
  };
214
215
  //#endregion
215
- //#region src/app/_function/create-processor/_remark-snippets.ts
216
+ //#region src/app/_function/create-processor/_to-snippet-body.ts
216
217
  /**
217
218
  * region 지정 시 해당 범위만 추출, 미지정 시 전체 사용
218
219
  *
219
220
  * - region 경계 줄과 공통 들여쓰기 제거
221
+ * - 빈 region은 빈 문자열, 없는 region은 undefined 반환
220
222
  */
221
223
  var toSnippetBody = (source, region) => {
222
224
  const lines = source.replace(/\r\n/g, "\n").split("\n");
@@ -235,12 +237,14 @@ var toSnippetBody = (source, region) => {
235
237
  if (inside) kept.push(line);
236
238
  }
237
239
  if (!found) return;
238
- while (kept.length > 0 && kept[0]?.trim() === "") kept.shift();
239
- while (kept.length > 0 && kept.at(-1)?.trim() === "") kept.pop();
240
- const indents = kept.filter((line) => line.trim() !== "").map((line) => line.length - line.trimStart().length);
241
- const indent = indents.length === 0 ? 0 : Math.min(...indents);
242
- return kept.map((line) => line.slice(Math.min(indent, line.length - line.trimStart().length))).join("\n");
240
+ const content = dropRightWhile(dropWhile(kept, (line) => line.trim() === ""), (line) => line.trim() === "");
241
+ const leastIndentedLine = minBy(content.filter((line) => line.trim() !== ""), (line) => line.length - line.trimStart().length);
242
+ if (leastIndentedLine === void 0) return "";
243
+ const indent = leastIndentedLine.length - leastIndentedLine.trimStart().length;
244
+ return content.map((line) => line.slice(Math.min(indent, line.length - line.trimStart().length))).join("\n");
243
245
  };
246
+ //#endregion
247
+ //#region src/app/_function/create-processor/_remark-snippets.ts
244
248
  /**
245
249
  * 문서 상대 경로의 코드 스니펫 삽입
246
250
  *
@@ -286,49 +290,43 @@ var shikiTransformers = [
286
290
  transformerNotationWordHighlight({ matchAlgorithm: "v3" }),
287
291
  transformerMetaHighlight(),
288
292
  {
289
- name: "for-humanity:line-numbers",
290
- pre(node) {
291
- const meta = this.options.meta?.__raw;
292
- if (typeof meta === "string" && /\bline-numbers\b/.test(meta)) this.addClassToHast(node, "wg_prose__lineNumbers");
293
- }
294
- },
295
- {
296
- name: "for-humanity:code-tone",
297
- pre(node) {
298
- const meta = this.options.meta?.__raw;
299
- if (typeof meta !== "string") return;
300
- for (const word of ["bad", "good"]) if (new RegExp(`\\b${word}\\b`).test(meta)) this.addClassToHast(node, `wg_prose__code--${word}`);
301
- }
302
- },
303
- {
304
- name: "for-humanity:code-title",
293
+ name: "for-humanity:code-metadata",
305
294
  root(root) {
306
- const meta = this.options.meta?.__raw;
307
- const title = typeof meta === "string" ? /\[([^\]]+)\]/.exec(meta)?.[1] : void 0;
295
+ this.pre.properties.style = void 0;
296
+ this.addClassToHast(this.pre, "wg_prose__code");
297
+ if (this.options.meta?.__raw === void 0) return;
298
+ if (/\bline-numbers\b/.test(this.options.meta.__raw)) this.addClassToHast(this.pre, "wg_prose__lineNumbers");
299
+ const isBad = /\bbad\b/.test(this.options.meta.__raw);
300
+ const isGood = /\bgood\b/.test(this.options.meta.__raw);
301
+ if (isBad) this.addClassToHast(this.pre, "wg_prose__code--bad");
302
+ if (isGood) this.addClassToHast(this.pre, "wg_prose__code--good");
303
+ const title = /\[([^\]]+)\]/.exec(this.options.meta.__raw)?.[1];
308
304
  if (title === void 0) return;
309
- const tones = typeof meta === "string" ? ["bad", "good"].filter((word) => new RegExp(`\\b${word}\\b`).test(meta)) : [];
310
- const caption = {
305
+ root.children = [{
311
306
  type: "element",
312
- tagName: "figcaption",
313
- properties: { className: ["wg_prose__codeFileTitle"] },
307
+ tagName: "figure",
308
+ properties: { className: ["wg_prose__codeFile"] },
314
309
  children: [{
315
- type: "text",
316
- value: title
317
- }, {
318
310
  type: "element",
319
- tagName: "span",
320
- properties: { className: ["wg_prose__codeFileLang"] },
311
+ tagName: "figcaption",
312
+ properties: { className: [
313
+ "wg_prose__codeFileTitle",
314
+ ...isBad ? ["wg_prose__codeFileTitle--bad"] : [],
315
+ ...isGood ? ["wg_prose__codeFileTitle--good"] : []
316
+ ] },
321
317
  children: [{
322
318
  type: "text",
323
- value: this.options.lang
319
+ value: title
320
+ }, {
321
+ type: "element",
322
+ tagName: "span",
323
+ properties: { className: ["wg_prose__codeFileLang"] },
324
+ children: [{
325
+ type: "text",
326
+ value: this.options.lang
327
+ }]
324
328
  }]
325
- }]
326
- };
327
- root.children = [{
328
- type: "element",
329
- tagName: "figure",
330
- properties: { className: ["wg_prose__codeFile", ...tones.map((tone) => `wg_prose__codeFile--${tone}`)] },
331
- children: [caption, ...root.children.filter((child) => child.type !== "doctype")]
329
+ }, ...root.children.filter((child) => child.type !== "doctype")]
332
330
  }];
333
331
  }
334
332
  }
@@ -363,25 +361,25 @@ var section_heading_depth = {
363
361
  * name 제목과 첫 절 앞 본문의 공통 머리. README는 원문의 h1 사용
364
362
  */
365
363
  var rehypeHead = () => (tree, file) => {
366
- const frontmatter = file.data.fh?.frontmatter;
367
364
  const firstSection = tree.children.findIndex((node) => node.type === "element" && node.tagName === `h${section_heading_depth.section}`);
368
365
  const leadEnd = firstSection === -1 ? tree.children.length : firstSection;
369
- if (frontmatter === void 0 && leadEnd === 0) return;
370
- tree.children = [{
366
+ const head = {
371
367
  type: "element",
372
368
  tagName: "header",
373
369
  properties: { className: ["wg_prose__head"] },
374
- children: [...tree.children.slice(0, leadEnd).filter((node) => node.type !== "doctype")]
375
- }, ...tree.children.slice(leadEnd)];
376
- if (frontmatter !== void 0 && tree.children[0].type === "element") tree.children[0].children.unshift({
370
+ children: tree.children.slice(0, leadEnd).filter((node) => node.type !== "doctype")
371
+ };
372
+ if (file.data.fh?.frontmatter !== void 0) head.children.unshift({
377
373
  type: "element",
378
374
  tagName: "h1",
379
- properties: {},
375
+ properties: { className: ["wg_prose__title"] },
380
376
  children: [{
381
377
  type: "text",
382
- value: frontmatter.name
378
+ value: file.data.fh.frontmatter.name
383
379
  }]
384
380
  });
381
+ else if (leadEnd === 0) return;
382
+ tree.children = [head, ...tree.children.slice(leadEnd)];
385
383
  };
386
384
  //#endregion
387
385
  //#region src/component/widget/prose/_function/rehype-sections/_to-section-headings.ts
@@ -407,7 +405,9 @@ var toSectionHeadings = (node) => {
407
405
  */
408
406
  var rehypeSections = () => (tree, file) => {
409
407
  if (file.data.fh === void 0) return;
410
- file.data.fh.sections = toSectionHeadings(tree).map((heading) => ({ part: typeof heading.properties.dataPart === "string" ? heading.properties.dataPart : void 0 }));
408
+ file.data.fh.sections = toSectionHeadings(tree).map((heading) => {
409
+ return { part: typeof heading.properties.dataPart === "string" ? heading.properties.dataPart : void 0 };
410
+ });
411
411
  };
412
412
  //#endregion
413
413
  //#region src/component/widget/prose/_function/rehype-tables.ts
@@ -626,12 +626,13 @@ var remarkUnknownDirectives = () => (tree, file) => {
626
626
  *
627
627
  * - 코드와 문서 머리 생성 후 제목 ID와 가름 수집
628
628
  * - 플러그인 HTML 조각은 마지막 rehype-raw에서 해석
629
- * - Shiki inline style: token.css의 --app-code-* 참조
629
+ * - 코드 상자의 색상은 prose CSS 소유, Shiki 토큰 색상은 --app-code-* 참조
630
630
  */
631
631
  var createProcessor = (options) => {
632
+ const files = options.files ?? /* @__PURE__ */ new Map();
632
633
  return unified().use(remarkParse).use(remarkFrontmatter, ["yaml"]).use(remarkGfm).use(remarkSnippets, { root: options.root }).use(remarkSmartypants).use(remarkDirective).use(remarkBlocks).use(remarkParts).use(remarkMermaid, {
633
634
  assets: options.mermaid,
634
- files: options.files ?? /* @__PURE__ */ new Map()
635
+ files
635
636
  }).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, {
636
637
  theme: createCssVariablesTheme({
637
638
  name: "for-humanity",
@@ -640,7 +641,7 @@ var createProcessor = (options) => {
640
641
  transformers: shikiTransformers
641
642
  }).use(rehypeHead).use(rehypeHeadingIds).use(rehypeSections).use(rehypeTables).use(rehypeRaw).use(rehypeAssets, {
642
643
  root: options.root,
643
- files: options.files ?? /* @__PURE__ */ new Map()
644
+ files
644
645
  }).use(rehypeStringify, { allowDangerousHtml: true });
645
646
  };
646
647
  //#endregion
@@ -759,7 +760,7 @@ var readDocs = async (options) => {
759
760
  const parent = docsById.get(parentId);
760
761
  if (parent === void 0) throw new Error(`${copy_error_doc_parent_missing}: ${doc.id} → ${parentId}`);
761
762
  if (ancestors.has(parent.id)) throw new Error(`${copy_error_doc_parent_cycle}: ${[...ancestors, parent.id].join(" → ")}`);
762
- if (JSON.stringify(parent.data.group) !== JSON.stringify(doc.data.group)) throw new Error(`${copy_error_doc_parent_group}: ${doc.id} → ${parent.id}`);
763
+ if (!isEqual(parent.data.group, doc.data.group)) throw new Error(`${copy_error_doc_parent_group}: ${doc.id} → ${parent.id}`);
763
764
  ancestors.add(parent.id);
764
765
  parentId = parent.data.parent;
765
766
  }
@@ -808,11 +809,13 @@ var readHome = async (options) => {
808
809
  var toCrawlingApp = (params) => {
809
810
  const app = new Hono();
810
811
  app.get(crawling_robots_path, (c) => c.text(`User-agent: *\nAllow: /\n\nSitemap: ${params.url}${crawling_sitemap_path}\n`));
811
- app.get(crawling_sitemap_path, (_c) => new Response(html`<?xml version="1.0" encoding="UTF-8"?>
812
+ app.get(crawling_sitemap_path, (_c) => {
813
+ return new Response(html`<?xml version="1.0" encoding="UTF-8"?>
812
814
  <urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
813
815
  <url><loc>${params.url}/</loc></url>
814
816
  ${params.store.docs.map((doc) => html`<url><loc>${params.url}/${doc.id.split("/").map(encodeURIComponent).join("/")}/</loc></url>`)}
815
- </urlset>`.toString(), { headers: { "Content-Type": "application/xml; charset=utf-8" } }));
817
+ </urlset>`.toString(), { headers: { "Content-Type": "application/xml; charset=utf-8" } });
818
+ });
816
819
  return app;
817
820
  };
818
821
  //#endregion
@@ -863,6 +866,14 @@ var toHttpApp = (options) => {
863
866
  return app;
864
867
  };
865
868
  //#endregion
869
+ //#region src/app/_function/to-page-app/_to-html.ts
870
+ /**
871
+ * Hono JSX 페이지의 HTML 문서 선언, 서버와 정적 출력의 공통 직렬화
872
+ */
873
+ var toHtml = (page) => {
874
+ return html`<!DOCTYPE html>${page}`;
875
+ };
876
+ //#endregion
866
877
  //#region src/component/widget/prose/wg-prose.tsx
867
878
  var WgProse = (props) => {
868
879
  if (props.html !== void 0) return /* @__PURE__ */ jsx("article", {
@@ -873,7 +884,13 @@ var WgProse = (props) => {
873
884
  className: clsx("wg_prose__root"),
874
885
  children: props.head !== void 0 && /* @__PURE__ */ jsxs("header", {
875
886
  className: clsx("wg_prose__head"),
876
- children: [/* @__PURE__ */ jsx("h1", { children: props.head.title }), props.head.lead !== void 0 && /* @__PURE__ */ jsx("p", { children: props.head.lead })]
887
+ children: [/* @__PURE__ */ jsx("h1", {
888
+ className: clsx("wg_prose__title"),
889
+ children: props.head.title
890
+ }), props.head.lead !== void 0 && /* @__PURE__ */ jsx("p", {
891
+ className: clsx("wg_prose__lead"),
892
+ children: props.head.lead
893
+ })]
877
894
  })
878
895
  });
879
896
  };
@@ -931,10 +948,13 @@ var toDocGroupBranches = (options) => {
931
948
  /**
932
949
  * 메타데이터 경로로 읽는 순서를 가진 문서 계층 구성
933
950
  *
934
- * - navigation의 첫 지정을 조상에도 적용. 미지정 형제 묶음은 이름순
951
+ * - 같은 조상의 중복 지정은 최초 순서 유지
952
+ * - 미지정 형제 묶음은 이름순
935
953
  */
936
954
  var toDocGroups = (options) => {
937
- const positions = new Map(options.navigation.flatMap((path, index) => path.map((_name, depth) => [JSON.stringify(path.slice(0, depth + 1)), index])).toReversed());
955
+ const positions = new Map(uniqBy(options.navigation.flatMap((path, index) => {
956
+ return path.map((_name, depth) => [JSON.stringify(path.slice(0, depth + 1)), index]);
957
+ }), (entry) => entry[0]));
938
958
  const children = Map.groupBy(options.docs, (doc) => doc.data.parent);
939
959
  return toDocGroupBranches({
940
960
  docs: options.docs,
@@ -992,15 +1012,23 @@ var toTocGroups = (outline) => {
992
1012
  heading,
993
1013
  section: outline.sections[index]
994
1014
  }));
995
- const sections = entries.flatMap((entry, index) => entry.heading.depth === section_heading_depth.section ? [{
996
- heading: entry.heading,
997
- part: entry.section.part,
998
- subs: takeWhile(entries.slice(index + 1), (sub) => sub.heading.depth !== section_heading_depth.section).map((sub) => ({ heading: sub.heading }))
999
- }] : []);
1000
- return sections.flatMap((section, index) => index === 0 || section.part !== void 0 ? [{
1001
- part: section.part,
1002
- sections: [section, ...takeWhile(sections.slice(index + 1), (next) => next.part === void 0)]
1003
- }] : []);
1015
+ const sections = entries.flatMap((entry, index) => {
1016
+ if (entry.heading.depth !== section_heading_depth.section) return [];
1017
+ return [{
1018
+ heading: entry.heading,
1019
+ part: entry.section.part,
1020
+ subs: takeWhile(entries.slice(index + 1), (sub) => sub.heading.depth !== section_heading_depth.section).map((sub) => {
1021
+ return { heading: sub.heading };
1022
+ })
1023
+ }];
1024
+ });
1025
+ return sections.flatMap((section, index) => {
1026
+ if (index > 0 && section.part === void 0) return [];
1027
+ return [{
1028
+ part: section.part,
1029
+ sections: [section, ...takeWhile(sections.slice(index + 1), (next) => next.part === void 0)]
1030
+ }];
1031
+ });
1004
1032
  };
1005
1033
  //#endregion
1006
1034
  //#region src/component/widget/navigation/_function/to-navigation-data/to-navigation-data.ts
@@ -1366,30 +1394,6 @@ var navigation_mobile_query = "(width < 1024px)";
1366
1394
  */
1367
1395
  var navigation_wide_query = "(width >= 1360px)";
1368
1396
  //#endregion
1369
- //#region src/component/widget/navigation/_function/restore-navigation-scroll.ts
1370
- /**
1371
- * 탐색 진입점에서 첫 paint 전 복원. 기존 배치별 저장 자료도 한 번 수용
1372
- */
1373
- var restoreNavigationScroll = (options) => {
1374
- const navigation = document.querySelector("[data-navigation]");
1375
- const documents = document.querySelector("[data-navigation-scroll=\"documents\"]");
1376
- const outline = document.querySelector("[data-navigation-scroll=\"outline\"]");
1377
- if (!navigation || !documents || !outline) return;
1378
- navigation.dataset.navigationLayout = options.layout;
1379
- try {
1380
- const value = sessionStorage.getItem(options.key);
1381
- const stored = value === null ? void 0 : JSON.parse(value);
1382
- const state = typeof stored === "object" && stored !== null && "version" in stored && stored.version === options.version && "state" in stored ? options.toState(stored.state) : { positions: {} };
1383
- const legacy = sessionStorage.getItem(`${options.key}:${options.layout === "drawer" ? "drawer" : "desktop"}`);
1384
- const previous = state.positions[options.layout] === void 0 && legacy !== null ? options.toState({ positions: { [options.layout]: JSON.parse(legacy) } }).positions[options.layout] : void 0;
1385
- const position = state.positions[options.layout] ?? previous;
1386
- const signature = [...navigation.querySelectorAll(".wg_navigationNav__root a[href]")].map((link) => `${link.pathname}:${link.textContent}`).join("\n");
1387
- if (position === void 0 || position.navigation !== signature) return;
1388
- documents.scrollTop = position.documents;
1389
- if (position.pathname === location.pathname) outline.scrollTop = position.outline;
1390
- } catch {}
1391
- };
1392
- //#endregion
1393
1397
  //#region src/component/widget/navigation/_wg-navigation-nav-list.tsx
1394
1398
  var WgNavigationNavList = (props) => {
1395
1399
  const entries = [...props.group.docs.map((doc) => ({
@@ -1404,24 +1408,26 @@ var WgNavigationNavList = (props) => {
1404
1408
  return /* @__PURE__ */ jsx("ul", {
1405
1409
  className: clsx("wg_navigationNavList__root"),
1406
1410
  "aria-label": props.group.name,
1407
- children: entries.map((entry) => /* @__PURE__ */ jsxs("li", {
1408
- className: clsx("wg_navigationNavList__item"),
1409
- children: [entry.kind === "document" && /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx("a", {
1410
- className: clsx("wg_navigationNavList__link", { "wg_navigationNavList__link--active": entry.doc.active }),
1411
- href: `/${entry.doc.id}/`,
1412
- title: entry.doc.label,
1413
- "aria-current": entry.doc.active ? "page" : void 0,
1414
- children: entry.doc.name
1415
- }), entry.doc.children.length > 0 && /* @__PURE__ */ jsx(WgNavigationNavList, { group: {
1416
- name: entry.doc.name,
1417
- path: props.group.path,
1418
- docs: entry.doc.children,
1419
- groups: []
1420
- } })] }), entry.kind === "group" && /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx("span", {
1421
- className: clsx("wg_navigationNavList__branch"),
1422
- children: entry.group.name
1423
- }), /* @__PURE__ */ jsx(WgNavigationNavList, { group: entry.group })] })]
1424
- }, entry.key))
1411
+ children: entries.map((entry) => {
1412
+ return /* @__PURE__ */ jsxs("li", {
1413
+ className: clsx("wg_navigationNavList__item"),
1414
+ children: [entry.kind === "document" && /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx("a", {
1415
+ className: clsx("wg_navigationNavList__link", { "wg_navigationNavList__link--active": entry.doc.active }),
1416
+ href: `/${entry.doc.id}/`,
1417
+ title: entry.doc.label,
1418
+ "aria-current": entry.doc.active ? "page" : void 0,
1419
+ children: entry.doc.name
1420
+ }), entry.doc.children.length > 0 && /* @__PURE__ */ jsx(WgNavigationNavList, { group: {
1421
+ name: entry.doc.name,
1422
+ path: props.group.path,
1423
+ docs: entry.doc.children,
1424
+ groups: []
1425
+ } })] }), entry.kind === "group" && /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx("span", {
1426
+ className: clsx("wg_navigationNavList__branch"),
1427
+ children: entry.group.name
1428
+ }), /* @__PURE__ */ jsx(WgNavigationNavList, { group: entry.group })] })]
1429
+ }, entry.key);
1430
+ })
1425
1431
  });
1426
1432
  };
1427
1433
  //#endregion
@@ -1432,14 +1438,16 @@ var WgNavigationNav = (props) => {
1432
1438
  "aria-label": copy_nav_aria_label,
1433
1439
  children: /* @__PURE__ */ jsx("div", {
1434
1440
  className: clsx("wg_navigationNav__docs"),
1435
- children: props.groups.map((group) => /* @__PURE__ */ jsxs("section", {
1436
- className: clsx("wg_navigationNav__docGroup"),
1437
- "aria-label": group.name,
1438
- children: [/* @__PURE__ */ jsx("div", {
1439
- className: clsx("wg_navigationNav__group"),
1440
- children: group.name
1441
- }), /* @__PURE__ */ jsx(WgNavigationNavList, { group })]
1442
- }, JSON.stringify(group.path)))
1441
+ children: props.groups.map((group) => {
1442
+ return /* @__PURE__ */ jsxs("section", {
1443
+ className: clsx("wg_navigationNav__docGroup"),
1444
+ "aria-label": group.name,
1445
+ children: [/* @__PURE__ */ jsx("div", {
1446
+ className: clsx("wg_navigationNav__group"),
1447
+ children: group.name
1448
+ }), /* @__PURE__ */ jsx(WgNavigationNavList, { group })]
1449
+ }, JSON.stringify(group.path));
1450
+ })
1443
1451
  })
1444
1452
  });
1445
1453
  };
@@ -1579,72 +1587,46 @@ var WgNavigationToc = (props) => {
1579
1587
  children: [/* @__PURE__ */ jsx("div", {
1580
1588
  className: clsx("wg_navigationToc__label"),
1581
1589
  children: copy_nav_toc_label
1582
- }), props.groups.map((group) => /* @__PURE__ */ jsxs("div", {
1583
- className: clsx("wg_navigationToc__groupSection", { "wg_navigationToc__groupSection--named": group.part !== void 0 }),
1584
- children: [group.part !== void 0 && /* @__PURE__ */ jsx("div", {
1585
- className: clsx("wg_navigationToc__group"),
1586
- children: group.part
1587
- }), /* @__PURE__ */ jsx("ul", {
1588
- className: clsx("wg_navigationToc__list"),
1589
- children: group.sections.map((section) => /* @__PURE__ */ jsxs("li", {
1590
- className: clsx("wg_navigationToc__item"),
1591
- children: [/* @__PURE__ */ jsx("a", {
1592
- className: clsx("wg_navigationToc__link", { "wg_navigationToc__link--active": section.heading.slug === currentSlug }),
1593
- href: `#${section.heading.slug}`,
1594
- "data-toc-link": "",
1595
- "aria-current": section.heading.slug === currentSlug ? "location" : void 0,
1596
- children: section.heading.text
1597
- }), section.subs.length > 0 && /* @__PURE__ */ jsx("ul", {
1598
- className: clsx("wg_navigationToc__sub"),
1599
- children: section.subs.map((sub) => /* @__PURE__ */ jsx("li", {
1600
- className: clsx("wg_navigationToc__subItem"),
1601
- children: /* @__PURE__ */ jsx("a", {
1602
- className: clsx("wg_navigationToc__subLink", { "wg_navigationToc__subLink--active": sub.heading.slug === currentSlug }),
1603
- href: `#${sub.heading.slug}`,
1604
- "data-toc-sub-link": "",
1605
- "aria-current": sub.heading.slug === currentSlug ? "location" : void 0,
1606
- children: sub.heading.text
1607
- })
1608
- }, sub.heading.slug))
1609
- })]
1610
- }, section.heading.slug))
1611
- })]
1612
- }, group.sections[0].heading.slug))]
1590
+ }), props.groups.map((group) => {
1591
+ return /* @__PURE__ */ jsxs("div", {
1592
+ className: clsx("wg_navigationToc__groupSection", { "wg_navigationToc__groupSection--named": group.part !== void 0 }),
1593
+ children: [group.part !== void 0 && /* @__PURE__ */ jsx("div", {
1594
+ className: clsx("wg_navigationToc__group"),
1595
+ children: group.part
1596
+ }), /* @__PURE__ */ jsx("ul", {
1597
+ className: clsx("wg_navigationToc__list"),
1598
+ children: group.sections.map((section) => {
1599
+ return /* @__PURE__ */ jsxs("li", {
1600
+ className: clsx("wg_navigationToc__item"),
1601
+ children: [/* @__PURE__ */ jsx("a", {
1602
+ className: clsx("wg_navigationToc__link", { "wg_navigationToc__link--active": section.heading.slug === currentSlug }),
1603
+ href: `#${section.heading.slug}`,
1604
+ "data-toc-link": "",
1605
+ "aria-current": section.heading.slug === currentSlug ? "location" : void 0,
1606
+ children: section.heading.text
1607
+ }), section.subs.length > 0 && /* @__PURE__ */ jsx("ul", {
1608
+ className: clsx("wg_navigationToc__sub"),
1609
+ children: section.subs.map((sub) => {
1610
+ return /* @__PURE__ */ jsx("li", {
1611
+ className: clsx("wg_navigationToc__subItem"),
1612
+ children: /* @__PURE__ */ jsx("a", {
1613
+ className: clsx("wg_navigationToc__subLink", { "wg_navigationToc__subLink--active": sub.heading.slug === currentSlug }),
1614
+ href: `#${sub.heading.slug}`,
1615
+ "data-toc-sub-link": "",
1616
+ "aria-current": sub.heading.slug === currentSlug ? "location" : void 0,
1617
+ children: sub.heading.text
1618
+ })
1619
+ }, sub.heading.slug);
1620
+ })
1621
+ })]
1622
+ }, section.heading.slug);
1623
+ })
1624
+ })]
1625
+ }, group.sections[0].heading.slug);
1626
+ })]
1613
1627
  });
1614
1628
  };
1615
1629
  //#endregion
1616
- //#region src/store/navigation/navigation-storage.ts
1617
- /**
1618
- * 같은 탭에서 문서 이동 후 탐색 위치 복원. 데스크톱과 드로어는 별도 저장
1619
- */
1620
- var navigation_scroll_storage_key = "for-humanity:navigation-scroll";
1621
- //#endregion
1622
- //#region src/store/navigation/to-navigation-scroll-state.ts
1623
- /**
1624
- * 알 수 없는 저장 형태, 음수, 무한 좌표 제외. 초기 HTML과 Zustand에 같은 검증 적용
1625
- */
1626
- var toNavigationScrollState = (value) => {
1627
- /**
1628
- * 한 배치의 완전한 좌표만 복원 대상으로 수용
1629
- */
1630
- const toPosition = (position) => {
1631
- if (typeof position !== "object" || position === null || !("navigation" in position) || typeof position.navigation !== "string" || !("pathname" in position) || typeof position.pathname !== "string" || !("documents" in position) || typeof position.documents !== "number" || !Number.isFinite(position.documents) || position.documents < 0 || !("outline" in position) || typeof position.outline !== "number" || !Number.isFinite(position.outline) || position.outline < 0) return;
1632
- return {
1633
- navigation: position.navigation,
1634
- pathname: position.pathname,
1635
- documents: position.documents,
1636
- outline: position.outline
1637
- };
1638
- };
1639
- if (typeof value !== "object" || value === null || !("positions" in value) || typeof value.positions !== "object" || value.positions === null) return { positions: {} };
1640
- const positions = {
1641
- wide: toPosition("wide" in value.positions ? value.positions.wide : void 0),
1642
- desktop: toPosition("desktop" in value.positions ? value.positions.desktop : void 0),
1643
- drawer: toPosition("drawer" in value.positions ? value.positions.drawer : void 0)
1644
- };
1645
- return { positions: Object.fromEntries(Object.entries(positions).filter((entry) => entry[1] !== void 0)) };
1646
- };
1647
- //#endregion
1648
1630
  //#region src/util/dom/reveal-hash-target.ts
1649
1631
  /**
1650
1632
  * 접힌 내용의 hash 대상과 상위 Details 공개
@@ -1678,6 +1660,7 @@ var WgNavigation = (props) => {
1678
1660
  const layoutRef = useRef(layout);
1679
1661
  const focusRef = useRef(null);
1680
1662
  const restorePendingRef = useRef(true);
1663
+ const navigationSignature = JSON.stringify(props.data.groups, (key, value) => key === "active" ? void 0 : value);
1681
1664
  /**
1682
1665
  * 저장된 배치와 현재 CSS 조건을 구분. 조건 변경 직후 잘린 좌표를 이전 배치에 저장하지 않음
1683
1666
  */
@@ -1699,31 +1682,23 @@ var WgNavigation = (props) => {
1699
1682
  return layoutRef.current === "drawer" ? drawerDocumentsRef.current : documentsRef.current;
1700
1683
  };
1701
1684
  /**
1702
- * 모든 링크의 URL, 표시명으로 사이트 공통 문서 목록의 동일성 판정
1703
- */
1704
- const getNavigationSignature = () => {
1705
- const navigation = getNavigationPort();
1706
- if (navigation === null) return void 0;
1707
- return [...navigation.querySelectorAll(".wg_navigationNav__root a[href]")].map((link) => `${link.pathname}:${link.textContent}`).join("\n");
1708
- };
1709
- /**
1710
1685
  * 닫기, 이동보다 먼저 실제 좌표 보존. 숨긴 모달과 아직 복원하지 않은 배치는 저장하지 않음
1711
1686
  */
1712
1687
  const saveScroll = () => {
1713
1688
  const documents = getDocumentsPort();
1714
- if (props.stores === void 0 || documents === null || outlineRef.current === null || restorePendingRef.current || layoutRef.current !== getNavigationLayout() || layoutRef.current === "drawer" && dialogRef.current?.open !== true) return;
1715
- const signature = getNavigationSignature();
1716
- if (signature === void 0) return;
1717
- const navigationPosition = {
1718
- navigation: signature,
1719
- pathname: location.pathname,
1720
- documents: documents.scrollTop,
1721
- outline: outlineRef.current.scrollTop
1722
- };
1723
- props.stores.scroll.setState((state) => ({ positions: {
1724
- ...state.positions,
1725
- [layoutRef.current]: navigationPosition
1726
- } }));
1689
+ const outline = outlineRef.current;
1690
+ if (props.stores === void 0 || documents === null || outline === null || restorePendingRef.current || layoutRef.current !== getNavigationLayout() || layoutRef.current === "drawer" && dialogRef.current?.open !== true) return;
1691
+ props.stores.scroll.setState((state) => {
1692
+ return { positions: {
1693
+ ...state.positions,
1694
+ [layoutRef.current]: {
1695
+ navigation: navigationSignature,
1696
+ pathname: location.pathname,
1697
+ documents: documents.scrollTop,
1698
+ outline: outline.scrollTop
1699
+ }
1700
+ } };
1701
+ });
1727
1702
  };
1728
1703
  /**
1729
1704
  * 연결된 영역에서 첫 paint 전 복원. 문서는 페이지 간 공유하고 목차는 같은 경로에서만 재사용
@@ -1731,25 +1706,16 @@ var WgNavigation = (props) => {
1731
1706
  const restoreScroll = () => {
1732
1707
  layoutRef.current = getNavigationLayout();
1733
1708
  const documents = getDocumentsPort();
1734
- const navigation = getNavigationPort();
1735
- if (props.stores === void 0 || documents === null || outlineRef.current === null || navigation === null) return;
1709
+ if (props.stores === void 0 || documents === null || outlineRef.current === null) return;
1736
1710
  if (layoutRef.current === "drawer" && dialogRef.current?.open !== true) return;
1737
- const signature = getNavigationSignature();
1738
- if (signature === void 0) return;
1739
1711
  const position = props.stores.scroll.getState().positions[layoutRef.current];
1740
- documents.scrollTop = 0;
1741
- outlineRef.current.scrollTop = 0;
1742
- if (position === void 0) restoreNavigationScroll({
1743
- key: navigation_scroll_storage_key,
1744
- version: 1,
1745
- layout: layoutRef.current,
1746
- toState: toNavigationScrollState
1747
- });
1748
- else if (position.navigation === signature) {
1712
+ if (position?.navigation === navigationSignature) {
1749
1713
  documents.scrollTop = position.documents;
1750
- if (position.pathname === location.pathname) outlineRef.current.scrollTop = position.outline;
1714
+ outlineRef.current.scrollTop = position.pathname === location.pathname ? position.outline : 0;
1715
+ } else {
1716
+ documents.scrollTop = 0;
1717
+ outlineRef.current.scrollTop = 0;
1751
1718
  }
1752
- navigation.dataset.navigationLayout = layoutRef.current;
1753
1719
  restorePendingRef.current = false;
1754
1720
  saveScroll();
1755
1721
  documents.dispatchEvent(new Event("scroll"));
@@ -1834,12 +1800,12 @@ var WgNavigation = (props) => {
1834
1800
  flushSync(() => setLayout(nextLayout));
1835
1801
  };
1836
1802
  /**
1837
- * BFCache 복귀 시 저장 좌표로 동기 렌더 후 복원
1803
+ * BFCache 복귀 시 persist의 최신 저장 좌표를 다시 읽어 복원
1838
1804
  */
1839
1805
  const handlePageShow = (event) => {
1840
1806
  if (!event.persisted || props.stores === void 0) return;
1841
1807
  restorePendingRef.current = true;
1842
- flushSync(() => props.stores?.scroll.persist.rehydrate());
1808
+ props.stores.scroll.persist.rehydrate();
1843
1809
  handleLayoutChange();
1844
1810
  restoreScroll();
1845
1811
  };
@@ -1921,6 +1887,7 @@ var WgNavigation = (props) => {
1921
1887
  ref: props.stores === void 0 ? void 0 : navigationRef,
1922
1888
  className: clsx("wg_navigation__navigation"),
1923
1889
  "data-navigation": "",
1890
+ "data-navigation-layout": layout,
1924
1891
  children: /* @__PURE__ */ jsx(WgNavigationScroll, {
1925
1892
  portRef: documentsRef,
1926
1893
  kind: "documents",
@@ -1983,6 +1950,7 @@ var WgNavigation = (props) => {
1983
1950
  ref: props.stores === void 0 ? void 0 : drawerNavigationRef,
1984
1951
  className: clsx("wg_navigation__navigation"),
1985
1952
  "data-navigation": "",
1953
+ "data-navigation-layout": layout,
1986
1954
  children: /* @__PURE__ */ jsx(WgNavigationScroll, {
1987
1955
  portRef: drawerDocumentsRef,
1988
1956
  kind: "documents",
@@ -2224,25 +2192,19 @@ var PgDoc = (props) => {
2224
2192
  //#endregion
2225
2193
  //#region src/page/home/pg-home.tsx
2226
2194
  var PgHome = (props) => {
2227
- return /* @__PURE__ */ jsx(WgShell, {
2195
+ return /* @__PURE__ */ jsxs(WgShell, {
2228
2196
  site: props.site,
2229
2197
  docs: props.docs,
2230
2198
  assets: props.assets,
2231
2199
  outline: props.home?.outline,
2232
- children: props.home === void 0 ? /* @__PURE__ */ jsx(WgProse, { head: {
2200
+ children: [props.home !== void 0 && /* @__PURE__ */ jsx(WgProse, { html: props.home.html }), props.home === void 0 && /* @__PURE__ */ jsx(WgProse, { head: {
2233
2201
  title: props.site.title,
2234
- lead: copy_home_missing
2235
- } }) : /* @__PURE__ */ jsx(WgProse, { html: props.home.html })
2202
+ lead: "문서 폴더에 README.md를 추가하면 이곳에 홈 화면으로 표시됩니다."
2203
+ } })]
2236
2204
  });
2237
2205
  };
2238
2206
  //#endregion
2239
- //#region src/app/_function/to-page-app.tsx
2240
- /**
2241
- * Hono JSX 페이지에 HTML 문서 선언 추가. 서버와 정적 출력에서 같은 렌더러 사용
2242
- */
2243
- var toHtml = (page) => {
2244
- return html`<!DOCTYPE html>${page}`;
2245
- };
2207
+ //#region src/app/_function/to-page-app/to-page-app.tsx
2246
2208
  /**
2247
2209
  * 첫 화면 /. 문서 /:slug{.+}/. 하위 폴더 id 지원
2248
2210
  *
@@ -2278,14 +2240,16 @@ var toPageApp = (options) => {
2278
2240
  var toFontCss = (options) => {
2279
2241
  const files = /* @__PURE__ */ new Map();
2280
2242
  return {
2281
- css: [...readFileSync(options.css, "utf8").matchAll(/@font-face\s*\{[^}]*\}/g)].map((match) => match[0].replace(/font-display:\s*[^;]+;/g, `font-display: ${font_display_strategy};`).replace(/url\(([^)]+)\)/g, (_match, relativeUrl) => {
2282
- const file = join(dirname(options.css), relativeUrl);
2283
- const extension = extname(file);
2284
- const hash = createHash("sha256").update(readFileSync(file)).digest("hex").slice(0, 12);
2285
- const url = `${options.fontDir}/${basename(file, extension)}.${hash}${extension}`;
2286
- files.set(url, file);
2287
- return `url(${url})`;
2288
- })).join("\n"),
2243
+ css: [...readFileSync(options.css, "utf8").matchAll(/@font-face\s*\{[^}]*\}/g)].map((match) => {
2244
+ return match[0].replace(/font-display:\s*[^;]+;/g, `font-display: ${font_display_strategy};`).replace(/url\(([^)]+)\)/g, (_match, relativeUrl) => {
2245
+ const file = join(dirname(options.css), relativeUrl);
2246
+ const extension = extname(file);
2247
+ const hash = createHash("sha256").update(readFileSync(file)).digest("hex").slice(0, 12);
2248
+ const url = `${options.fontDir}/${basename(file, extension)}.${hash}${extension}`;
2249
+ files.set(url, file);
2250
+ return `url(${url})`;
2251
+ });
2252
+ }).join("\n"),
2289
2253
  files
2290
2254
  };
2291
2255
  };