@mandujs/core 0.13.0 → 0.13.2
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/README.ko.md +4 -4
- package/README.md +653 -653
- package/package.json +1 -1
- package/src/bundler/build.ts +91 -91
- package/src/bundler/css.ts +302 -302
- package/src/client/Link.tsx +227 -227
- package/src/client/globals.ts +44 -44
- package/src/client/hooks.ts +267 -267
- package/src/client/index.ts +5 -5
- package/src/client/island.ts +8 -8
- package/src/client/router.ts +435 -435
- package/src/client/runtime.ts +23 -23
- package/src/client/serialize.ts +404 -404
- package/src/client/window-state.ts +101 -101
- package/src/config/mandu.ts +9 -0
- package/src/config/validate.ts +12 -0
- package/src/config/watcher.ts +311 -311
- package/src/constants.ts +40 -40
- package/src/content/content-layer.ts +314 -314
- package/src/content/content.test.ts +433 -433
- package/src/content/data-store.ts +245 -245
- package/src/content/digest.ts +133 -133
- package/src/content/index.ts +164 -164
- package/src/content/loader-context.ts +172 -172
- package/src/content/loaders/api.ts +216 -216
- package/src/content/loaders/file.ts +169 -169
- package/src/content/loaders/glob.ts +252 -252
- package/src/content/loaders/index.ts +34 -34
- package/src/content/loaders/types.ts +137 -137
- package/src/content/meta-store.ts +209 -209
- package/src/content/types.ts +282 -282
- package/src/content/watcher.ts +135 -135
- package/src/contract/client-safe.test.ts +42 -42
- package/src/contract/client-safe.ts +114 -114
- package/src/contract/client.ts +16 -16
- package/src/contract/define.ts +459 -459
- package/src/contract/handler.ts +10 -10
- package/src/contract/normalize.test.ts +276 -276
- package/src/contract/normalize.ts +404 -404
- package/src/contract/registry.test.ts +206 -206
- package/src/contract/registry.ts +568 -568
- package/src/contract/schema.ts +48 -48
- package/src/contract/types.ts +58 -58
- package/src/contract/validator.ts +32 -32
- package/src/devtools/ai/context-builder.ts +375 -375
- package/src/devtools/ai/index.ts +25 -25
- package/src/devtools/ai/mcp-connector.ts +465 -465
- package/src/devtools/client/catchers/error-catcher.ts +327 -327
- package/src/devtools/client/catchers/index.ts +18 -18
- package/src/devtools/client/catchers/network-proxy.ts +363 -363
- package/src/devtools/client/components/index.ts +39 -39
- package/src/devtools/client/components/kitchen-root.tsx +362 -362
- package/src/devtools/client/components/mandu-character.tsx +241 -241
- package/src/devtools/client/components/overlay.tsx +368 -368
- package/src/devtools/client/components/panel/errors-panel.tsx +259 -259
- package/src/devtools/client/components/panel/guard-panel.tsx +244 -244
- package/src/devtools/client/components/panel/index.ts +32 -32
- package/src/devtools/client/components/panel/islands-panel.tsx +304 -304
- package/src/devtools/client/components/panel/network-panel.tsx +292 -292
- package/src/devtools/client/components/panel/panel-container.tsx +259 -259
- package/src/devtools/client/filters/context-filters.ts +282 -282
- package/src/devtools/client/filters/index.ts +16 -16
- package/src/devtools/client/index.ts +63 -63
- package/src/devtools/client/persistence.ts +335 -335
- package/src/devtools/client/state-manager.ts +478 -478
- package/src/devtools/design-tokens.ts +263 -263
- package/src/devtools/hook/create-hook.ts +207 -207
- package/src/devtools/hook/index.ts +13 -13
- package/src/devtools/index.ts +439 -439
- package/src/devtools/init.ts +266 -266
- package/src/devtools/protocol.ts +237 -237
- package/src/devtools/server/index.ts +17 -17
- package/src/devtools/server/source-context.ts +444 -444
- package/src/devtools/types.ts +319 -319
- package/src/devtools/worker/index.ts +25 -25
- package/src/devtools/worker/redaction-worker.ts +222 -222
- package/src/devtools/worker/worker-manager.ts +409 -409
- package/src/error/domains.ts +265 -265
- package/src/error/result.ts +46 -46
- package/src/error/types.ts +6 -6
- package/src/errors/extractor.ts +409 -409
- package/src/errors/index.ts +19 -19
- package/src/filling/auth.ts +308 -308
- package/src/filling/context.ts +24 -1
- package/src/filling/deps.ts +238 -238
- package/src/filling/index.ts +4 -0
- package/src/filling/sse-catchup.test.ts +56 -0
- package/src/filling/sse-catchup.ts +67 -0
- package/src/filling/sse.test.ts +168 -0
- package/src/filling/sse.ts +162 -0
- package/src/generator/index.ts +3 -3
- package/src/guard/analyzer.ts +360 -360
- package/src/guard/ast-analyzer.ts +806 -806
- package/src/guard/contract-guard.ts +9 -9
- package/src/guard/file-type.test.ts +24 -24
- package/src/guard/presets/atomic.ts +70 -70
- package/src/guard/presets/clean.ts +77 -77
- package/src/guard/presets/fsd.ts +79 -79
- package/src/guard/presets/hexagonal.ts +68 -68
- package/src/guard/presets/index.ts +291 -291
- package/src/guard/reporter.ts +445 -445
- package/src/guard/rules.ts +12 -12
- package/src/guard/statistics.ts +578 -578
- package/src/guard/suggestions.ts +358 -358
- package/src/guard/types.ts +348 -348
- package/src/guard/validator.ts +834 -834
- package/src/guard/watcher.ts +404 -404
- package/src/index.ts +6 -1
- package/src/intent/index.ts +310 -310
- package/src/island/index.ts +304 -304
- package/src/logging/index.ts +22 -22
- package/src/logging/transports.ts +365 -365
- package/src/plugins/index.ts +38 -38
- package/src/plugins/registry.ts +377 -377
- package/src/plugins/types.ts +363 -363
- package/src/report/index.ts +1 -1
- package/src/router/fs-patterns.ts +387 -387
- package/src/router/fs-scanner.ts +497 -497
- package/src/runtime/boundary.tsx +232 -232
- package/src/runtime/compose.ts +222 -222
- package/src/runtime/escape.ts +44 -0
- package/src/runtime/lifecycle.ts +381 -381
- package/src/runtime/logger.test.ts +345 -345
- package/src/runtime/logger.ts +677 -677
- package/src/runtime/router.test.ts +476 -476
- package/src/runtime/router.ts +105 -105
- package/src/runtime/security.ts +155 -155
- package/src/runtime/server.ts +257 -0
- package/src/runtime/session-key.ts +328 -328
- package/src/runtime/ssr.ts +16 -21
- package/src/runtime/streaming-ssr.ts +24 -33
- package/src/runtime/trace.ts +144 -144
- package/src/seo/index.ts +214 -214
- package/src/seo/integration/ssr.ts +307 -307
- package/src/seo/render/basic.ts +427 -427
- package/src/seo/render/index.ts +143 -143
- package/src/seo/render/jsonld.ts +539 -539
- package/src/seo/render/opengraph.ts +191 -191
- package/src/seo/render/robots.ts +116 -116
- package/src/seo/render/sitemap.ts +137 -137
- package/src/seo/render/twitter.ts +126 -126
- package/src/seo/resolve/index.ts +353 -353
- package/src/seo/resolve/opengraph.ts +143 -143
- package/src/seo/resolve/robots.ts +73 -73
- package/src/seo/resolve/title.ts +94 -94
- package/src/seo/resolve/twitter.ts +73 -73
- package/src/seo/resolve/url.ts +97 -97
- package/src/seo/routes/index.ts +290 -290
- package/src/seo/types.ts +575 -575
- package/src/slot/validator.ts +39 -39
- package/src/spec/index.ts +3 -3
- package/src/spec/load.ts +76 -76
- package/src/spec/lock.ts +56 -56
- package/src/utils/bun.ts +8 -8
- package/src/utils/lru-cache.ts +75 -75
- package/src/utils/safe-io.ts +188 -188
- package/src/utils/string-safe.ts +298 -298
|
@@ -1,172 +1,172 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* LoaderContext Factory - 로더에 전달되는 컨텍스트 생성
|
|
3
|
-
*
|
|
4
|
-
* 각 컬렉션의 load() 함수에 필요한 모든 유틸리티 제공
|
|
5
|
-
*/
|
|
6
|
-
|
|
7
|
-
import type {
|
|
8
|
-
LoaderContext,
|
|
9
|
-
DataStore,
|
|
10
|
-
MetaStore,
|
|
11
|
-
ContentLogger,
|
|
12
|
-
ManduContentConfig,
|
|
13
|
-
ContentWatcher,
|
|
14
|
-
ParseDataOptions,
|
|
15
|
-
RenderedContent,
|
|
16
|
-
ValidationError as ContentValidationError,
|
|
17
|
-
} from "./types";
|
|
18
|
-
import { ValidationError } from "./types";
|
|
19
|
-
import { generateDigest } from "./digest";
|
|
20
|
-
import type { ZodSchema } from "zod";
|
|
21
|
-
|
|
22
|
-
/**
|
|
23
|
-
* LoaderContext 생성 옵션
|
|
24
|
-
*/
|
|
25
|
-
export interface CreateLoaderContextOptions {
|
|
26
|
-
/** 컬렉션 이름 */
|
|
27
|
-
collection: string;
|
|
28
|
-
/** 데이터 스토어 */
|
|
29
|
-
store: DataStore;
|
|
30
|
-
/** 메타 스토어 */
|
|
31
|
-
meta: MetaStore;
|
|
32
|
-
/** Mandu 설정 */
|
|
33
|
-
config: ManduContentConfig;
|
|
34
|
-
/** Zod 스키마 (검증용) */
|
|
35
|
-
schema?: ZodSchema;
|
|
36
|
-
/** Markdown 렌더러 */
|
|
37
|
-
markdownRenderer?: (content: string) => Promise<RenderedContent>;
|
|
38
|
-
/** 파일 감시자 */
|
|
39
|
-
watcher?: ContentWatcher;
|
|
40
|
-
/** 로거 (기본: console) */
|
|
41
|
-
logger?: ContentLogger;
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
/**
|
|
45
|
-
* 기본 콘텐츠 로거
|
|
46
|
-
*/
|
|
47
|
-
function createDefaultLogger(collection: string): ContentLogger {
|
|
48
|
-
const prefix = `[Content:${collection}]`;
|
|
49
|
-
|
|
50
|
-
return {
|
|
51
|
-
info: (msg) => console.log(`${prefix} ${msg}`),
|
|
52
|
-
warn: (msg) => console.warn(`${prefix} ${msg}`),
|
|
53
|
-
error: (msg) => console.error(`${prefix} ${msg}`),
|
|
54
|
-
debug: (msg) => {
|
|
55
|
-
if (process.env.DEBUG || process.env.NODE_ENV === "development") {
|
|
56
|
-
console.debug(`${prefix} ${msg}`);
|
|
57
|
-
}
|
|
58
|
-
},
|
|
59
|
-
};
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
/**
|
|
63
|
-
* LoaderContext 생성
|
|
64
|
-
*/
|
|
65
|
-
export function createLoaderContext(options: CreateLoaderContextOptions): LoaderContext {
|
|
66
|
-
const {
|
|
67
|
-
collection,
|
|
68
|
-
store,
|
|
69
|
-
meta,
|
|
70
|
-
config,
|
|
71
|
-
schema,
|
|
72
|
-
markdownRenderer,
|
|
73
|
-
watcher,
|
|
74
|
-
logger = createDefaultLogger(collection),
|
|
75
|
-
} = options;
|
|
76
|
-
|
|
77
|
-
/**
|
|
78
|
-
* 데이터 파싱 및 검증
|
|
79
|
-
*/
|
|
80
|
-
async function parseData<T>(parseOptions: ParseDataOptions<T>): Promise<T> {
|
|
81
|
-
const { id, data, filePath } = parseOptions;
|
|
82
|
-
|
|
83
|
-
if (!schema) {
|
|
84
|
-
// 스키마 없으면 그대로 반환
|
|
85
|
-
return data as T;
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
const result = await schema.safeParseAsync(data);
|
|
89
|
-
|
|
90
|
-
if (!result.success) {
|
|
91
|
-
const errorMessage = result.error.issues
|
|
92
|
-
.map((issue) => `${issue.path.join(".")}: ${issue.message}`)
|
|
93
|
-
.join("; ");
|
|
94
|
-
|
|
95
|
-
throw new ValidationError(
|
|
96
|
-
`Validation failed for entry "${id}"${filePath ? ` (${filePath})` : ""}: ${errorMessage}`,
|
|
97
|
-
result.error,
|
|
98
|
-
collection,
|
|
99
|
-
id
|
|
100
|
-
);
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
return result.data as T;
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
const context: LoaderContext = {
|
|
107
|
-
collection,
|
|
108
|
-
store,
|
|
109
|
-
meta,
|
|
110
|
-
logger,
|
|
111
|
-
config,
|
|
112
|
-
parseData,
|
|
113
|
-
generateDigest: (data: unknown) => generateDigest(data),
|
|
114
|
-
watcher,
|
|
115
|
-
};
|
|
116
|
-
|
|
117
|
-
// Markdown 렌더러가 있으면 추가
|
|
118
|
-
if (markdownRenderer) {
|
|
119
|
-
context.renderMarkdown = markdownRenderer;
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
return context;
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
/**
|
|
126
|
-
* 간단한 Markdown 렌더러 (기본 제공)
|
|
127
|
-
*
|
|
128
|
-
* 실제 프로젝트에서는 remark/rehype 파이프라인으로 교체 권장
|
|
129
|
-
*/
|
|
130
|
-
export function createSimpleMarkdownRenderer(): (content: string) => Promise<RenderedContent> {
|
|
131
|
-
return async (content: string): Promise<RenderedContent> => {
|
|
132
|
-
// 매우 기본적인 Markdown → HTML 변환
|
|
133
|
-
// 실제 사용 시 marked, remark 등으로 교체
|
|
134
|
-
const html = content
|
|
135
|
-
// 헤딩
|
|
136
|
-
.replace(/^### (.*$)/gim, "<h3>$1</h3>")
|
|
137
|
-
.replace(/^## (.*$)/gim, "<h2>$1</h2>")
|
|
138
|
-
.replace(/^# (.*$)/gim, "<h1>$1</h1>")
|
|
139
|
-
// 볼드/이탤릭
|
|
140
|
-
.replace(/\*\*(.+?)\*\*/g, "<strong>$1</strong>")
|
|
141
|
-
.replace(/\*(.+?)\*/g, "<em>$1</em>")
|
|
142
|
-
// 링크
|
|
143
|
-
.replace(/\[([^\]]+)\]\(([^)]+)\)/g, '<a href="$2">$1</a>')
|
|
144
|
-
// 코드 블록
|
|
145
|
-
.replace(/```([^`]+)```/g, "<pre><code>$1</code></pre>")
|
|
146
|
-
.replace(/`([^`]+)`/g, "<code>$1</code>")
|
|
147
|
-
// 줄바꿈
|
|
148
|
-
.replace(/\n\n/g, "</p><p>")
|
|
149
|
-
.replace(/\n/g, "<br>");
|
|
150
|
-
|
|
151
|
-
// 헤딩 추출
|
|
152
|
-
const headings: Array<{ depth: 1 | 2 | 3 | 4 | 5 | 6; slug: string; text: string }> = [];
|
|
153
|
-
const headingRegex = /^(#{1,6})\s+(.+)$/gm;
|
|
154
|
-
let match;
|
|
155
|
-
|
|
156
|
-
while ((match = headingRegex.exec(content)) !== null) {
|
|
157
|
-
const depth = match[1].length as 1 | 2 | 3 | 4 | 5 | 6;
|
|
158
|
-
const text = match[2].trim();
|
|
159
|
-
const slug = text
|
|
160
|
-
.toLowerCase()
|
|
161
|
-
.replace(/[^a-z0-9가-힣]+/g, "-")
|
|
162
|
-
.replace(/^-|-$/g, "");
|
|
163
|
-
|
|
164
|
-
headings.push({ depth, slug, text });
|
|
165
|
-
}
|
|
166
|
-
|
|
167
|
-
return {
|
|
168
|
-
html: `<p>${html}</p>`,
|
|
169
|
-
headings,
|
|
170
|
-
};
|
|
171
|
-
};
|
|
172
|
-
}
|
|
1
|
+
/**
|
|
2
|
+
* LoaderContext Factory - 로더에 전달되는 컨텍스트 생성
|
|
3
|
+
*
|
|
4
|
+
* 각 컬렉션의 load() 함수에 필요한 모든 유틸리티 제공
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import type {
|
|
8
|
+
LoaderContext,
|
|
9
|
+
DataStore,
|
|
10
|
+
MetaStore,
|
|
11
|
+
ContentLogger,
|
|
12
|
+
ManduContentConfig,
|
|
13
|
+
ContentWatcher,
|
|
14
|
+
ParseDataOptions,
|
|
15
|
+
RenderedContent,
|
|
16
|
+
ValidationError as ContentValidationError,
|
|
17
|
+
} from "./types";
|
|
18
|
+
import { ValidationError } from "./types";
|
|
19
|
+
import { generateDigest } from "./digest";
|
|
20
|
+
import type { ZodSchema } from "zod";
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* LoaderContext 생성 옵션
|
|
24
|
+
*/
|
|
25
|
+
export interface CreateLoaderContextOptions {
|
|
26
|
+
/** 컬렉션 이름 */
|
|
27
|
+
collection: string;
|
|
28
|
+
/** 데이터 스토어 */
|
|
29
|
+
store: DataStore;
|
|
30
|
+
/** 메타 스토어 */
|
|
31
|
+
meta: MetaStore;
|
|
32
|
+
/** Mandu 설정 */
|
|
33
|
+
config: ManduContentConfig;
|
|
34
|
+
/** Zod 스키마 (검증용) */
|
|
35
|
+
schema?: ZodSchema;
|
|
36
|
+
/** Markdown 렌더러 */
|
|
37
|
+
markdownRenderer?: (content: string) => Promise<RenderedContent>;
|
|
38
|
+
/** 파일 감시자 */
|
|
39
|
+
watcher?: ContentWatcher;
|
|
40
|
+
/** 로거 (기본: console) */
|
|
41
|
+
logger?: ContentLogger;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* 기본 콘텐츠 로거
|
|
46
|
+
*/
|
|
47
|
+
function createDefaultLogger(collection: string): ContentLogger {
|
|
48
|
+
const prefix = `[Content:${collection}]`;
|
|
49
|
+
|
|
50
|
+
return {
|
|
51
|
+
info: (msg) => console.log(`${prefix} ${msg}`),
|
|
52
|
+
warn: (msg) => console.warn(`${prefix} ${msg}`),
|
|
53
|
+
error: (msg) => console.error(`${prefix} ${msg}`),
|
|
54
|
+
debug: (msg) => {
|
|
55
|
+
if (process.env.DEBUG || process.env.NODE_ENV === "development") {
|
|
56
|
+
console.debug(`${prefix} ${msg}`);
|
|
57
|
+
}
|
|
58
|
+
},
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* LoaderContext 생성
|
|
64
|
+
*/
|
|
65
|
+
export function createLoaderContext(options: CreateLoaderContextOptions): LoaderContext {
|
|
66
|
+
const {
|
|
67
|
+
collection,
|
|
68
|
+
store,
|
|
69
|
+
meta,
|
|
70
|
+
config,
|
|
71
|
+
schema,
|
|
72
|
+
markdownRenderer,
|
|
73
|
+
watcher,
|
|
74
|
+
logger = createDefaultLogger(collection),
|
|
75
|
+
} = options;
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* 데이터 파싱 및 검증
|
|
79
|
+
*/
|
|
80
|
+
async function parseData<T>(parseOptions: ParseDataOptions<T>): Promise<T> {
|
|
81
|
+
const { id, data, filePath } = parseOptions;
|
|
82
|
+
|
|
83
|
+
if (!schema) {
|
|
84
|
+
// 스키마 없으면 그대로 반환
|
|
85
|
+
return data as T;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
const result = await schema.safeParseAsync(data);
|
|
89
|
+
|
|
90
|
+
if (!result.success) {
|
|
91
|
+
const errorMessage = result.error.issues
|
|
92
|
+
.map((issue) => `${issue.path.join(".")}: ${issue.message}`)
|
|
93
|
+
.join("; ");
|
|
94
|
+
|
|
95
|
+
throw new ValidationError(
|
|
96
|
+
`Validation failed for entry "${id}"${filePath ? ` (${filePath})` : ""}: ${errorMessage}`,
|
|
97
|
+
result.error,
|
|
98
|
+
collection,
|
|
99
|
+
id
|
|
100
|
+
);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
return result.data as T;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
const context: LoaderContext = {
|
|
107
|
+
collection,
|
|
108
|
+
store,
|
|
109
|
+
meta,
|
|
110
|
+
logger,
|
|
111
|
+
config,
|
|
112
|
+
parseData,
|
|
113
|
+
generateDigest: (data: unknown) => generateDigest(data),
|
|
114
|
+
watcher,
|
|
115
|
+
};
|
|
116
|
+
|
|
117
|
+
// Markdown 렌더러가 있으면 추가
|
|
118
|
+
if (markdownRenderer) {
|
|
119
|
+
context.renderMarkdown = markdownRenderer;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
return context;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* 간단한 Markdown 렌더러 (기본 제공)
|
|
127
|
+
*
|
|
128
|
+
* 실제 프로젝트에서는 remark/rehype 파이프라인으로 교체 권장
|
|
129
|
+
*/
|
|
130
|
+
export function createSimpleMarkdownRenderer(): (content: string) => Promise<RenderedContent> {
|
|
131
|
+
return async (content: string): Promise<RenderedContent> => {
|
|
132
|
+
// 매우 기본적인 Markdown → HTML 변환
|
|
133
|
+
// 실제 사용 시 marked, remark 등으로 교체
|
|
134
|
+
const html = content
|
|
135
|
+
// 헤딩
|
|
136
|
+
.replace(/^### (.*$)/gim, "<h3>$1</h3>")
|
|
137
|
+
.replace(/^## (.*$)/gim, "<h2>$1</h2>")
|
|
138
|
+
.replace(/^# (.*$)/gim, "<h1>$1</h1>")
|
|
139
|
+
// 볼드/이탤릭
|
|
140
|
+
.replace(/\*\*(.+?)\*\*/g, "<strong>$1</strong>")
|
|
141
|
+
.replace(/\*(.+?)\*/g, "<em>$1</em>")
|
|
142
|
+
// 링크
|
|
143
|
+
.replace(/\[([^\]]+)\]\(([^)]+)\)/g, '<a href="$2">$1</a>')
|
|
144
|
+
// 코드 블록
|
|
145
|
+
.replace(/```([^`]+)```/g, "<pre><code>$1</code></pre>")
|
|
146
|
+
.replace(/`([^`]+)`/g, "<code>$1</code>")
|
|
147
|
+
// 줄바꿈
|
|
148
|
+
.replace(/\n\n/g, "</p><p>")
|
|
149
|
+
.replace(/\n/g, "<br>");
|
|
150
|
+
|
|
151
|
+
// 헤딩 추출
|
|
152
|
+
const headings: Array<{ depth: 1 | 2 | 3 | 4 | 5 | 6; slug: string; text: string }> = [];
|
|
153
|
+
const headingRegex = /^(#{1,6})\s+(.+)$/gm;
|
|
154
|
+
let match;
|
|
155
|
+
|
|
156
|
+
while ((match = headingRegex.exec(content)) !== null) {
|
|
157
|
+
const depth = match[1].length as 1 | 2 | 3 | 4 | 5 | 6;
|
|
158
|
+
const text = match[2].trim();
|
|
159
|
+
const slug = text
|
|
160
|
+
.toLowerCase()
|
|
161
|
+
.replace(/[^a-z0-9가-힣]+/g, "-")
|
|
162
|
+
.replace(/^-|-$/g, "");
|
|
163
|
+
|
|
164
|
+
headings.push({ depth, slug, text });
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
return {
|
|
168
|
+
html: `<p>${html}</p>`,
|
|
169
|
+
headings,
|
|
170
|
+
};
|
|
171
|
+
};
|
|
172
|
+
}
|