for-humanity 0.14.0 → 0.14.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 +15 -2
- package/README.md +7 -7
- package/dist/browser.js +4 -4
- package/dist/cli.css +1 -1
- package/dist/cli.js +4 -4
- package/dist/{load-site-CBJGMNfH.js → load-site-BpLDY-YA.js} +484 -403
- package/dist/{logger-Dv1i9xSV.js → logger-CLH67K3L.js} +1 -1
- package/dist/{run-site-command-DPG5RNIN.js → run-site-command--qSrkFie.js} +4 -4
- package/dist/site.js +1 -1
- package/dist/{start-dev-server-x7Ja5uwv.js → start-dev-server-BL2HA7v6.js} +1 -2
- package/package.json +1 -1
- package/src/asset/font/architects-daughter/architects-daughter.css +1 -1
- package/src/asset/font/ibm-plex-sans-kr/ibm-plex-sans-kr.css +3 -3
- package/src/asset/font/monoplex-kr/monoplex-kr.css +2 -2
|
@@ -1,5 +1,5 @@
|
|
|
1
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
|
-
import { t as logger } from "./logger-
|
|
2
|
+
import { t as logger } from "./logger-CLH67K3L.js";
|
|
3
3
|
import { t as groupPathSchema } from "./group-path-XAThmzvC.js";
|
|
4
4
|
import { t as toChangeQueue } from "./to-change-queue-DbEnZGNK.js";
|
|
5
5
|
import { readFile, readdir } from "node:fs/promises";
|
|
@@ -21,7 +21,7 @@ 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
|
+
import { dropRightWhile, dropWhile, minBy, takeWhile, uniqBy, zip } from "es-toolkit/array";
|
|
25
25
|
import { transformerMetaHighlight, transformerNotationDiff, transformerNotationErrorLevel, transformerNotationHighlight, transformerNotationWordHighlight } from "@shikijs/transformers";
|
|
26
26
|
import { toString as toString$1 } from "mdast-util-to-string";
|
|
27
27
|
import { escape, escapeRegExp } from "es-toolkit/string";
|
|
@@ -210,8 +210,7 @@ var rehypeContentLinks = (options) => (tree, file) => {
|
|
|
210
210
|
]) {
|
|
211
211
|
const value = node.properties[property];
|
|
212
212
|
if (typeof value !== "string") continue;
|
|
213
|
-
const
|
|
214
|
-
for (const url of urls) {
|
|
213
|
+
for (const url of property === "srcSet" ? value.split(",").map((entry) => entry.trim().split(/\s+/)[0]) : [value]) {
|
|
215
214
|
if (/^(?:[a-z][a-z\d+.-]*:|\/\/|#)/i.test(url)) continue;
|
|
216
215
|
const path = decodeURIComponent(url.split(/[?#]/)[0]);
|
|
217
216
|
try {
|
|
@@ -262,7 +261,7 @@ var rehypeHeadingIds = () => (tree, file) => {
|
|
|
262
261
|
node.children = [{
|
|
263
262
|
type: "element",
|
|
264
263
|
tagName: "span",
|
|
265
|
-
properties: { className: ["
|
|
264
|
+
properties: { className: ["wg_article__headingText"] },
|
|
266
265
|
children: node.children
|
|
267
266
|
}];
|
|
268
267
|
});
|
|
@@ -309,11 +308,10 @@ var snippet_language_by_extension = {
|
|
|
309
308
|
* - 빈 region은 빈 문자열, 없는 region은 undefined 반환
|
|
310
309
|
*/
|
|
311
310
|
var toSnippetBody = (source, region) => {
|
|
312
|
-
const lines = source.replace(/\r\n/g, "\n").split("\n");
|
|
313
311
|
let inside = region === void 0;
|
|
314
312
|
let found = region === void 0;
|
|
315
313
|
const kept = [];
|
|
316
|
-
for (const line of
|
|
314
|
+
for (const line of source.replace(/\r\n/g, "\n").split("\n")) {
|
|
317
315
|
const marker = snippet_region_pattern.exec(line);
|
|
318
316
|
if (marker !== null) {
|
|
319
317
|
if (region !== void 0 && marker[2] === region) {
|
|
@@ -367,10 +365,9 @@ var remarkSnippets = (options) => (tree, file) => {
|
|
|
367
365
|
return;
|
|
368
366
|
}
|
|
369
367
|
const extension = extname(actual).toLowerCase();
|
|
370
|
-
const lang = snippet_language_by_extension[extension] === void 0 ? extension.slice(1) : snippet_language_by_extension[extension];
|
|
371
368
|
parent.children.splice(index, 1, {
|
|
372
369
|
type: "code",
|
|
373
|
-
lang,
|
|
370
|
+
lang: snippet_language_by_extension[extension] === void 0 ? extension.slice(1) : snippet_language_by_extension[extension],
|
|
374
371
|
meta: meta === void 0 ? null : meta,
|
|
375
372
|
value: body
|
|
376
373
|
});
|
|
@@ -389,26 +386,26 @@ var shikiTransformers = [
|
|
|
389
386
|
name: "for-humanity:code-metadata",
|
|
390
387
|
root(root) {
|
|
391
388
|
this.pre.properties.style = void 0;
|
|
392
|
-
this.addClassToHast(this.pre, "
|
|
389
|
+
this.addClassToHast(this.pre, "wg_article__code");
|
|
393
390
|
if (this.options.meta?.__raw === void 0) return;
|
|
394
|
-
if (/\bline-numbers\b/.test(this.options.meta.__raw)) this.addClassToHast(this.pre, "
|
|
391
|
+
if (/\bline-numbers\b/.test(this.options.meta.__raw)) this.addClassToHast(this.pre, "wg_article__lineNumbers");
|
|
395
392
|
const isBad = /\bbad\b/.test(this.options.meta.__raw);
|
|
396
393
|
const isGood = /\bgood\b/.test(this.options.meta.__raw);
|
|
397
|
-
if (isBad) this.addClassToHast(this.pre, "
|
|
398
|
-
if (isGood) this.addClassToHast(this.pre, "
|
|
394
|
+
if (isBad) this.addClassToHast(this.pre, "wg_article__code--bad");
|
|
395
|
+
if (isGood) this.addClassToHast(this.pre, "wg_article__code--good");
|
|
399
396
|
const title = /\[([^\]]+)\]/.exec(this.options.meta.__raw)?.[1];
|
|
400
397
|
if (title === void 0) return;
|
|
401
398
|
root.children = [{
|
|
402
399
|
type: "element",
|
|
403
400
|
tagName: "figure",
|
|
404
|
-
properties: { className: ["
|
|
401
|
+
properties: { className: ["wg_article__codeFile"] },
|
|
405
402
|
children: [{
|
|
406
403
|
type: "element",
|
|
407
404
|
tagName: "figcaption",
|
|
408
405
|
properties: { className: [
|
|
409
|
-
"
|
|
410
|
-
...isBad ? ["
|
|
411
|
-
...isGood ? ["
|
|
406
|
+
"wg_article__codeFileTitle",
|
|
407
|
+
...isBad ? ["wg_article__codeFileTitle--bad"] : [],
|
|
408
|
+
...isGood ? ["wg_article__codeFileTitle--good"] : []
|
|
412
409
|
] },
|
|
413
410
|
children: [{
|
|
414
411
|
type: "text",
|
|
@@ -416,7 +413,7 @@ var shikiTransformers = [
|
|
|
416
413
|
}, {
|
|
417
414
|
type: "element",
|
|
418
415
|
tagName: "span",
|
|
419
|
-
properties: { className: ["
|
|
416
|
+
properties: { className: ["wg_article__codeFileLang"] },
|
|
420
417
|
children: [{
|
|
421
418
|
type: "text",
|
|
422
419
|
value: this.options.lang
|
|
@@ -428,22 +425,7 @@ var shikiTransformers = [
|
|
|
428
425
|
}
|
|
429
426
|
];
|
|
430
427
|
//#endregion
|
|
431
|
-
//#region src/component/widget/
|
|
432
|
-
/**
|
|
433
|
-
* Mermaid 원문과 브라우저 렌더 영역. JavaScript 비활성 환경에서도 원문 유지
|
|
434
|
-
*/
|
|
435
|
-
var remarkMermaid = (options) => (tree) => {
|
|
436
|
-
visit(tree, "code", (node, index, parent) => {
|
|
437
|
-
if (node.lang !== "mermaid" || parent === void 0 || index === void 0) return;
|
|
438
|
-
if (options.assets !== void 0) for (const [url, path] of options.assets.files) options.files.set(url, path);
|
|
439
|
-
parent.children[index] = {
|
|
440
|
-
type: "html",
|
|
441
|
-
value: `<figure class="wg_mermaid__root" data-mermaid${options.assets === void 0 ? "" : ` data-mermaid-module="${options.assets.entry}"`}><div class="wg_mermaid__result" hidden></div><pre class="wg_mermaid__source"><code data-mermaid-source>${node.value.replaceAll("&", "&").replaceAll("<", "<").replaceAll(">", ">")}</code></pre><p class="wg_mermaid__error" role="status" hidden></p></figure>`
|
|
442
|
-
};
|
|
443
|
-
});
|
|
444
|
-
};
|
|
445
|
-
//#endregion
|
|
446
|
-
//#region src/component/widget/prose/_constant/section.ts
|
|
428
|
+
//#region src/component/widget/article/_constant/section.ts
|
|
447
429
|
/**
|
|
448
430
|
* 목차 대상 제목 깊이. h2 절, h3 소제목
|
|
449
431
|
*/
|
|
@@ -452,7 +434,7 @@ var section_heading_depth = {
|
|
|
452
434
|
sub: 3
|
|
453
435
|
};
|
|
454
436
|
//#endregion
|
|
455
|
-
//#region src/component/widget/
|
|
437
|
+
//#region src/component/widget/article/_function/rehype-head.ts
|
|
456
438
|
/**
|
|
457
439
|
* name 제목과 첫 절 앞 본문의 공통 머리. README는 원문의 h1 사용
|
|
458
440
|
*/
|
|
@@ -462,13 +444,13 @@ var rehypeHead = () => (tree, file) => {
|
|
|
462
444
|
const head = {
|
|
463
445
|
type: "element",
|
|
464
446
|
tagName: "header",
|
|
465
|
-
properties: { className: ["
|
|
447
|
+
properties: { className: ["wg_article__head"] },
|
|
466
448
|
children: tree.children.slice(0, leadEnd).filter((node) => node.type !== "doctype")
|
|
467
449
|
};
|
|
468
450
|
if (file.data.fh?.frontmatter !== void 0) head.children.unshift({
|
|
469
451
|
type: "element",
|
|
470
452
|
tagName: "h1",
|
|
471
|
-
properties: { className: ["
|
|
453
|
+
properties: { className: ["wg_article__title"] },
|
|
472
454
|
children: [{
|
|
473
455
|
type: "text",
|
|
474
456
|
value: file.data.fh.frontmatter.name
|
|
@@ -478,7 +460,7 @@ var rehypeHead = () => (tree, file) => {
|
|
|
478
460
|
tree.children = [head, ...tree.children.slice(leadEnd)];
|
|
479
461
|
};
|
|
480
462
|
//#endregion
|
|
481
|
-
//#region src/component/widget/
|
|
463
|
+
//#region src/component/widget/article/_function/rehype-sections/_to-section-headings.ts
|
|
482
464
|
/**
|
|
483
465
|
* 목차 대상 h2, h3. 중첩 블록까지 문서 순서 수집
|
|
484
466
|
*
|
|
@@ -492,7 +474,7 @@ var toSectionHeadings = (node) => {
|
|
|
492
474
|
});
|
|
493
475
|
};
|
|
494
476
|
//#endregion
|
|
495
|
-
//#region src/component/widget/
|
|
477
|
+
//#region src/component/widget/article/_function/rehype-sections/rehype-sections.ts
|
|
496
478
|
/**
|
|
497
479
|
* h2, h3의 가름 메타데이터 수집. 작성한 제목은 그대로 유지
|
|
498
480
|
*
|
|
@@ -506,7 +488,7 @@ var rehypeSections = () => (tree, file) => {
|
|
|
506
488
|
});
|
|
507
489
|
};
|
|
508
490
|
//#endregion
|
|
509
|
-
//#region src/component/widget/
|
|
491
|
+
//#region src/component/widget/article/_function/rehype-tables.ts
|
|
510
492
|
/**
|
|
511
493
|
* 넓은 표의 내부 가로 스크롤 래퍼
|
|
512
494
|
*/
|
|
@@ -516,14 +498,14 @@ var rehypeTables = () => (tree) => {
|
|
|
516
498
|
parent.children[index] = {
|
|
517
499
|
type: "element",
|
|
518
500
|
tagName: "div",
|
|
519
|
-
properties: { className: ["
|
|
501
|
+
properties: { className: ["wg_article__table"] },
|
|
520
502
|
children: [node]
|
|
521
503
|
};
|
|
522
504
|
return [SKIP, index + 1];
|
|
523
505
|
});
|
|
524
506
|
};
|
|
525
507
|
//#endregion
|
|
526
|
-
//#region src/component/widget/
|
|
508
|
+
//#region src/component/widget/article/_function/has-block-content.ts
|
|
527
509
|
/**
|
|
528
510
|
* 부품 본문의 내용 판정. 참조 정의, 주석, 빈 텍스트와 빈 구조 제외
|
|
529
511
|
*
|
|
@@ -538,7 +520,7 @@ var hasBlockContent = (node) => {
|
|
|
538
520
|
return true;
|
|
539
521
|
};
|
|
540
522
|
//#endregion
|
|
541
|
-
//#region src/component/widget/
|
|
523
|
+
//#region src/component/widget/article/_function/remark-blocks.ts
|
|
542
524
|
/**
|
|
543
525
|
* Note, Details의 제목, 본문, 속성, 닫힘 계약
|
|
544
526
|
*
|
|
@@ -558,7 +540,6 @@ var remarkBlocks = () => (tree, file) => {
|
|
|
558
540
|
if (!node.children.slice(1).some(hasBlockContent)) file.fail(`${copy_error_directive_syntax}: ${node.name} · 본문 필수`, node);
|
|
559
541
|
const unsupported = node.attributes ? Object.keys(node.attributes).find((key) => node.name !== "details" || key !== "open") : void 0;
|
|
560
542
|
if (unsupported !== void 0) file.fail(`${copy_error_directive_syntax}: ${node.name} · 미지원 속성 ${unsupported}`, node);
|
|
561
|
-
const open = node.attributes?.open;
|
|
562
543
|
const attributeSource = source.slice(label.position?.end.offset, node.position?.end.offset).split(/\r?\n/, 1)[0].trim();
|
|
563
544
|
if (attributeSource !== "" && (node.name !== "details" || !/^\{[ \t]*open[ \t]*\}$/.test(attributeSource))) file.fail(`${copy_error_directive_syntax}: ${node.name} · 허용 속성은 Details의 {open}, 나머지는 생략`, node);
|
|
564
545
|
const original = source.slice(node.position?.start.offset, node.position?.end.offset);
|
|
@@ -581,23 +562,23 @@ var remarkBlocks = () => (tree, file) => {
|
|
|
581
562
|
label.data = {
|
|
582
563
|
...label.data,
|
|
583
564
|
hName: node.name === "note" ? "strong" : "summary",
|
|
584
|
-
hProperties: { className: [node.name === "note" ? "
|
|
565
|
+
hProperties: { className: [node.name === "note" ? "wg_article__noteTitle" : "wg_article__detailsSummary"] }
|
|
585
566
|
};
|
|
586
567
|
node.data = {
|
|
587
568
|
...node.data,
|
|
588
569
|
hName: node.name === "note" ? "aside" : "details",
|
|
589
570
|
hProperties: node.name === "note" ? {
|
|
590
|
-
className: ["
|
|
571
|
+
className: ["wg_article__note"],
|
|
591
572
|
ariaLabel: title
|
|
592
573
|
} : {
|
|
593
|
-
className: ["
|
|
594
|
-
open: open === ""
|
|
574
|
+
className: ["wg_article__details"],
|
|
575
|
+
open: node.attributes?.open === ""
|
|
595
576
|
}
|
|
596
577
|
};
|
|
597
578
|
});
|
|
598
579
|
};
|
|
599
580
|
//#endregion
|
|
600
|
-
//#region src/component/widget/
|
|
581
|
+
//#region src/component/widget/article/_function/remark-links.ts
|
|
601
582
|
/**
|
|
602
583
|
* 상대 Markdown 링크의 사이트 URL 변환, query와 hash 원문 유지
|
|
603
584
|
*
|
|
@@ -634,7 +615,7 @@ var remarkLinks = (options) => (tree, file) => {
|
|
|
634
615
|
});
|
|
635
616
|
};
|
|
636
617
|
//#endregion
|
|
637
|
-
//#region src/component/widget/
|
|
618
|
+
//#region src/component/widget/article/_function/remark-parts.ts
|
|
638
619
|
/**
|
|
639
620
|
* ::part[이름]의 본문 가름과 다음 h2의 data-part
|
|
640
621
|
*
|
|
@@ -646,7 +627,7 @@ var remarkParts = () => (tree) => {
|
|
|
646
627
|
const section = tree.children.slice(index + 1).find((next) => next.type === "heading" && next.depth === section_heading_depth.section);
|
|
647
628
|
node.data = {
|
|
648
629
|
hName: "div",
|
|
649
|
-
hProperties: { className: ["
|
|
630
|
+
hProperties: { className: ["wg_article__part"] }
|
|
650
631
|
};
|
|
651
632
|
if (section) section.data = {
|
|
652
633
|
...section.data,
|
|
@@ -658,7 +639,7 @@ var remarkParts = () => (tree) => {
|
|
|
658
639
|
}
|
|
659
640
|
};
|
|
660
641
|
//#endregion
|
|
661
|
-
//#region src/component/widget/
|
|
642
|
+
//#region src/component/widget/article/_constant/status.ts
|
|
662
643
|
/**
|
|
663
644
|
* 상태 문구 뒤 날짜 패턴. YYYY-MM-DD
|
|
664
645
|
*/
|
|
@@ -668,7 +649,7 @@ var status_date_pattern = String.raw`\d{4}-\d{2}-\d{2}`;
|
|
|
668
649
|
*/
|
|
669
650
|
var status_candidate_pattern = new RegExp(String.raw`\([^()\n]*${status_date_pattern}\)`, "g");
|
|
670
651
|
//#endregion
|
|
671
|
-
//#region src/component/widget/
|
|
652
|
+
//#region src/component/widget/article/_function/remark-status.ts
|
|
672
653
|
/**
|
|
673
654
|
* 설정 문구의 상태 표지 변환. 괄호 제거. 제목, 링크 제외
|
|
674
655
|
*
|
|
@@ -690,7 +671,7 @@ var remarkStatus = (options) => {
|
|
|
690
671
|
if (text === void 0 || item === void 0) return false;
|
|
691
672
|
return {
|
|
692
673
|
type: "html",
|
|
693
|
-
value: `<span class="
|
|
674
|
+
value: `<span class="wg_article__pill wg_article__pill--${item.kind}">${escape(text)}</span>`
|
|
694
675
|
};
|
|
695
676
|
}], { ignore });
|
|
696
677
|
findAndReplace(tree, [status_candidate_pattern, (match, info) => {
|
|
@@ -700,22 +681,22 @@ var remarkStatus = (options) => {
|
|
|
700
681
|
};
|
|
701
682
|
};
|
|
702
683
|
//#endregion
|
|
703
|
-
//#region src/component/widget/
|
|
684
|
+
//#region src/component/widget/article/_function/remark-swatch.ts
|
|
704
685
|
/**
|
|
705
686
|
* 인라인 hex 색 값의 칩 표시
|
|
706
687
|
*
|
|
707
|
-
* - 문서별 색은 --wg-
|
|
688
|
+
* - 문서별 색은 --wg-article-chip-color로 전달. 고정 스타일시트 값 불가
|
|
708
689
|
*/
|
|
709
690
|
var remarkSwatch = () => (tree) => {
|
|
710
691
|
visit(tree, "inlineCode", (node) => {
|
|
711
692
|
if (/^#[0-9a-f]{3,8}$/i.test(node.value.trim())) node.data = { hProperties: {
|
|
712
|
-
className: ["
|
|
713
|
-
style: `--wg-
|
|
693
|
+
className: ["wg_article__color"],
|
|
694
|
+
style: `--wg-article-chip-color: ${node.value.trim()}`
|
|
714
695
|
} };
|
|
715
696
|
});
|
|
716
697
|
};
|
|
717
698
|
//#endregion
|
|
718
|
-
//#region src/component/widget/
|
|
699
|
+
//#region src/component/widget/article/_function/remark-unknown-directives.ts
|
|
719
700
|
/**
|
|
720
701
|
* hName 없는 블록 지시문은 작성 오류
|
|
721
702
|
*
|
|
@@ -734,13 +715,28 @@ var remarkUnknownDirectives = () => (tree, file) => {
|
|
|
734
715
|
});
|
|
735
716
|
};
|
|
736
717
|
//#endregion
|
|
718
|
+
//#region src/component/widget/mermaid/_function/remark-mermaid.ts
|
|
719
|
+
/**
|
|
720
|
+
* Mermaid 원문과 브라우저 렌더 영역. JavaScript 비활성 환경에서도 원문 유지
|
|
721
|
+
*/
|
|
722
|
+
var remarkMermaid = (options) => (tree) => {
|
|
723
|
+
visit(tree, "code", (node, index, parent) => {
|
|
724
|
+
if (node.lang !== "mermaid" || parent === void 0 || index === void 0) return;
|
|
725
|
+
if (options.assets !== void 0) for (const [url, path] of options.assets.files) options.files.set(url, path);
|
|
726
|
+
parent.children[index] = {
|
|
727
|
+
type: "html",
|
|
728
|
+
value: `<figure class="wg_mermaid__root" data-mermaid${options.assets === void 0 ? "" : ` data-mermaid-module="${options.assets.entry}"`}><div class="wg_mermaid__result" hidden></div><pre class="wg_mermaid__source"><code data-mermaid-source>${node.value.replaceAll("&", "&").replaceAll("<", "<").replaceAll(">", ">")}</code></pre><p class="wg_mermaid__error" role="status" hidden></p></figure>`
|
|
729
|
+
};
|
|
730
|
+
});
|
|
731
|
+
};
|
|
732
|
+
//#endregion
|
|
737
733
|
//#region src/app/_function/create-processor/create-processor.ts
|
|
738
734
|
/**
|
|
739
735
|
* Markdown 변환과 문서 메타데이터 수집
|
|
740
736
|
*
|
|
741
737
|
* - 코드와 문서 머리 생성 후 제목 ID와 가름 수집
|
|
742
738
|
* - 플러그인 HTML 조각은 마지막 rehype-raw에서 해석
|
|
743
|
-
* - 코드 상자의 색상은
|
|
739
|
+
* - 코드 상자의 색상은 article CSS 소유, Shiki 토큰 색상은 --app-code-* 참조
|
|
744
740
|
*/
|
|
745
741
|
var createProcessor = (options) => {
|
|
746
742
|
const files = options.files ?? /* @__PURE__ */ new Map();
|
|
@@ -781,32 +777,6 @@ var loadMermaidAssets = (kitRoot) => {
|
|
|
781
777
|
};
|
|
782
778
|
};
|
|
783
779
|
//#endregion
|
|
784
|
-
//#region src/app/_function/read-content-scope.ts
|
|
785
|
-
/**
|
|
786
|
-
* 공개 폴더 필수, 내부 폴더 선택, 범위 폴더의 symlink 별칭 금지
|
|
787
|
-
*/
|
|
788
|
-
var readContentScope = (params) => {
|
|
789
|
-
if (!existsSync(params.root) || !statSync(params.root).isDirectory()) throw new Error(`content 문서 루트가 없습니다: ${params.root}`);
|
|
790
|
-
const root = realpathSync(params.root);
|
|
791
|
-
const publicRoot = join(root, params.content.public);
|
|
792
|
-
const privateRoot = join(root, params.content.private);
|
|
793
|
-
for (const path of [publicRoot, privateRoot]) {
|
|
794
|
-
if (!existsSync(path)) {
|
|
795
|
-
if (path === privateRoot) continue;
|
|
796
|
-
throw new Error(`공개 문서 폴더가 없습니다: ${path}. content/public 구조 또는 content 설정을 확인하세요`);
|
|
797
|
-
}
|
|
798
|
-
const actual = realpathSync(path);
|
|
799
|
-
const location = relative(root, actual);
|
|
800
|
-
if (!statSync(path).isDirectory() || actual !== path || location === ".." || location.startsWith(`..${sep}`) || isAbsolute(location)) throw new Error(`문서 폴더는 content 내부의 실제 폴더여야 합니다: ${path}`);
|
|
801
|
-
}
|
|
802
|
-
return {
|
|
803
|
-
root,
|
|
804
|
-
publicRoot,
|
|
805
|
-
privateRoot,
|
|
806
|
-
includePrivate: params.includePrivate
|
|
807
|
-
};
|
|
808
|
-
};
|
|
809
|
-
//#endregion
|
|
810
780
|
//#region src/constant/doc.ts
|
|
811
781
|
/**
|
|
812
782
|
* 기존 frontmatter type의 호환 값. 모두 같은 Markdown 렌더링
|
|
@@ -857,7 +827,7 @@ var docDataSchema = z.object({
|
|
|
857
827
|
/**
|
|
858
828
|
* 공개 범위의 Markdown 수집과 렌더링, 범위 미지정 시 독립 Markdown 처리
|
|
859
829
|
*
|
|
860
|
-
* - 사이트 실행에서
|
|
830
|
+
* - 사이트 실행에서 공개와 내부 문서만 수집, content 루트 안내 파일 제외
|
|
861
831
|
* - 머리말, 문서 식별자 중복, 플러그인 오류 시 실패
|
|
862
832
|
*/
|
|
863
833
|
var readDocs = async (options) => {
|
|
@@ -1038,190 +1008,32 @@ var toHtml = (page) => {
|
|
|
1038
1008
|
return html`<!DOCTYPE html>${page}`;
|
|
1039
1009
|
};
|
|
1040
1010
|
//#endregion
|
|
1041
|
-
//#region src/component/widget/
|
|
1042
|
-
var
|
|
1011
|
+
//#region src/component/widget/article/wg-article.tsx
|
|
1012
|
+
var WgArticle = (props) => {
|
|
1043
1013
|
if (props.html !== void 0) return /* @__PURE__ */ jsx("article", {
|
|
1044
|
-
className: clsx("
|
|
1014
|
+
className: clsx("wg_article__root"),
|
|
1045
1015
|
dangerouslySetInnerHTML: { __html: props.html }
|
|
1046
1016
|
});
|
|
1047
1017
|
return /* @__PURE__ */ jsx("article", {
|
|
1048
|
-
className: clsx("
|
|
1018
|
+
className: clsx("wg_article__root"),
|
|
1049
1019
|
children: props.head !== void 0 && /* @__PURE__ */ jsxs("header", {
|
|
1050
|
-
className: clsx("
|
|
1020
|
+
className: clsx("wg_article__head"),
|
|
1051
1021
|
children: [/* @__PURE__ */ jsx("h1", {
|
|
1052
|
-
className: clsx("
|
|
1022
|
+
className: clsx("wg_article__title"),
|
|
1053
1023
|
children: props.head.title
|
|
1054
1024
|
}), props.head.lead !== void 0 && /* @__PURE__ */ jsx("p", {
|
|
1055
|
-
className: clsx("
|
|
1025
|
+
className: clsx("wg_article__lead"),
|
|
1056
1026
|
children: props.head.lead
|
|
1057
1027
|
})]
|
|
1058
1028
|
})
|
|
1059
1029
|
});
|
|
1060
1030
|
};
|
|
1061
1031
|
//#endregion
|
|
1062
|
-
//#region src/component/widget/
|
|
1032
|
+
//#region src/component/widget/article/_constant/heading-highlight.ts
|
|
1063
1033
|
/**
|
|
1064
|
-
*
|
|
1065
|
-
*/
|
|
1066
|
-
var toDocBranches = (options) => {
|
|
1067
|
-
return options.docs.toSorted((a, b) => a.data.order - b.data.order || a.data.name.localeCompare(b.data.name, "en") || a.id.localeCompare(b.id, "en")).map((doc) => {
|
|
1068
|
-
const children = options.children.get(doc.id);
|
|
1069
|
-
return {
|
|
1070
|
-
...doc,
|
|
1071
|
-
children: children === void 0 ? void 0 : toDocBranches({
|
|
1072
|
-
docs: children,
|
|
1073
|
-
children: options.children
|
|
1074
|
-
})
|
|
1075
|
-
};
|
|
1076
|
-
});
|
|
1077
|
-
};
|
|
1078
|
-
//#endregion
|
|
1079
|
-
//#region src/component/widget/navigation/_function/to-doc-groups/_to-doc-group-branches.ts
|
|
1080
|
-
/**
|
|
1081
|
-
* 각 단계의 직접 문서와 하위 묶음을 분리. 고정된 깊이 제한 없이 구성
|
|
1082
|
-
*/
|
|
1083
|
-
var toDocGroupBranches = (options) => {
|
|
1084
|
-
return [...Map.groupBy(options.docs.filter((doc) => doc.data.group.length > options.path.length), (doc) => doc.data.group[options.path.length])].map(([name, docs]) => {
|
|
1085
|
-
const path = [...options.path, name];
|
|
1086
|
-
return {
|
|
1087
|
-
name,
|
|
1088
|
-
path,
|
|
1089
|
-
docs,
|
|
1090
|
-
position: options.positions.get(JSON.stringify(path)) ?? options.unlistedPosition
|
|
1091
|
-
};
|
|
1092
|
-
}).toSorted((a, b) => a.position - b.position || a.name.localeCompare(b.name, "ko")).map((group) => {
|
|
1093
|
-
return {
|
|
1094
|
-
name: group.name,
|
|
1095
|
-
path: group.path,
|
|
1096
|
-
docs: toDocBranches({
|
|
1097
|
-
docs: group.docs.filter((doc) => doc.data.group.length === group.path.length && doc.data.parent === void 0),
|
|
1098
|
-
children: options.children
|
|
1099
|
-
}),
|
|
1100
|
-
groups: toDocGroupBranches({
|
|
1101
|
-
docs: group.docs,
|
|
1102
|
-
children: options.children,
|
|
1103
|
-
path: group.path,
|
|
1104
|
-
positions: options.positions,
|
|
1105
|
-
unlistedPosition: options.unlistedPosition
|
|
1106
|
-
})
|
|
1107
|
-
};
|
|
1108
|
-
});
|
|
1109
|
-
};
|
|
1110
|
-
//#endregion
|
|
1111
|
-
//#region src/component/widget/navigation/_function/to-doc-groups/to-doc-groups.ts
|
|
1112
|
-
/**
|
|
1113
|
-
* 메타데이터 경로로 읽는 순서를 가진 문서 계층 구성
|
|
1114
|
-
*
|
|
1115
|
-
* - 같은 조상의 중복 지정은 최초 순서 유지
|
|
1116
|
-
* - 미지정 형제 묶음은 이름순
|
|
1117
|
-
*/
|
|
1118
|
-
var toDocGroups = (options) => {
|
|
1119
|
-
const positions = new Map(uniqBy(options.navigation.flatMap((path, index) => {
|
|
1120
|
-
return path.map((_name, depth) => [JSON.stringify(path.slice(0, depth + 1)), index]);
|
|
1121
|
-
}), (entry) => entry[0]));
|
|
1122
|
-
const children = Map.groupBy(options.docs, (doc) => doc.data.parent);
|
|
1123
|
-
return toDocGroupBranches({
|
|
1124
|
-
docs: options.docs,
|
|
1125
|
-
children,
|
|
1126
|
-
path: [],
|
|
1127
|
-
positions,
|
|
1128
|
-
unlistedPosition: options.navigation.length
|
|
1129
|
-
});
|
|
1130
|
-
};
|
|
1131
|
-
//#endregion
|
|
1132
|
-
//#region src/component/widget/navigation/_function/to-navigation-data/_to-navigation-document.ts
|
|
1133
|
-
/**
|
|
1134
|
-
* 본문 없는 문서 가지. 재귀에서도 현재 페이지 한 항목만 표시
|
|
1135
|
-
*/
|
|
1136
|
-
var toNavigationDocument = (options) => {
|
|
1137
|
-
return {
|
|
1138
|
-
id: options.doc.id,
|
|
1139
|
-
name: options.doc.data.name,
|
|
1140
|
-
label: options.doc.data.label,
|
|
1141
|
-
active: options.doc.id === options.current,
|
|
1142
|
-
children: options.doc.children === void 0 ? [] : options.doc.children.map((doc) => toNavigationDocument({
|
|
1143
|
-
...options,
|
|
1144
|
-
doc
|
|
1145
|
-
}))
|
|
1146
|
-
};
|
|
1147
|
-
};
|
|
1148
|
-
//#endregion
|
|
1149
|
-
//#region src/component/widget/navigation/_function/to-navigation-data/_to-navigation-group.ts
|
|
1150
|
-
/**
|
|
1151
|
-
* 전체 묶음 경로와 본문 없는 하위 가지
|
|
1152
|
-
*/
|
|
1153
|
-
var toNavigationGroup = (options) => {
|
|
1154
|
-
return {
|
|
1155
|
-
name: options.group.name,
|
|
1156
|
-
path: options.group.path,
|
|
1157
|
-
docs: options.group.docs.map((doc) => toNavigationDocument({
|
|
1158
|
-
doc,
|
|
1159
|
-
current: options.current
|
|
1160
|
-
})),
|
|
1161
|
-
groups: options.group.groups.map((group) => toNavigationGroup({
|
|
1162
|
-
...options,
|
|
1163
|
-
group
|
|
1164
|
-
}))
|
|
1165
|
-
};
|
|
1166
|
-
};
|
|
1167
|
-
//#endregion
|
|
1168
|
-
//#region src/component/widget/navigation/_function/to-toc-groups.ts
|
|
1169
|
-
/**
|
|
1170
|
-
* h2별 h3 수집. 가름 시작 절에서 목차 묶음 분리
|
|
1171
|
-
*
|
|
1172
|
-
* - 첫 h2 앞의 h3은 목차 제외
|
|
1173
|
-
*/
|
|
1174
|
-
var toTocGroups = (outline) => {
|
|
1175
|
-
const entries = outline.headings.filter((heading) => heading.depth === section_heading_depth.section || heading.depth === section_heading_depth.sub).map((heading, index) => ({
|
|
1176
|
-
heading,
|
|
1177
|
-
section: outline.sections[index]
|
|
1178
|
-
}));
|
|
1179
|
-
const sections = entries.flatMap((entry, index) => {
|
|
1180
|
-
if (entry.heading.depth !== section_heading_depth.section) return [];
|
|
1181
|
-
return [{
|
|
1182
|
-
heading: entry.heading,
|
|
1183
|
-
part: entry.section.part,
|
|
1184
|
-
subs: takeWhile(entries.slice(index + 1), (sub) => sub.heading.depth !== section_heading_depth.section).map((sub) => {
|
|
1185
|
-
return { heading: sub.heading };
|
|
1186
|
-
})
|
|
1187
|
-
}];
|
|
1188
|
-
});
|
|
1189
|
-
return sections.flatMap((section, index) => {
|
|
1190
|
-
if (index > 0 && section.part === void 0) return [];
|
|
1191
|
-
return [{
|
|
1192
|
-
part: section.part,
|
|
1193
|
-
sections: [section, ...takeWhile(sections.slice(index + 1), (next) => next.part === void 0)]
|
|
1194
|
-
}];
|
|
1195
|
-
});
|
|
1196
|
-
};
|
|
1197
|
-
//#endregion
|
|
1198
|
-
//#region src/component/widget/navigation/_function/to-navigation-data/to-navigation-data.ts
|
|
1199
|
-
/**
|
|
1200
|
-
* 현재 문서의 이름, 계층, 목차만 전송. 다른 문서 본문은 브라우저 탐색 입력에서 제외
|
|
1201
|
-
*/
|
|
1202
|
-
var toNavigationData = (options) => {
|
|
1203
|
-
return {
|
|
1204
|
-
title: options.site.title,
|
|
1205
|
-
groups: toDocGroups({
|
|
1206
|
-
docs: options.docs,
|
|
1207
|
-
navigation: options.site.navigation
|
|
1208
|
-
}).map((group) => toNavigationGroup({
|
|
1209
|
-
group,
|
|
1210
|
-
current: options.current
|
|
1211
|
-
})),
|
|
1212
|
-
outline: options.outline === void 0 ? [] : toTocGroups(options.outline),
|
|
1213
|
-
home: options.current === void 0,
|
|
1214
|
-
cursorFace: options.site.cursorFace
|
|
1215
|
-
};
|
|
1216
|
-
};
|
|
1217
|
-
//#endregion
|
|
1218
|
-
//#region src/component/widget/navigation/_function/to-navigation-json.ts
|
|
1219
|
-
/**
|
|
1220
|
-
* 작성한 이름이 script 종료 태그나 JavaScript 줄 구분자로 해석되지 않는 JSON
|
|
1034
|
+
* 앵커 이동 후 제목 강조 유지 시간. 이후 CSS로 서서히 해제
|
|
1221
1035
|
*/
|
|
1222
|
-
var
|
|
1223
|
-
return JSON.stringify(data).replaceAll("<", "\\u003c").replaceAll("\u2028", "\\u2028").replaceAll("\u2029", "\\u2029");
|
|
1224
|
-
};
|
|
1036
|
+
var heading_highlight_hold_ms = 3e3;
|
|
1225
1037
|
//#endregion
|
|
1226
1038
|
//#region src/component/widget/cursor-face/_constant/cursor-face.ts
|
|
1227
1039
|
/**
|
|
@@ -1420,7 +1232,8 @@ var WgMermaid = (props) => {
|
|
|
1420
1232
|
* 공유 툴팁 재사용, 테마나 접힘 변경에서 숨긴 안내의 다음 호버 복원
|
|
1421
1233
|
*/
|
|
1422
1234
|
const handleTooltipShow = (event) => {
|
|
1423
|
-
if (event.target instanceof Element
|
|
1235
|
+
if (!(event.target instanceof Element) || event.target.closest("[title]") === null) return;
|
|
1236
|
+
for (const tooltip of tooltips) {
|
|
1424
1237
|
tooltip.hidden = false;
|
|
1425
1238
|
const bounds = tooltip.getBoundingClientRect();
|
|
1426
1239
|
tooltip.style.left = `${scrollX + Math.max(0, Math.min(bounds.left, document.documentElement.clientWidth - bounds.width))}px`;
|
|
@@ -1432,6 +1245,9 @@ var WgMermaid = (props) => {
|
|
|
1432
1245
|
const handleTooltipHide = () => {
|
|
1433
1246
|
for (const tooltip of tooltips) tooltip.hidden = true;
|
|
1434
1247
|
};
|
|
1248
|
+
/**
|
|
1249
|
+
* 보이는 다이어그램만 현재 테마로 차례 렌더. 모듈 수신 실패는 모든 영역에 오류 안내
|
|
1250
|
+
*/
|
|
1435
1251
|
const render = toChangeQueue({
|
|
1436
1252
|
run: async () => {
|
|
1437
1253
|
const elements = [...document.querySelectorAll(".wg_mermaid__root[data-mermaid]")];
|
|
@@ -1461,10 +1277,9 @@ var WgMermaid = (props) => {
|
|
|
1461
1277
|
library.default.initialize(mode === "dark" ? render_dark_config : render_config);
|
|
1462
1278
|
const rendered = await library.default.render(`fh-mermaid-${crypto.getRandomValues(/* @__PURE__ */ new Uint32Array(4)).join("-")}`, source.textContent, measurement);
|
|
1463
1279
|
if (lifetime.signal.aborted || !element.isConnected || theme.matches !== (mode === "dark")) return;
|
|
1464
|
-
const shadow = result.shadowRoot ?? result.attachShadow({ mode: "open" });
|
|
1465
1280
|
const canvas = document.createElement("div");
|
|
1466
1281
|
canvas.innerHTML = rendered.svg;
|
|
1467
|
-
|
|
1282
|
+
(result.shadowRoot ?? result.attachShadow({ mode: "open" })).replaceChildren(canvas);
|
|
1468
1283
|
rendered.bindFunctions?.(canvas);
|
|
1469
1284
|
canvas.addEventListener("mouseover", handleTooltipShow, { signal: lifetime.signal });
|
|
1470
1285
|
canvas.addEventListener("mouseout", handleTooltipHide, { signal: lifetime.signal });
|
|
@@ -1535,8 +1350,10 @@ var WgMermaid = (props) => {
|
|
|
1535
1350
|
const source = element.querySelector(".wg_mermaid__source");
|
|
1536
1351
|
const result = element.querySelector(".wg_mermaid__result");
|
|
1537
1352
|
const error = element.querySelector(".wg_mermaid__error");
|
|
1538
|
-
result
|
|
1539
|
-
|
|
1353
|
+
if (result !== null) {
|
|
1354
|
+
result.shadowRoot?.replaceChildren();
|
|
1355
|
+
result.hidden = true;
|
|
1356
|
+
}
|
|
1540
1357
|
if (source !== null) source.hidden = false;
|
|
1541
1358
|
if (error !== null) error.hidden = true;
|
|
1542
1359
|
delete element.dataset.mermaidTheme;
|
|
@@ -1548,7 +1365,7 @@ var WgMermaid = (props) => {
|
|
|
1548
1365
|
return null;
|
|
1549
1366
|
};
|
|
1550
1367
|
//#endregion
|
|
1551
|
-
//#region src/component/widget/
|
|
1368
|
+
//#region src/component/widget/sidebar/_constant/navigation.ts
|
|
1552
1369
|
/**
|
|
1553
1370
|
* 모바일 탐색 전환. wg-shell.css의 폭 조건과 일치
|
|
1554
1371
|
*/
|
|
@@ -1558,66 +1375,65 @@ var navigation_mobile_query = "(width < 1024px)";
|
|
|
1558
1375
|
*/
|
|
1559
1376
|
var navigation_wide_query = "(width >= 1360px)";
|
|
1560
1377
|
//#endregion
|
|
1561
|
-
//#region src/component/widget/
|
|
1562
|
-
var
|
|
1563
|
-
const entries = [...props.group.docs.map((doc) => ({
|
|
1564
|
-
kind: "document",
|
|
1565
|
-
key: `doc:${doc.id}`,
|
|
1566
|
-
doc
|
|
1567
|
-
})), ...props.group.groups.map((group) => ({
|
|
1568
|
-
kind: "group",
|
|
1569
|
-
key: `group:${JSON.stringify(group.path)}`,
|
|
1570
|
-
group
|
|
1571
|
-
}))];
|
|
1378
|
+
//#region src/component/widget/sidebar/_wg-doc-list.tsx
|
|
1379
|
+
var WgDocList = (props) => {
|
|
1572
1380
|
return /* @__PURE__ */ jsx("ul", {
|
|
1573
|
-
className: clsx("
|
|
1381
|
+
className: clsx("wg_docList__root"),
|
|
1574
1382
|
"aria-label": props.group.name,
|
|
1575
|
-
children:
|
|
1383
|
+
children: [...props.group.docs.map((doc) => ({
|
|
1384
|
+
kind: "document",
|
|
1385
|
+
key: `doc:${doc.id}`,
|
|
1386
|
+
doc
|
|
1387
|
+
})), ...props.group.groups.map((group) => ({
|
|
1388
|
+
kind: "group",
|
|
1389
|
+
key: `group:${JSON.stringify(group.path)}`,
|
|
1390
|
+
group
|
|
1391
|
+
}))].map((entry) => {
|
|
1576
1392
|
return /* @__PURE__ */ jsxs("li", {
|
|
1577
|
-
className: clsx("
|
|
1393
|
+
className: clsx("wg_docList__item"),
|
|
1578
1394
|
children: [entry.kind === "document" && /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx("a", {
|
|
1579
|
-
className: clsx("
|
|
1395
|
+
className: clsx("wg_docList__link", { "wg_docList__link--active": entry.doc.active }),
|
|
1580
1396
|
href: `/${entry.doc.id}/`,
|
|
1581
1397
|
title: entry.doc.label,
|
|
1582
1398
|
"aria-current": entry.doc.active ? "page" : void 0,
|
|
1583
1399
|
children: entry.doc.name
|
|
1584
|
-
}), entry.doc.children.length > 0 && /* @__PURE__ */ jsx(
|
|
1400
|
+
}), entry.doc.children.length > 0 && /* @__PURE__ */ jsx(WgDocList, { group: {
|
|
1585
1401
|
name: entry.doc.name,
|
|
1586
1402
|
path: props.group.path,
|
|
1587
1403
|
docs: entry.doc.children,
|
|
1588
1404
|
groups: []
|
|
1589
1405
|
} })] }), entry.kind === "group" && /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx("span", {
|
|
1590
|
-
className: clsx("
|
|
1406
|
+
className: clsx("wg_docList__branch"),
|
|
1591
1407
|
children: entry.group.name
|
|
1592
|
-
}), /* @__PURE__ */ jsx(
|
|
1408
|
+
}), /* @__PURE__ */ jsx(WgDocList, { group: entry.group })] })]
|
|
1593
1409
|
}, entry.key);
|
|
1594
1410
|
})
|
|
1595
1411
|
});
|
|
1596
1412
|
};
|
|
1597
1413
|
//#endregion
|
|
1598
|
-
//#region src/component/widget/
|
|
1599
|
-
var
|
|
1414
|
+
//#region src/component/widget/sidebar/_wg-doc-tree.tsx
|
|
1415
|
+
var WgDocTree = (props) => {
|
|
1600
1416
|
return /* @__PURE__ */ jsx("nav", {
|
|
1601
|
-
className: clsx("
|
|
1417
|
+
className: clsx("wg_docTree__root"),
|
|
1602
1418
|
"aria-label": copy_nav_aria_label,
|
|
1603
1419
|
children: /* @__PURE__ */ jsx("div", {
|
|
1604
|
-
className: clsx("
|
|
1420
|
+
className: clsx("wg_docTree__docs"),
|
|
1605
1421
|
children: props.groups.map((group) => {
|
|
1606
1422
|
return /* @__PURE__ */ jsxs("section", {
|
|
1607
|
-
className: clsx("
|
|
1423
|
+
className: clsx("wg_docTree__docGroup"),
|
|
1608
1424
|
"aria-label": group.name,
|
|
1609
1425
|
children: [/* @__PURE__ */ jsx("div", {
|
|
1610
|
-
className: clsx("
|
|
1426
|
+
className: clsx("wg_docTree__group"),
|
|
1611
1427
|
children: group.name
|
|
1612
|
-
}), /* @__PURE__ */ jsx(
|
|
1428
|
+
}), /* @__PURE__ */ jsx(WgDocList, { group })]
|
|
1613
1429
|
}, JSON.stringify(group.path));
|
|
1614
1430
|
})
|
|
1615
1431
|
})
|
|
1616
1432
|
});
|
|
1617
1433
|
};
|
|
1618
1434
|
//#endregion
|
|
1619
|
-
//#region src/component/widget/
|
|
1620
|
-
var
|
|
1435
|
+
//#region src/component/widget/sidebar/_wg-sidebar-scroll.tsx
|
|
1436
|
+
var WgSidebarScroll = (props) => {
|
|
1621
1437
|
const [overflow, setOverflow] = useState({
|
|
1622
1438
|
above: false,
|
|
1623
1439
|
below: false
|
|
@@ -1655,9 +1471,9 @@ var WgNavigationScroll = (props) => {
|
|
|
1655
1471
|
useLayoutEffect(() => {
|
|
1656
1472
|
if (!props.ready || props.portRef.current === null) return;
|
|
1657
1473
|
const observer = new ResizeObserver(scheduleOverflow);
|
|
1474
|
+
let active = true;
|
|
1658
1475
|
observer.observe(props.portRef.current);
|
|
1659
1476
|
if (props.portRef.current.firstElementChild !== null) observer.observe(props.portRef.current.firstElementChild);
|
|
1660
|
-
let active = true;
|
|
1661
1477
|
addEventListener("pageshow", scheduleOverflow);
|
|
1662
1478
|
if (props.portRef.current.isConnected) updateOverflow();
|
|
1663
1479
|
else queueMicrotask(() => {
|
|
@@ -1679,30 +1495,79 @@ var WgNavigationScroll = (props) => {
|
|
|
1679
1495
|
}, [props.children, props.ready]);
|
|
1680
1496
|
return /* @__PURE__ */ jsx("div", {
|
|
1681
1497
|
ref: props.ready ? props.portRef : void 0,
|
|
1682
|
-
className: clsx("
|
|
1683
|
-
"data-
|
|
1498
|
+
className: clsx("wg_sidebar__scroll", props.kind === "documents" && "wg_sidebar__docsScroll", props.kind === "outline" && "wg_sidebar__tocScroll", props.ready && "wg_sidebar__scroll--ready", props.ready && (overflow.above || overflow.below) && "wg_sidebar__scroll--overflow", props.ready && overflow.above && "wg_sidebar__scroll--above", props.ready && overflow.below && "wg_sidebar__scroll--below"),
|
|
1499
|
+
"data-sidebar-scroll": props.kind,
|
|
1684
1500
|
onClick: props.onClick,
|
|
1685
1501
|
onScroll: handlePortScroll,
|
|
1686
1502
|
children: props.children
|
|
1687
1503
|
});
|
|
1688
1504
|
};
|
|
1689
1505
|
//#endregion
|
|
1690
|
-
//#region src/
|
|
1506
|
+
//#region src/component/widget/article/_constant/anchor-shift.ts
|
|
1691
1507
|
/**
|
|
1692
|
-
*
|
|
1508
|
+
* 끝 구간 제목의 추가 도착 여백. 목차가 실행 중 계산해 제목에 지정
|
|
1693
1509
|
*/
|
|
1694
|
-
var
|
|
1695
|
-
|
|
1510
|
+
var anchor_shift_property = "--wg-article-anchor-shift";
|
|
1511
|
+
//#endregion
|
|
1512
|
+
//#region src/constant/scroll.ts
|
|
1513
|
+
/**
|
|
1514
|
+
* 사용자가 문서 스크롤을 직접 조작하는 입력. 초기 앵커 보정 중단과 목차 고정 해제 기준
|
|
1515
|
+
*/
|
|
1516
|
+
var scroll_user_input_events = [
|
|
1517
|
+
"wheel",
|
|
1518
|
+
"touchmove",
|
|
1519
|
+
"keydown",
|
|
1520
|
+
"mousedown"
|
|
1521
|
+
];
|
|
1522
|
+
//#endregion
|
|
1523
|
+
//#region src/util/dom/find-hash-link.ts
|
|
1524
|
+
/**
|
|
1525
|
+
* 현재 문서 안 hash 이동을 일으키는 기본 클릭의 링크
|
|
1526
|
+
*
|
|
1527
|
+
* - 보조키, 새 창, 다운로드, 다른 문서 링크는 없음
|
|
1528
|
+
*/
|
|
1529
|
+
var findHashLink = (event) => {
|
|
1530
|
+
if (event.defaultPrevented || event.button !== 0 || event.metaKey || event.ctrlKey || event.shiftKey || event.altKey) return null;
|
|
1531
|
+
const link = event.target instanceof Element ? event.target.closest("a[href]") : null;
|
|
1532
|
+
return link instanceof HTMLAnchorElement && (link.target === "" || link.target === "_self") && !link.hasAttribute("download") && link.origin === location.origin && link.pathname === location.pathname && link.search === location.search && link.hash !== "" ? link : null;
|
|
1533
|
+
};
|
|
1534
|
+
//#endregion
|
|
1535
|
+
//#region src/util/dom/to-hash-id.ts
|
|
1536
|
+
/**
|
|
1537
|
+
* URL hash의 대상 id. 한국어 제목의 percent encoding 해석
|
|
1538
|
+
*
|
|
1539
|
+
* - 빈 hash와 잘못된 encoding은 대상 없음
|
|
1540
|
+
*/
|
|
1541
|
+
var toHashId = (hash) => {
|
|
1542
|
+
if (!hash) return;
|
|
1696
1543
|
try {
|
|
1697
|
-
return
|
|
1544
|
+
return decodeURIComponent(hash.slice(1));
|
|
1698
1545
|
} catch (error) {
|
|
1699
|
-
if (error instanceof URIError) return
|
|
1546
|
+
if (error instanceof URIError) return;
|
|
1700
1547
|
throw error;
|
|
1701
1548
|
}
|
|
1702
1549
|
};
|
|
1703
1550
|
//#endregion
|
|
1704
|
-
//#region src/
|
|
1705
|
-
|
|
1551
|
+
//#region src/util/dom/find-hash-target.ts
|
|
1552
|
+
/**
|
|
1553
|
+
* URL hash 대상 요소. 대상 id가 없으면 없음
|
|
1554
|
+
*/
|
|
1555
|
+
var findHashTarget = (hash) => {
|
|
1556
|
+
const id = toHashId(hash);
|
|
1557
|
+
return id === void 0 ? null : document.getElementById(id);
|
|
1558
|
+
};
|
|
1559
|
+
//#endregion
|
|
1560
|
+
//#region src/util/dom/is-restoring-navigation.ts
|
|
1561
|
+
/**
|
|
1562
|
+
* 브라우저가 이전 스크롤 위치를 복원하는 문서 진입. 새로고침, 뒤로가기, 앞으로가기
|
|
1563
|
+
*/
|
|
1564
|
+
var isRestoringNavigation = () => {
|
|
1565
|
+
const navigation = performance.getEntriesByType("navigation")[0];
|
|
1566
|
+
return navigation instanceof PerformanceNavigationTiming && (navigation.type === "reload" || navigation.type === "back_forward");
|
|
1567
|
+
};
|
|
1568
|
+
//#endregion
|
|
1569
|
+
//#region src/component/widget/sidebar/_wg-toc.tsx
|
|
1570
|
+
var WgToc = (props) => {
|
|
1706
1571
|
const [currentSlug, setCurrentSlug] = useState();
|
|
1707
1572
|
/**
|
|
1708
1573
|
* 연결마다 구독 설치, 초기 위치 갱신은 본문 파싱 이후 실행
|
|
@@ -1710,71 +1575,133 @@ var WgNavigationToc = (props) => {
|
|
|
1710
1575
|
useLayoutEffect(() => {
|
|
1711
1576
|
const headings = props.groups.flatMap((group) => group.sections).flatMap((section) => [section.heading, ...section.subs.map((sub) => sub.heading)]);
|
|
1712
1577
|
const frame = { id: 0 };
|
|
1578
|
+
const lifetime = new AbortController();
|
|
1579
|
+
const lock = { slug: isRestoringNavigation() ? void 0 : toHashId(location.hash) };
|
|
1713
1580
|
/**
|
|
1714
|
-
*
|
|
1581
|
+
* 연속 스크롤의 프레임당 1회 현재 제목 갱신
|
|
1582
|
+
*
|
|
1583
|
+
* - 접힌 본문 제목은 제외하고 읽는 선을 통과한 마지막 제목 선택
|
|
1584
|
+
* - 링크, hash로 이동한 제목은 사용자 스크롤 전까지 유지
|
|
1715
1585
|
*/
|
|
1716
|
-
const
|
|
1717
|
-
frame.id
|
|
1718
|
-
|
|
1586
|
+
const handleReadingPositionChange = () => {
|
|
1587
|
+
if (frame.id !== 0) return;
|
|
1588
|
+
frame.id = requestAnimationFrame(() => {
|
|
1589
|
+
frame.id = 0;
|
|
1590
|
+
const visible = headings.flatMap((heading) => {
|
|
1591
|
+
const target = findHashTarget(`#${heading.slug}`);
|
|
1592
|
+
return target?.checkVisibility() ? [{
|
|
1593
|
+
slug: heading.slug,
|
|
1594
|
+
top: target.getBoundingClientRect().top - Number.parseFloat(getComputedStyle(target).scrollMarginTop)
|
|
1595
|
+
}] : [];
|
|
1596
|
+
});
|
|
1597
|
+
setCurrentSlug(visible.find((heading) => heading.slug === lock.slug)?.slug ?? visible.findLast((heading) => heading.top <= 1)?.slug ?? visible[0]?.slug);
|
|
1598
|
+
});
|
|
1599
|
+
};
|
|
1600
|
+
/**
|
|
1601
|
+
* 문서 크기, 화면 높이 변경. 끝 구간 제목의 도착 위치를 목차 전환 위치와 맞춘 뒤 현재 제목 재계산
|
|
1602
|
+
*
|
|
1603
|
+
* - 읽는 선에 닿지 못하는 끝 절이 있으면 마지막 한 화면 구간의 도착 위치를 문서 끝까지 비례 압축
|
|
1604
|
+
* - 압축한 거리만큼 제목 위 여백 추가, 앵커 이동과 목차 판정이 같은 위치 사용
|
|
1605
|
+
*/
|
|
1606
|
+
const handleLayoutChange = () => {
|
|
1607
|
+
for (const heading of headings) findHashTarget(`#${heading.slug}`)?.style.removeProperty(anchor_shift_property);
|
|
1608
|
+
const anchors = headings.flatMap((heading) => {
|
|
1719
1609
|
const target = findHashTarget(`#${heading.slug}`);
|
|
1720
1610
|
return target?.checkVisibility() ? [{
|
|
1721
|
-
|
|
1722
|
-
|
|
1611
|
+
target,
|
|
1612
|
+
arrival: scrollY + target.getBoundingClientRect().top - Number.parseFloat(getComputedStyle(target).scrollMarginTop)
|
|
1723
1613
|
}] : [];
|
|
1724
1614
|
});
|
|
1725
|
-
|
|
1615
|
+
const last = anchors.at(-1);
|
|
1616
|
+
const scrollEnd = document.documentElement.scrollHeight - innerHeight;
|
|
1617
|
+
if (last !== void 0 && scrollEnd > 0 && last.arrival > scrollEnd) {
|
|
1618
|
+
const stretchFrom = Math.max(0, scrollEnd - innerHeight);
|
|
1619
|
+
const stretchScale = (scrollEnd - stretchFrom) / (last.arrival - stretchFrom);
|
|
1620
|
+
for (const anchor of anchors) if (anchor.arrival > stretchFrom) anchor.target.style.setProperty(anchor_shift_property, `${(anchor.arrival - stretchFrom) * (1 - stretchScale)}px`);
|
|
1621
|
+
}
|
|
1622
|
+
handleReadingPositionChange();
|
|
1726
1623
|
};
|
|
1727
1624
|
/**
|
|
1728
|
-
*
|
|
1625
|
+
* 같은 문서 hash 링크의 도착 제목 고정. 같은 hash 재클릭 포함
|
|
1729
1626
|
*/
|
|
1730
|
-
const
|
|
1731
|
-
|
|
1627
|
+
const handleHashLinkClick = (event) => {
|
|
1628
|
+
const link = findHashLink(event);
|
|
1629
|
+
if (link === null) return;
|
|
1630
|
+
lock.slug = toHashId(link.hash);
|
|
1631
|
+
handleReadingPositionChange();
|
|
1632
|
+
};
|
|
1633
|
+
/**
|
|
1634
|
+
* 뒤로가기, 주소 수정의 hash 변경 도착 제목 고정
|
|
1635
|
+
*/
|
|
1636
|
+
const handleHashChange = () => {
|
|
1637
|
+
lock.slug = toHashId(location.hash);
|
|
1638
|
+
handleReadingPositionChange();
|
|
1732
1639
|
};
|
|
1733
|
-
|
|
1734
|
-
|
|
1735
|
-
|
|
1736
|
-
|
|
1737
|
-
|
|
1738
|
-
|
|
1640
|
+
/**
|
|
1641
|
+
* 사용자 스크롤 시작. 도착 제목 고정 해제
|
|
1642
|
+
*
|
|
1643
|
+
* - 재계산은 실제 스크롤에서 실행, 링크를 누르는 동안 다른 제목 표시 방지
|
|
1644
|
+
*/
|
|
1645
|
+
const handleUserScroll = () => {
|
|
1646
|
+
lock.slug = void 0;
|
|
1647
|
+
};
|
|
1648
|
+
const observer = new ResizeObserver(handleLayoutChange);
|
|
1649
|
+
observer.observe(document.body);
|
|
1650
|
+
addEventListener("scroll", handleReadingPositionChange, {
|
|
1651
|
+
passive: true,
|
|
1652
|
+
signal: lifetime.signal
|
|
1653
|
+
});
|
|
1654
|
+
addEventListener("resize", handleLayoutChange, { signal: lifetime.signal });
|
|
1655
|
+
addEventListener("toggle", handleReadingPositionChange, {
|
|
1656
|
+
capture: true,
|
|
1657
|
+
signal: lifetime.signal
|
|
1658
|
+
});
|
|
1659
|
+
addEventListener("pageshow", handleReadingPositionChange, { signal: lifetime.signal });
|
|
1660
|
+
addEventListener("hashchange", handleHashChange, { signal: lifetime.signal });
|
|
1661
|
+
document.addEventListener("DOMContentLoaded", handleLayoutChange, { signal: lifetime.signal });
|
|
1662
|
+
document.addEventListener("click", handleHashLinkClick, { signal: lifetime.signal });
|
|
1663
|
+
for (const type of scroll_user_input_events) addEventListener(type, handleUserScroll, {
|
|
1664
|
+
passive: true,
|
|
1665
|
+
signal: lifetime.signal
|
|
1666
|
+
});
|
|
1667
|
+
if (document.readyState !== "loading") handleLayoutChange();
|
|
1739
1668
|
return () => {
|
|
1740
1669
|
cancelAnimationFrame(frame.id);
|
|
1741
|
-
|
|
1742
|
-
|
|
1743
|
-
|
|
1744
|
-
removeEventListener("pageshow", handleReadingPositionChange);
|
|
1745
|
-
document.removeEventListener("DOMContentLoaded", handleReadingPositionChange);
|
|
1670
|
+
lifetime.abort();
|
|
1671
|
+
observer.disconnect();
|
|
1672
|
+
for (const heading of headings) findHashTarget(`#${heading.slug}`)?.style.removeProperty(anchor_shift_property);
|
|
1746
1673
|
};
|
|
1747
1674
|
}, [props.groups]);
|
|
1748
1675
|
return /* @__PURE__ */ jsxs("nav", {
|
|
1749
|
-
className: clsx("
|
|
1676
|
+
className: clsx("wg_toc__root"),
|
|
1750
1677
|
"aria-label": copy_nav_toc_label,
|
|
1751
1678
|
children: [/* @__PURE__ */ jsx("div", {
|
|
1752
|
-
className: clsx("
|
|
1679
|
+
className: clsx("wg_toc__label"),
|
|
1753
1680
|
children: copy_nav_toc_label
|
|
1754
1681
|
}), props.groups.map((group) => {
|
|
1755
1682
|
return /* @__PURE__ */ jsxs("div", {
|
|
1756
|
-
className: clsx("
|
|
1683
|
+
className: clsx("wg_toc__groupSection", { "wg_toc__groupSection--named": group.part !== void 0 }),
|
|
1757
1684
|
children: [group.part !== void 0 && /* @__PURE__ */ jsx("div", {
|
|
1758
|
-
className: clsx("
|
|
1685
|
+
className: clsx("wg_toc__group"),
|
|
1759
1686
|
children: group.part
|
|
1760
1687
|
}), /* @__PURE__ */ jsx("ul", {
|
|
1761
|
-
className: clsx("
|
|
1688
|
+
className: clsx("wg_toc__list"),
|
|
1762
1689
|
children: group.sections.map((section) => {
|
|
1763
1690
|
return /* @__PURE__ */ jsxs("li", {
|
|
1764
|
-
className: clsx("
|
|
1691
|
+
className: clsx("wg_toc__item"),
|
|
1765
1692
|
children: [/* @__PURE__ */ jsx("a", {
|
|
1766
|
-
className: clsx("
|
|
1693
|
+
className: clsx("wg_toc__link", { "wg_toc__link--active": section.heading.slug === currentSlug }),
|
|
1767
1694
|
href: `#${section.heading.slug}`,
|
|
1768
1695
|
"data-toc-link": "",
|
|
1769
1696
|
"aria-current": section.heading.slug === currentSlug ? "location" : void 0,
|
|
1770
1697
|
children: section.heading.text
|
|
1771
1698
|
}), section.subs.length > 0 && /* @__PURE__ */ jsx("ul", {
|
|
1772
|
-
className: clsx("
|
|
1699
|
+
className: clsx("wg_toc__sub"),
|
|
1773
1700
|
children: section.subs.map((sub) => {
|
|
1774
1701
|
return /* @__PURE__ */ jsx("li", {
|
|
1775
|
-
className: clsx("
|
|
1702
|
+
className: clsx("wg_toc__subItem"),
|
|
1776
1703
|
children: /* @__PURE__ */ jsx("a", {
|
|
1777
|
-
className: clsx("
|
|
1704
|
+
className: clsx("wg_toc__subLink", { "wg_toc__subLink--active": sub.heading.slug === currentSlug }),
|
|
1778
1705
|
href: `#${sub.heading.slug}`,
|
|
1779
1706
|
"data-toc-sub-link": "",
|
|
1780
1707
|
"aria-current": sub.heading.slug === currentSlug ? "location" : void 0,
|
|
@@ -1803,8 +1730,8 @@ var revealHashTarget = (hash) => {
|
|
|
1803
1730
|
return target;
|
|
1804
1731
|
};
|
|
1805
1732
|
//#endregion
|
|
1806
|
-
//#region src/component/widget/
|
|
1807
|
-
var
|
|
1733
|
+
//#region src/component/widget/sidebar/wg-sidebar.tsx
|
|
1734
|
+
var WgSidebar = (props) => {
|
|
1808
1735
|
const [layout, setLayout] = useState(() => {
|
|
1809
1736
|
if (props.stores === void 0) return "desktop";
|
|
1810
1737
|
if (matchMedia("(width < 1024px)").matches) return "drawer";
|
|
@@ -1834,12 +1761,6 @@ var WgNavigation = (props) => {
|
|
|
1834
1761
|
return wideRef.current.matches ? "wide" : "desktop";
|
|
1835
1762
|
};
|
|
1836
1763
|
/**
|
|
1837
|
-
* 배치별 DOM 참조 분리, 이전 노드 정리 시 새 배치 참조 유지
|
|
1838
|
-
*/
|
|
1839
|
-
const getNavigationPort = () => {
|
|
1840
|
-
return layoutRef.current === "drawer" ? drawerNavigationRef.current : navigationRef.current;
|
|
1841
|
-
};
|
|
1842
|
-
/**
|
|
1843
1764
|
* 배치별 DOM은 분리하되 좌표 저장과 복원은 현재 표시되는 한 영역을 소비
|
|
1844
1765
|
*/
|
|
1845
1766
|
const getDocumentsPort = () => {
|
|
@@ -1954,8 +1875,8 @@ var WgNavigation = (props) => {
|
|
|
1954
1875
|
const nextLayout = getNavigationLayout();
|
|
1955
1876
|
if (nextLayout === layoutRef.current) return;
|
|
1956
1877
|
if (document.activeElement instanceof HTMLElement) focusRef.current = {
|
|
1957
|
-
navigation: document.activeElement.closest("[data-
|
|
1958
|
-
drawer: document.activeElement === openButtonRef.current || document.activeElement.closest("[data-
|
|
1878
|
+
navigation: document.activeElement.closest("[data-sidebar-docs]") !== null,
|
|
1879
|
+
drawer: document.activeElement === openButtonRef.current || document.activeElement.closest("[data-sidebar-drawer]") !== null,
|
|
1959
1880
|
href: document.activeElement.getAttribute("href")
|
|
1960
1881
|
};
|
|
1961
1882
|
if (nextLayout !== "drawer") closeNavigation();
|
|
@@ -2000,11 +1921,14 @@ var WgNavigation = (props) => {
|
|
|
2000
1921
|
useLayoutEffect(() => {
|
|
2001
1922
|
if (props.stores === void 0) return;
|
|
2002
1923
|
let active = true;
|
|
1924
|
+
/**
|
|
1925
|
+
* 연결된 배치의 좌표 복원 후 전환 전 포커스를 같은 링크나 진입 버튼으로 이동
|
|
1926
|
+
*/
|
|
2003
1927
|
const restoreNavigation = () => {
|
|
2004
1928
|
if (!active) return;
|
|
2005
1929
|
restoreScroll();
|
|
2006
1930
|
if (focusRef.current === null) return;
|
|
2007
|
-
const navigation =
|
|
1931
|
+
const navigation = layoutRef.current === "drawer" ? drawerNavigationRef.current : navigationRef.current;
|
|
2008
1932
|
if (layout === "drawer") {
|
|
2009
1933
|
if (focusRef.current.navigation) openButtonRef.current?.focus({ preventScroll: true });
|
|
2010
1934
|
} else if (focusRef.current.navigation && navigation !== null) {
|
|
@@ -2021,26 +1945,25 @@ var WgNavigation = (props) => {
|
|
|
2021
1945
|
}, [layout, props.stores]);
|
|
2022
1946
|
return /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
2023
1947
|
/* @__PURE__ */ jsxs("div", {
|
|
2024
|
-
className: clsx("
|
|
1948
|
+
className: clsx("wg_sidebar__docsColumn", props.stores !== void 0 && "wg_sidebar__docsColumn--ready"),
|
|
2025
1949
|
children: [/* @__PURE__ */ jsxs("div", {
|
|
2026
|
-
className: clsx("
|
|
1950
|
+
className: clsx("wg_sidebar__brandRow"),
|
|
2027
1951
|
children: [/* @__PURE__ */ jsx("a", {
|
|
2028
1952
|
ref: props.stores === void 0 ? void 0 : brandRef,
|
|
2029
|
-
className: clsx("
|
|
1953
|
+
className: clsx("wg_sidebar__brand"),
|
|
2030
1954
|
href: "/",
|
|
2031
1955
|
"aria-current": props.data.home ? "page" : void 0,
|
|
2032
1956
|
children: props.data.title
|
|
2033
1957
|
}), /* @__PURE__ */ jsx("button", {
|
|
2034
1958
|
ref: props.stores === void 0 ? void 0 : openButtonRef,
|
|
2035
|
-
className: clsx("
|
|
1959
|
+
className: clsx("wg_sidebar__menuToggle", props.stores !== void 0 && layout === "drawer" && "wg_sidebar__menuToggle--visible"),
|
|
2036
1960
|
type: "button",
|
|
2037
1961
|
"aria-label": copy_nav_open,
|
|
2038
1962
|
"aria-controls": "fh-navigation-drawer",
|
|
2039
1963
|
"aria-expanded": String(drawerOpen),
|
|
2040
|
-
"data-navigation-open": "",
|
|
2041
1964
|
onClick: handleOpenButtonClick,
|
|
2042
1965
|
children: /* @__PURE__ */ jsx("svg", {
|
|
2043
|
-
className: clsx("
|
|
1966
|
+
className: clsx("wg_sidebar__menuIcon"),
|
|
2044
1967
|
viewBox: "0 0 24 24",
|
|
2045
1968
|
"aria-hidden": "true",
|
|
2046
1969
|
focusable: "false",
|
|
@@ -2049,58 +1972,56 @@ var WgNavigation = (props) => {
|
|
|
2049
1972
|
})]
|
|
2050
1973
|
}), layout !== "drawer" && /* @__PURE__ */ jsx("div", {
|
|
2051
1974
|
ref: props.stores === void 0 ? void 0 : navigationRef,
|
|
2052
|
-
className: clsx("
|
|
2053
|
-
"data-
|
|
2054
|
-
"data-
|
|
2055
|
-
children: /* @__PURE__ */ jsx(
|
|
1975
|
+
className: clsx("wg_sidebar__docsPanel"),
|
|
1976
|
+
"data-sidebar-docs": "",
|
|
1977
|
+
"data-sidebar-layout": layout,
|
|
1978
|
+
children: /* @__PURE__ */ jsx(WgSidebarScroll, {
|
|
2056
1979
|
portRef: documentsRef,
|
|
2057
1980
|
kind: "documents",
|
|
2058
1981
|
ready: props.stores !== void 0,
|
|
2059
1982
|
onScroll: saveScroll,
|
|
2060
1983
|
onClick: handleNavigationClick,
|
|
2061
|
-
children: /* @__PURE__ */ jsx(
|
|
1984
|
+
children: /* @__PURE__ */ jsx(WgDocTree, { groups: props.data.groups })
|
|
2062
1985
|
})
|
|
2063
1986
|
})]
|
|
2064
1987
|
}),
|
|
2065
1988
|
/* @__PURE__ */ jsx("aside", {
|
|
2066
|
-
className: clsx("
|
|
2067
|
-
"data-
|
|
1989
|
+
className: clsx("wg_sidebar__tocColumn"),
|
|
1990
|
+
"data-sidebar-toc": "",
|
|
2068
1991
|
children: /* @__PURE__ */ jsx("div", {
|
|
2069
|
-
className: clsx("
|
|
2070
|
-
|
|
2071
|
-
children: /* @__PURE__ */ jsx(WgNavigationScroll, {
|
|
1992
|
+
className: clsx("wg_sidebar__tocPanel"),
|
|
1993
|
+
children: /* @__PURE__ */ jsx(WgSidebarScroll, {
|
|
2072
1994
|
portRef: outlineRef,
|
|
2073
1995
|
kind: "outline",
|
|
2074
1996
|
ready: props.stores !== void 0,
|
|
2075
1997
|
onScroll: saveScroll,
|
|
2076
1998
|
onClick: handleNavigationClick,
|
|
2077
|
-
children: props.data.outline.length > 0 && /* @__PURE__ */ jsx(
|
|
1999
|
+
children: props.data.outline.length > 0 && /* @__PURE__ */ jsx(WgToc, { groups: props.data.outline })
|
|
2078
2000
|
})
|
|
2079
2001
|
})
|
|
2080
2002
|
}),
|
|
2081
2003
|
/* @__PURE__ */ jsxs("dialog", {
|
|
2082
2004
|
ref: props.stores === void 0 ? void 0 : dialogRef,
|
|
2083
|
-
className: clsx("
|
|
2005
|
+
className: clsx("wg_sidebar__drawer"),
|
|
2084
2006
|
id: "fh-navigation-drawer",
|
|
2085
2007
|
"aria-labelledby": "fh-navigation-title",
|
|
2086
|
-
"data-
|
|
2008
|
+
"data-sidebar-drawer": "",
|
|
2087
2009
|
onClose: handleDrawerClose,
|
|
2088
2010
|
onCancel: handleDrawerCancel,
|
|
2089
2011
|
onClick: handleDrawerClick,
|
|
2090
2012
|
children: [/* @__PURE__ */ jsxs("div", {
|
|
2091
|
-
className: clsx("
|
|
2013
|
+
className: clsx("wg_sidebar__drawerHeader"),
|
|
2092
2014
|
children: [/* @__PURE__ */ jsx("h2", {
|
|
2093
|
-
className: clsx("
|
|
2015
|
+
className: clsx("wg_sidebar__drawerTitle"),
|
|
2094
2016
|
id: "fh-navigation-title",
|
|
2095
2017
|
children: copy_nav_title
|
|
2096
2018
|
}), /* @__PURE__ */ jsx("button", {
|
|
2097
|
-
className: clsx("
|
|
2019
|
+
className: clsx("wg_sidebar__drawerClose"),
|
|
2098
2020
|
type: "button",
|
|
2099
2021
|
"aria-label": copy_nav_close,
|
|
2100
|
-
"data-navigation-close": "",
|
|
2101
2022
|
onClick: handleCloseButtonClick,
|
|
2102
2023
|
children: /* @__PURE__ */ jsx("svg", {
|
|
2103
|
-
className: clsx("
|
|
2024
|
+
className: clsx("wg_sidebar__menuIcon"),
|
|
2104
2025
|
viewBox: "0 0 24 24",
|
|
2105
2026
|
"aria-hidden": "true",
|
|
2106
2027
|
focusable: "false",
|
|
@@ -2108,20 +2029,19 @@ var WgNavigation = (props) => {
|
|
|
2108
2029
|
})
|
|
2109
2030
|
})]
|
|
2110
2031
|
}), /* @__PURE__ */ jsx("div", {
|
|
2111
|
-
className: clsx("
|
|
2112
|
-
"data-navigation-content": "",
|
|
2032
|
+
className: clsx("wg_sidebar__drawerContent"),
|
|
2113
2033
|
children: layout === "drawer" && /* @__PURE__ */ jsx("div", {
|
|
2114
2034
|
ref: props.stores === void 0 ? void 0 : drawerNavigationRef,
|
|
2115
|
-
className: clsx("
|
|
2116
|
-
"data-
|
|
2117
|
-
"data-
|
|
2118
|
-
children: /* @__PURE__ */ jsx(
|
|
2035
|
+
className: clsx("wg_sidebar__docsPanel"),
|
|
2036
|
+
"data-sidebar-docs": "",
|
|
2037
|
+
"data-sidebar-layout": layout,
|
|
2038
|
+
children: /* @__PURE__ */ jsx(WgSidebarScroll, {
|
|
2119
2039
|
portRef: drawerDocumentsRef,
|
|
2120
2040
|
kind: "documents",
|
|
2121
2041
|
ready: props.stores !== void 0,
|
|
2122
2042
|
onScroll: saveScroll,
|
|
2123
2043
|
onClick: handleNavigationClick,
|
|
2124
|
-
children: /* @__PURE__ */ jsx(
|
|
2044
|
+
children: /* @__PURE__ */ jsx(WgDocTree, { groups: props.data.groups })
|
|
2125
2045
|
})
|
|
2126
2046
|
})
|
|
2127
2047
|
})]
|
|
@@ -2129,12 +2049,6 @@ var WgNavigation = (props) => {
|
|
|
2129
2049
|
] });
|
|
2130
2050
|
};
|
|
2131
2051
|
//#endregion
|
|
2132
|
-
//#region src/component/widget/prose/_constant/heading-highlight.ts
|
|
2133
|
-
/**
|
|
2134
|
-
* 앵커 이동 후 제목 강조 유지 시간. 이후 CSS로 서서히 해제
|
|
2135
|
-
*/
|
|
2136
|
-
var heading_highlight_hold_ms = 3e3;
|
|
2137
|
-
//#endregion
|
|
2138
2052
|
//#region src/component/widget/shell-controls/wg-shell-controls.tsx
|
|
2139
2053
|
var WgShellControls = (props) => {
|
|
2140
2054
|
const headingRef = useRef(null);
|
|
@@ -2150,7 +2064,7 @@ var WgShellControls = (props) => {
|
|
|
2150
2064
|
timerRef.current = 0;
|
|
2151
2065
|
arrivalRef.current?.abort();
|
|
2152
2066
|
arrivalRef.current = null;
|
|
2153
|
-
headingRef.current?.classList.remove("
|
|
2067
|
+
headingRef.current?.classList.remove("wg_article__headingText--highlighted");
|
|
2154
2068
|
headingRef.current = null;
|
|
2155
2069
|
};
|
|
2156
2070
|
/**
|
|
@@ -2168,9 +2082,9 @@ var WgShellControls = (props) => {
|
|
|
2168
2082
|
*/
|
|
2169
2083
|
const highlightHeading = (target) => {
|
|
2170
2084
|
clearHeadingHighlight();
|
|
2171
|
-
headingRef.current = target === null ? null : target.querySelector(".
|
|
2085
|
+
headingRef.current = target === null ? null : target.querySelector(".wg_article__headingText");
|
|
2172
2086
|
if (headingRef.current === null) return;
|
|
2173
|
-
headingRef.current.classList.add("
|
|
2087
|
+
headingRef.current.classList.add("wg_article__headingText--highlighted");
|
|
2174
2088
|
timerRef.current = window.setTimeout(clearHeadingHighlight, heading_highlight_hold_ms);
|
|
2175
2089
|
arrivalRef.current = new AbortController();
|
|
2176
2090
|
document.addEventListener("scrollend", handleHighlightArrival, { signal: arrivalRef.current.signal });
|
|
@@ -2179,12 +2093,10 @@ var WgShellControls = (props) => {
|
|
|
2179
2093
|
* 탐색 DOM이 다시 만들어져도 같은 hash의 접힌 Details 공개를 유지
|
|
2180
2094
|
*/
|
|
2181
2095
|
const handleHashClick = (event) => {
|
|
2182
|
-
|
|
2183
|
-
|
|
2184
|
-
|
|
2185
|
-
|
|
2186
|
-
if (link.hash === location.hash) highlightHeading(target);
|
|
2187
|
-
}
|
|
2096
|
+
const link = findHashLink(event);
|
|
2097
|
+
if (link === null) return;
|
|
2098
|
+
const target = revealHashTarget(link.hash);
|
|
2099
|
+
if (link.hash === location.hash) highlightHeading(target);
|
|
2188
2100
|
};
|
|
2189
2101
|
/**
|
|
2190
2102
|
* hash 변경 시 접힌 대상을 공개하고 네이티브 스크롤, 강조 적용
|
|
@@ -2194,23 +2106,31 @@ var WgShellControls = (props) => {
|
|
|
2194
2106
|
target?.scrollIntoView();
|
|
2195
2107
|
highlightHeading(target);
|
|
2196
2108
|
};
|
|
2109
|
+
/**
|
|
2110
|
+
* 첫 사용자 입력. 초기 앵커 보정 중단, 부드러운 절 이동 활성화
|
|
2111
|
+
*/
|
|
2197
2112
|
const handleUserScroll = () => {
|
|
2198
2113
|
userScrollRef.current?.abort();
|
|
2114
|
+
document.documentElement.style.scrollBehavior = "smooth";
|
|
2199
2115
|
};
|
|
2200
2116
|
/**
|
|
2201
2117
|
* 초기 폰트 로드 후 앵커 보정. 해제된 인스턴스와 사용자 이동에 개입하지 않음
|
|
2118
|
+
*
|
|
2119
|
+
* - 새로고침, 뒤로가기는 브라우저의 이전 위치 복원 사용
|
|
2120
|
+
* - 부드러운 절 이동은 보정 후 활성화, 복원 진입은 첫 사용자 입력까지 즉시 스크롤 유지
|
|
2202
2121
|
*/
|
|
2203
2122
|
const handlePageLoad = async () => {
|
|
2204
2123
|
const lifetime = lifetimeRef.current;
|
|
2205
2124
|
const hash = location.hash;
|
|
2206
2125
|
const target = revealHashTarget(hash);
|
|
2126
|
+
if (isRestoringNavigation()) return;
|
|
2207
2127
|
await document.fonts.ready;
|
|
2208
2128
|
if (lifetime === null || lifetime.signal.aborted) return;
|
|
2209
2129
|
if (target !== null && !userScrollRef.current?.signal.aborted && hash === location.hash) {
|
|
2210
2130
|
target.scrollIntoView({ behavior: "instant" });
|
|
2211
2131
|
highlightHeading(target);
|
|
2212
2132
|
}
|
|
2213
|
-
document.documentElement.style.
|
|
2133
|
+
document.documentElement.style.scrollBehavior = "smooth";
|
|
2214
2134
|
};
|
|
2215
2135
|
/**
|
|
2216
2136
|
* 셸은 본문 앞에서 마운트. 본문 파싱 이후에만 초기 접힘 대상 조회
|
|
@@ -2226,19 +2146,13 @@ var WgShellControls = (props) => {
|
|
|
2226
2146
|
const lifetime = new AbortController();
|
|
2227
2147
|
lifetimeRef.current = lifetime;
|
|
2228
2148
|
userScrollRef.current = new AbortController();
|
|
2229
|
-
document.documentElement.style.scrollBehavior = "auto";
|
|
2230
2149
|
document.addEventListener("click", handleHashClick, { signal: lifetime.signal });
|
|
2231
2150
|
addEventListener("hashchange", handleHashChange, { signal: lifetime.signal });
|
|
2232
2151
|
addEventListener("load", handlePageLoad, {
|
|
2233
2152
|
once: true,
|
|
2234
2153
|
signal: lifetime.signal
|
|
2235
2154
|
});
|
|
2236
|
-
for (const type of
|
|
2237
|
-
"wheel",
|
|
2238
|
-
"touchmove",
|
|
2239
|
-
"keydown",
|
|
2240
|
-
"mousedown"
|
|
2241
|
-
]) addEventListener(type, handleUserScroll, {
|
|
2155
|
+
for (const type of scroll_user_input_events) addEventListener(type, handleUserScroll, {
|
|
2242
2156
|
once: true,
|
|
2243
2157
|
passive: true,
|
|
2244
2158
|
signal: lifetime.signal
|
|
@@ -2259,8 +2173,8 @@ var WgShellControls = (props) => {
|
|
|
2259
2173
|
}, []);
|
|
2260
2174
|
return /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
2261
2175
|
/* @__PURE__ */ jsx("div", {
|
|
2262
|
-
className: clsx("
|
|
2263
|
-
children: /* @__PURE__ */ jsx(
|
|
2176
|
+
className: clsx("wg_shellControls__sidebar"),
|
|
2177
|
+
children: /* @__PURE__ */ jsx(WgSidebar, {
|
|
2264
2178
|
data: props.data,
|
|
2265
2179
|
stores: props.stores
|
|
2266
2180
|
})
|
|
@@ -2270,6 +2184,159 @@ var WgShellControls = (props) => {
|
|
|
2270
2184
|
] });
|
|
2271
2185
|
};
|
|
2272
2186
|
//#endregion
|
|
2187
|
+
//#region src/component/widget/sidebar/_function/to-doc-groups/_to-doc-branches.ts
|
|
2188
|
+
/**
|
|
2189
|
+
* 검증된 부모 관계로 문서 가지를 재귀 구성. 형제 순서와 URL 유지
|
|
2190
|
+
*/
|
|
2191
|
+
var toDocBranches = (options) => {
|
|
2192
|
+
return options.docs.toSorted((a, b) => a.data.order - b.data.order || a.data.name.localeCompare(b.data.name, "en") || a.id.localeCompare(b.id, "en")).map((doc) => {
|
|
2193
|
+
const children = options.children.get(doc.id);
|
|
2194
|
+
return {
|
|
2195
|
+
...doc,
|
|
2196
|
+
children: children === void 0 ? void 0 : toDocBranches({
|
|
2197
|
+
docs: children,
|
|
2198
|
+
children: options.children
|
|
2199
|
+
})
|
|
2200
|
+
};
|
|
2201
|
+
});
|
|
2202
|
+
};
|
|
2203
|
+
//#endregion
|
|
2204
|
+
//#region src/component/widget/sidebar/_function/to-doc-groups/_to-doc-group-branches.ts
|
|
2205
|
+
/**
|
|
2206
|
+
* 각 단계의 직접 문서와 하위 묶음을 분리. 고정된 깊이 제한 없이 구성
|
|
2207
|
+
*/
|
|
2208
|
+
var toDocGroupBranches = (options) => {
|
|
2209
|
+
return [...Map.groupBy(options.docs.filter((doc) => doc.data.group.length > options.path.length), (doc) => doc.data.group[options.path.length])].map(([name, docs]) => {
|
|
2210
|
+
const path = [...options.path, name];
|
|
2211
|
+
return {
|
|
2212
|
+
name,
|
|
2213
|
+
path,
|
|
2214
|
+
docs,
|
|
2215
|
+
position: options.positions.get(JSON.stringify(path)) ?? options.unlistedPosition
|
|
2216
|
+
};
|
|
2217
|
+
}).toSorted((a, b) => a.position - b.position || a.name.localeCompare(b.name, "ko")).map((group) => {
|
|
2218
|
+
return {
|
|
2219
|
+
name: group.name,
|
|
2220
|
+
path: group.path,
|
|
2221
|
+
docs: toDocBranches({
|
|
2222
|
+
docs: group.docs.filter((doc) => doc.data.group.length === group.path.length && doc.data.parent === void 0),
|
|
2223
|
+
children: options.children
|
|
2224
|
+
}),
|
|
2225
|
+
groups: toDocGroupBranches({
|
|
2226
|
+
docs: group.docs,
|
|
2227
|
+
children: options.children,
|
|
2228
|
+
path: group.path,
|
|
2229
|
+
positions: options.positions,
|
|
2230
|
+
unlistedPosition: options.unlistedPosition
|
|
2231
|
+
})
|
|
2232
|
+
};
|
|
2233
|
+
});
|
|
2234
|
+
};
|
|
2235
|
+
//#endregion
|
|
2236
|
+
//#region src/component/widget/sidebar/_function/to-doc-groups/to-doc-groups.ts
|
|
2237
|
+
/**
|
|
2238
|
+
* 메타데이터 경로로 읽는 순서를 가진 문서 계층 구성
|
|
2239
|
+
*
|
|
2240
|
+
* - 같은 조상의 중복 지정은 최초 순서 유지
|
|
2241
|
+
* - 미지정 형제 묶음은 이름순
|
|
2242
|
+
*/
|
|
2243
|
+
var toDocGroups = (options) => {
|
|
2244
|
+
const positions = new Map(uniqBy(options.navigation.flatMap((path, index) => {
|
|
2245
|
+
return path.map((_name, depth) => [JSON.stringify(path.slice(0, depth + 1)), index]);
|
|
2246
|
+
}), (entry) => entry[0]));
|
|
2247
|
+
return toDocGroupBranches({
|
|
2248
|
+
docs: options.docs,
|
|
2249
|
+
children: Map.groupBy(options.docs, (doc) => doc.data.parent),
|
|
2250
|
+
path: [],
|
|
2251
|
+
positions,
|
|
2252
|
+
unlistedPosition: options.navigation.length
|
|
2253
|
+
});
|
|
2254
|
+
};
|
|
2255
|
+
//#endregion
|
|
2256
|
+
//#region src/component/widget/sidebar/_function/to-navigation-data/_to-navigation-document.ts
|
|
2257
|
+
/**
|
|
2258
|
+
* 본문 없는 문서 가지. 재귀에서도 현재 페이지 한 항목만 표시
|
|
2259
|
+
*/
|
|
2260
|
+
var toNavigationDocument = (options) => {
|
|
2261
|
+
return {
|
|
2262
|
+
id: options.doc.id,
|
|
2263
|
+
name: options.doc.data.name,
|
|
2264
|
+
label: options.doc.data.label,
|
|
2265
|
+
active: options.doc.id === options.current,
|
|
2266
|
+
children: options.doc.children === void 0 ? [] : options.doc.children.map((doc) => toNavigationDocument({
|
|
2267
|
+
...options,
|
|
2268
|
+
doc
|
|
2269
|
+
}))
|
|
2270
|
+
};
|
|
2271
|
+
};
|
|
2272
|
+
//#endregion
|
|
2273
|
+
//#region src/component/widget/sidebar/_function/to-navigation-data/_to-navigation-group.ts
|
|
2274
|
+
/**
|
|
2275
|
+
* 전체 묶음 경로와 본문 없는 하위 가지
|
|
2276
|
+
*/
|
|
2277
|
+
var toNavigationGroup = (options) => {
|
|
2278
|
+
return {
|
|
2279
|
+
name: options.group.name,
|
|
2280
|
+
path: options.group.path,
|
|
2281
|
+
docs: options.group.docs.map((doc) => toNavigationDocument({
|
|
2282
|
+
doc,
|
|
2283
|
+
current: options.current
|
|
2284
|
+
})),
|
|
2285
|
+
groups: options.group.groups.map((group) => toNavigationGroup({
|
|
2286
|
+
...options,
|
|
2287
|
+
group
|
|
2288
|
+
}))
|
|
2289
|
+
};
|
|
2290
|
+
};
|
|
2291
|
+
//#endregion
|
|
2292
|
+
//#region src/component/widget/sidebar/_function/to-navigation-data/to-navigation-data.ts
|
|
2293
|
+
/**
|
|
2294
|
+
* 현재 문서의 이름, 계층, 목차만 전송. 다른 문서 본문은 브라우저 탐색 입력에서 제외
|
|
2295
|
+
*
|
|
2296
|
+
* - 목차는 h2별 h3 수집, 가름 시작 절에서 목차 묶음 분리
|
|
2297
|
+
* - 첫 h2 앞의 h3은 목차 제외
|
|
2298
|
+
*/
|
|
2299
|
+
var toNavigationData = (options) => {
|
|
2300
|
+
const entries = options.outline === void 0 ? [] : zip(options.outline.headings.filter((heading) => heading.depth === section_heading_depth.section || heading.depth === section_heading_depth.sub), options.outline.sections);
|
|
2301
|
+
const sections = entries.flatMap(([heading, section], index) => {
|
|
2302
|
+
if (heading.depth !== section_heading_depth.section) return [];
|
|
2303
|
+
return [{
|
|
2304
|
+
heading,
|
|
2305
|
+
part: section.part,
|
|
2306
|
+
subs: takeWhile(entries.slice(index + 1), ([sub]) => sub.depth !== section_heading_depth.section).map(([sub]) => {
|
|
2307
|
+
return { heading: sub };
|
|
2308
|
+
})
|
|
2309
|
+
}];
|
|
2310
|
+
});
|
|
2311
|
+
return {
|
|
2312
|
+
title: options.site.title,
|
|
2313
|
+
groups: toDocGroups({
|
|
2314
|
+
docs: options.docs,
|
|
2315
|
+
navigation: options.site.navigation
|
|
2316
|
+
}).map((group) => toNavigationGroup({
|
|
2317
|
+
group,
|
|
2318
|
+
current: options.current
|
|
2319
|
+
})),
|
|
2320
|
+
outline: sections.flatMap((section, index) => {
|
|
2321
|
+
if (index > 0 && section.part === void 0) return [];
|
|
2322
|
+
return [{
|
|
2323
|
+
part: section.part,
|
|
2324
|
+
sections: [section, ...takeWhile(sections.slice(index + 1), (next) => next.part === void 0)]
|
|
2325
|
+
}];
|
|
2326
|
+
}),
|
|
2327
|
+
home: options.current === void 0,
|
|
2328
|
+
cursorFace: options.site.cursorFace
|
|
2329
|
+
};
|
|
2330
|
+
};
|
|
2331
|
+
//#endregion
|
|
2332
|
+
//#region src/component/widget/sidebar/_function/to-navigation-json.ts
|
|
2333
|
+
/**
|
|
2334
|
+
* 작성한 이름이 script 종료 태그나 JavaScript 줄 구분자로 해석되지 않는 JSON
|
|
2335
|
+
*/
|
|
2336
|
+
var toNavigationJson = (data) => {
|
|
2337
|
+
return JSON.stringify(data).replaceAll("<", "\\u003c").replaceAll("\u2028", "\\u2028").replaceAll("\u2029", "\\u2029");
|
|
2338
|
+
};
|
|
2339
|
+
//#endregion
|
|
2273
2340
|
//#region src/component/widget/shell/wg-shell.tsx
|
|
2274
2341
|
var WgShell = (props) => {
|
|
2275
2342
|
const navigationData = toNavigationData({
|
|
@@ -2350,7 +2417,7 @@ var PgDoc = (props) => {
|
|
|
2350
2417
|
title: props.doc.data.name,
|
|
2351
2418
|
current: props.doc.id,
|
|
2352
2419
|
outline: props.doc.outline,
|
|
2353
|
-
children: /* @__PURE__ */ jsx(
|
|
2420
|
+
children: /* @__PURE__ */ jsx(WgArticle, { html: props.doc.html })
|
|
2354
2421
|
});
|
|
2355
2422
|
};
|
|
2356
2423
|
//#endregion
|
|
@@ -2361,7 +2428,7 @@ var PgHome = (props) => {
|
|
|
2361
2428
|
docs: props.docs,
|
|
2362
2429
|
assets: props.assets,
|
|
2363
2430
|
outline: props.home?.outline,
|
|
2364
|
-
children: [props.home !== void 0 && /* @__PURE__ */ jsx(
|
|
2431
|
+
children: [props.home !== void 0 && /* @__PURE__ */ jsx(WgArticle, { html: props.home.html }), props.home === void 0 && /* @__PURE__ */ jsx(WgArticle, { head: {
|
|
2365
2432
|
title: props.site.title,
|
|
2366
2433
|
lead: "문서 폴더에 README.md를 추가하면 이곳에 홈 화면으로 표시됩니다."
|
|
2367
2434
|
} })]
|
|
@@ -2449,11 +2516,25 @@ var loadSite = async (params) => {
|
|
|
2449
2516
|
...brand.files
|
|
2450
2517
|
]);
|
|
2451
2518
|
if (params.stylePaths === void 0) files.set(stylePaths[0], join(params.kitRoot, "dist/cli.css"));
|
|
2452
|
-
|
|
2453
|
-
|
|
2454
|
-
|
|
2519
|
+
if (!existsSync(params.root) || !statSync(params.root).isDirectory()) throw new Error(`content 문서 루트가 없습니다: ${params.root}`);
|
|
2520
|
+
const root = realpathSync(params.root);
|
|
2521
|
+
const publicRoot = join(root, params.site.content.public);
|
|
2522
|
+
const privateRoot = join(root, params.site.content.private);
|
|
2523
|
+
for (const path of [publicRoot, privateRoot]) {
|
|
2524
|
+
if (!existsSync(path)) {
|
|
2525
|
+
if (path === privateRoot) continue;
|
|
2526
|
+
throw new Error(`공개 문서 폴더가 없습니다: ${path}. content/public 구조 또는 content 설정을 확인하세요`);
|
|
2527
|
+
}
|
|
2528
|
+
const actual = realpathSync(path);
|
|
2529
|
+
const location = relative(root, actual);
|
|
2530
|
+
if (!statSync(path).isDirectory() || actual !== path || location === ".." || location.startsWith(`..${sep}`) || isAbsolute(location)) throw new Error(`문서 폴더는 content 내부의 실제 폴더여야 합니다: ${path}`);
|
|
2531
|
+
}
|
|
2532
|
+
const scope = {
|
|
2533
|
+
root,
|
|
2534
|
+
publicRoot,
|
|
2535
|
+
privateRoot,
|
|
2455
2536
|
includePrivate: params.includePrivate === true
|
|
2456
|
-
}
|
|
2537
|
+
};
|
|
2457
2538
|
const processor = createProcessor({
|
|
2458
2539
|
site: params.site,
|
|
2459
2540
|
root: scope.root,
|