@limcpf/everything-is-a-markdown 0.4.1 → 0.4.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/LICENSE +21 -0
- package/README.ko.md +128 -0
- package/README.md +84 -253
- package/package.json +2 -1
- package/src/build.ts +70 -39
- package/src/runtime/app.css +15 -0
- package/src/runtime/app.js +17 -10
- package/src/types.ts +4 -2
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 lim
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.ko.md
ADDED
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
# Everything-Is-A-Markdown (EIAM)
|
|
2
|
+
|
|
3
|
+
언어: [English](README.md) | **한국어**
|
|
4
|
+
|
|
5
|
+
Everything-Is-A-Markdown은 로컬 Markdown 볼트를 정적 웹사이트로 빌드해, 폴더/파일 탐색 구조를 유지한 채 공개할 수 있게 해주는 CLI 도구입니다.
|
|
6
|
+
|
|
7
|
+
## 이 앱은 무엇을 하나요
|
|
8
|
+
|
|
9
|
+
- Markdown 볼트에서 정적 문서/블로그 사이트를 생성
|
|
10
|
+
- `publish: true` 문서만 선택적으로 공개
|
|
11
|
+
- 비공개 노트와 공개 콘텐츠 분리
|
|
12
|
+
|
|
13
|
+
## Obsidian 사용자에게 특히 잘 맞습니다
|
|
14
|
+
|
|
15
|
+
- Obsidian에서 평소처럼 작성한 뒤, 공개할 문서만 빌드할 수 있습니다.
|
|
16
|
+
- Obsidian 스타일 위키링크(`[[...]]`)를 지원합니다.
|
|
17
|
+
|
|
18
|
+
## 설치
|
|
19
|
+
|
|
20
|
+
```bash
|
|
21
|
+
bun install
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
## 사용 방법
|
|
25
|
+
|
|
26
|
+
```bash
|
|
27
|
+
bun run blog [build|dev|clean] [options]
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
명령:
|
|
31
|
+
|
|
32
|
+
- `bun run build`: 정적 파일 빌드
|
|
33
|
+
- `bun run dev`: 로컬 개발 서버 실행 (기본 `http://localhost:3000`)
|
|
34
|
+
- `bun run clean`: `dist`와 `.cache` 삭제
|
|
35
|
+
|
|
36
|
+
자주 쓰는 옵션:
|
|
37
|
+
|
|
38
|
+
- `--vault <path>`: Markdown 루트 디렉터리 (기본 `.`)
|
|
39
|
+
- `--out <path>`: 출력 디렉터리 (기본 `dist`)
|
|
40
|
+
- `--exclude <glob>`: 제외 패턴 추가 (반복 가능)
|
|
41
|
+
- `--port <n>`: 개발 서버 포트 (기본 `3000`)
|
|
42
|
+
|
|
43
|
+
예시:
|
|
44
|
+
|
|
45
|
+
```bash
|
|
46
|
+
# 샘플 볼트로 실행
|
|
47
|
+
bun run dev -- --vault ./test-vault --out ./dist
|
|
48
|
+
|
|
49
|
+
# 빌드
|
|
50
|
+
bun run build -- --vault ./test-vault --out ./dist
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
## Markdown Frontmatter
|
|
54
|
+
|
|
55
|
+
공개 여부를 결정하는 핵심 필드는 `publish`입니다.
|
|
56
|
+
|
|
57
|
+
필수:
|
|
58
|
+
|
|
59
|
+
- `publish: true`
|
|
60
|
+
이 값이 `true`인 문서만 빌드 결과에 포함됩니다.
|
|
61
|
+
|
|
62
|
+
선택:
|
|
63
|
+
|
|
64
|
+
- `draft: true`
|
|
65
|
+
`publish: true`여도 문서를 제외합니다.
|
|
66
|
+
- `title: "..."`
|
|
67
|
+
문서 제목. 없으면 파일명을 사용합니다.
|
|
68
|
+
- `prefix: "A-01"`
|
|
69
|
+
탐색기 제목 앞과 본문 메타 줄에 표시할 짧은 코드.
|
|
70
|
+
- `branch: dev`
|
|
71
|
+
브랜치 필터 분류값.
|
|
72
|
+
- `description: "..."`
|
|
73
|
+
요약 설명.
|
|
74
|
+
- `tags: ["tag1", "tag2"]`
|
|
75
|
+
문자열 배열.
|
|
76
|
+
- `date: "YYYY-MM-DD"` 또는 `createdDate: "..."`
|
|
77
|
+
생성일.
|
|
78
|
+
- `updatedDate: "..."` 또는 `modifiedDate: "..."` 또는 `lastModified: "..."`
|
|
79
|
+
수정일.
|
|
80
|
+
|
|
81
|
+
## Frontmatter 예시
|
|
82
|
+
|
|
83
|
+
게시 문서:
|
|
84
|
+
|
|
85
|
+
```md
|
|
86
|
+
---
|
|
87
|
+
publish: true
|
|
88
|
+
prefix: "DEV-01"
|
|
89
|
+
branch: dev
|
|
90
|
+
title: Setup Guide
|
|
91
|
+
date: "2024-09-15"
|
|
92
|
+
updatedDate: "2024-09-20T09:30:00"
|
|
93
|
+
description: How to set up your development environment
|
|
94
|
+
tags: ["tutorial", "setup"]
|
|
95
|
+
---
|
|
96
|
+
|
|
97
|
+
# Setup Guide
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
비공개 문서 (제외됨):
|
|
101
|
+
|
|
102
|
+
```md
|
|
103
|
+
---
|
|
104
|
+
publish: false
|
|
105
|
+
title: Internal Notes
|
|
106
|
+
---
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
초안 문서:
|
|
110
|
+
|
|
111
|
+
```md
|
|
112
|
+
---
|
|
113
|
+
publish: true
|
|
114
|
+
draft: true
|
|
115
|
+
title: Work In Progress
|
|
116
|
+
---
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
## bunx 실행 (선택)
|
|
120
|
+
|
|
121
|
+
```bash
|
|
122
|
+
bunx @limcpf/everything-is-a-markdown build --vault ./vault --out ./dist
|
|
123
|
+
bunx @limcpf/everything-is-a-markdown dev --port 3000
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
## 라이선스
|
|
127
|
+
|
|
128
|
+
MIT. `LICENSE` 파일을 참고하세요.
|
package/README.md
CHANGED
|
@@ -1,297 +1,128 @@
|
|
|
1
1
|
# Everything-Is-A-Markdown (EIAM)
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
-
|
|
11
|
-
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
- (설정 팝업 화면: 메뉴 버튼 위치 토글 + Light/System/Dark 테마 세그먼트가 보이는 화면)
|
|
20
|
-
- (다크 모드 화면: 동일 문서를 라이트/다크로 비교 가능한 화면)
|
|
21
|
-
- (모바일 화면: 하단 Files 버튼으로 사이드바를 열었을 때 오버레이 포함 화면)
|
|
22
|
-
|
|
23
|
-
**프로젝트 개요**
|
|
24
|
-
이 프로젝트는 로컬 Markdown 문서를 파일 트리 형태로 탐색하고, 정적 사이트로 빌드하는 File-System 스타일 블로그 생성기입니다.
|
|
25
|
-
|
|
26
|
-
**주요 기능**
|
|
27
|
-
- 파일 트리 기반 탐색 UI (폴더/파일 구조 유지)
|
|
28
|
-
- 문서별 슬러그 고정 URL 경로 생성 (`/path/to/doc/`)
|
|
29
|
-
- Shiki 기반 코드 하이라이팅
|
|
30
|
-
- Obsidian 스타일 위키링크 `[[...]]` 지원
|
|
31
|
-
- 라이트/시스템/다크 테마 전환 지원 (설정 팝업)
|
|
32
|
-
- NEW 배지, Recent 가상 폴더, 문서 메타 표시
|
|
33
|
-
- 증분 빌드 캐시(`.cache/build-index.json`)로 재빌드 최적화
|
|
34
|
-
|
|
35
|
-
**빠른 시작**
|
|
36
|
-
1. 의존성 설치
|
|
3
|
+
Language: **English** | [한국어](README.ko.md)
|
|
4
|
+
|
|
5
|
+
Everything-Is-A-Markdown is a CLI tool that turns a local Markdown vault into a static website while keeping the folder/file navigation experience.
|
|
6
|
+
|
|
7
|
+
## What This App Is For
|
|
8
|
+
|
|
9
|
+
- Build a static docs/blog site from your Markdown vault
|
|
10
|
+
- Publish only documents with `publish: true`
|
|
11
|
+
- Keep private notes and public content separate
|
|
12
|
+
|
|
13
|
+
## Works Great with Obsidian
|
|
14
|
+
|
|
15
|
+
- You can keep writing in Obsidian and publish selected notes.
|
|
16
|
+
- Obsidian-style wikilinks (`[[...]]`) are supported.
|
|
17
|
+
|
|
18
|
+
## Install
|
|
37
19
|
|
|
38
20
|
```bash
|
|
39
21
|
bun install
|
|
40
22
|
```
|
|
41
23
|
|
|
42
|
-
|
|
24
|
+
## Usage
|
|
43
25
|
|
|
44
26
|
```bash
|
|
45
|
-
bun run dev
|
|
27
|
+
bun run blog [build|dev|clean] [options]
|
|
46
28
|
```
|
|
47
29
|
|
|
48
|
-
|
|
30
|
+
Commands:
|
|
49
31
|
|
|
50
|
-
|
|
51
|
-
|
|
32
|
+
- `bun run build`: Build static files
|
|
33
|
+
- `bun run dev`: Run local dev server (default `http://localhost:3000`)
|
|
34
|
+
- `bun run clean`: Remove `dist` and `.cache`
|
|
52
35
|
|
|
53
|
-
|
|
54
|
-
bun run blog [build|dev|clean] [options]
|
|
55
|
-
```
|
|
36
|
+
Common options:
|
|
56
37
|
|
|
57
|
-
|
|
58
|
-
-
|
|
59
|
-
-
|
|
60
|
-
-
|
|
61
|
-
|
|
62
|
-
옵션
|
|
63
|
-
- `--vault <path>`: 마크다운 루트 디렉터리 (기본: `.`)
|
|
64
|
-
- `--out <path>`: 출력 디렉터리 (기본: `dist`)
|
|
65
|
-
- `--exclude <glob>`: 제외 패턴 (반복 가능, 기본 포함: `.obsidian/**`)
|
|
66
|
-
- `--new-within-days <n>`: NEW 배지 기준 일수 (기본: `7`)
|
|
67
|
-
- `--recent-limit <n>`: Recent 가상 폴더 노출 개수 (기본: `5`)
|
|
68
|
-
- `--menu-config <path>`: 상단 고정 메뉴를 JSON 파일로 임시 덮어쓰기(선택)
|
|
69
|
-
- `--port <n>`: dev 서버 포트 (기본: `3000`)
|
|
70
|
-
|
|
71
|
-
**릴리즈 단일파일 배포 (GitHub Actions)**
|
|
72
|
-
- 워크플로우 파일: `.github/workflows/release-single-file.yml`
|
|
73
|
-
- 동작: `bun run build` 결과인 `dist`를 단일 `.tar.gz` 파일로 묶어 Release asset으로 업로드
|
|
74
|
-
- 자동 실행: `v*` 태그 푸시 시 실행 (예: `v0.1.0`)
|
|
75
|
-
- 수동 실행: `Actions > Release Single File > Run workflow`에서 `tag`(필수), `asset_name`(선택) 입력
|
|
76
|
-
|
|
77
|
-
릴리즈 태그 생성 예시
|
|
78
|
-
```bash
|
|
79
|
-
git tag v0.1.0
|
|
80
|
-
git push origin v0.1.0
|
|
81
|
-
```
|
|
38
|
+
- `--vault <path>`: Markdown root directory (default `.`)
|
|
39
|
+
- `--out <path>`: Output directory (default `dist`)
|
|
40
|
+
- `--exclude <glob>`: Add exclude pattern (repeatable)
|
|
41
|
+
- `--port <n>`: Dev server port (default `3000`)
|
|
82
42
|
|
|
83
|
-
|
|
84
|
-
- 워크플로우 파일: `.github/workflows/publish-bunx.yml`
|
|
85
|
-
- 동작: `v*` 태그 푸시 시 npm에 패키지 publish (`bun publish`)
|
|
86
|
-
- 전제: GitHub 저장소 `Settings > Secrets and variables > Actions`에 `NPM_TOKEN` 추가
|
|
87
|
-
- 검증: 태그 `vX.Y.Z`와 `package.json`의 `version`이 다르면 배포 실패
|
|
43
|
+
Examples:
|
|
88
44
|
|
|
89
|
-
사용자 실행 예시
|
|
90
45
|
```bash
|
|
91
|
-
|
|
92
|
-
|
|
46
|
+
# Run with the sample vault
|
|
47
|
+
bun run dev -- --vault ./test-vault --out ./dist
|
|
93
48
|
|
|
94
|
-
#
|
|
95
|
-
|
|
49
|
+
# Build
|
|
50
|
+
bun run build -- --vault ./test-vault --out ./dist
|
|
96
51
|
```
|
|
97
52
|
|
|
98
|
-
|
|
99
|
-
```bash
|
|
100
|
-
# 다른 볼트 경로로 빌드
|
|
101
|
-
bun run build -- --vault ../vault --out ./dist
|
|
53
|
+
## Markdown Frontmatter
|
|
102
54
|
|
|
103
|
-
|
|
104
|
-
bun run dev -- --port 4000
|
|
55
|
+
The key field for publishing is `publish`.
|
|
105
56
|
|
|
106
|
-
|
|
107
|
-
bun run build -- --exclude "private/**" --exclude "**/drafts/**"
|
|
57
|
+
Required:
|
|
108
58
|
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
```
|
|
59
|
+
- `publish: true`
|
|
60
|
+
Only documents with this value are included in build output.
|
|
112
61
|
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
twitterCreator: "@author_handle", // optional
|
|
132
|
-
defaultSocialImage: "/assets/social/default.png", // optional, absolute URL or /-relative
|
|
133
|
-
defaultOgImage: "/assets/social/og.png", // optional, overrides defaultSocialImage for og:image
|
|
134
|
-
defaultTwitterImage: "/assets/social/twitter.png", // optional, overrides defaultSocialImage for twitter:image
|
|
135
|
-
},
|
|
136
|
-
ui: {
|
|
137
|
-
newWithinDays: 7,
|
|
138
|
-
recentLimit: 5,
|
|
139
|
-
},
|
|
140
|
-
pinnedMenu: {
|
|
141
|
-
label: "NOTICE",
|
|
142
|
-
sourceDir: "Log/(Blog)/Notice",
|
|
143
|
-
},
|
|
144
|
-
markdown: {
|
|
145
|
-
wikilinks: true,
|
|
146
|
-
images: "omit-local", // "keep" | "omit-local"
|
|
147
|
-
gfm: true,
|
|
148
|
-
highlight: {
|
|
149
|
-
theme: "github-dark",
|
|
150
|
-
},
|
|
151
|
-
},
|
|
152
|
-
};
|
|
153
|
-
```
|
|
62
|
+
Optional:
|
|
63
|
+
|
|
64
|
+
- `draft: true`
|
|
65
|
+
Excludes the document even if `publish: true`.
|
|
66
|
+
- `title: "..."`
|
|
67
|
+
Display title. If missing, file name is used.
|
|
68
|
+
- `prefix: "A-01"`
|
|
69
|
+
Short code shown before the title in the explorer and meta line.
|
|
70
|
+
- `branch: dev`
|
|
71
|
+
Branch filter label.
|
|
72
|
+
- `description: "..."`
|
|
73
|
+
Short summary.
|
|
74
|
+
- `tags: ["tag1", "tag2"]`
|
|
75
|
+
String array.
|
|
76
|
+
- `date: "YYYY-MM-DD"` or `createdDate: "..."`
|
|
77
|
+
Created date.
|
|
78
|
+
- `updatedDate: "..."` or `modifiedDate: "..."` or `lastModified: "..."`
|
|
79
|
+
Updated date.
|
|
154
80
|
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
- `--menu-config`를 주면 `blog.config.*`의 `pinnedMenu`를 해당 실행에서만 덮어씁니다.
|
|
159
|
-
|
|
160
|
-
SEO 설정 메모
|
|
161
|
-
- `seo.siteUrl`: 필수. 절대 origin만 허용됩니다 (예: `https://example.com`, path/query/hash 불가).
|
|
162
|
-
- `seo.pathBase`: 선택. `/blog` 같은 배포 base path를 canonical/OG/sitemap URL에 함께 붙입니다.
|
|
163
|
-
- `seo.siteName`: 선택. `og:site_name` 및 루트 JSON-LD(WebSite.name)에 반영됩니다.
|
|
164
|
-
- `seo.defaultTitle`: 선택. 문서 제목이 없을 때 fallback `<title>`로 사용됩니다.
|
|
165
|
-
- `seo.defaultDescription`: 선택. 문서 설명이 없을 때 fallback description/OG/Twitter 설명으로 사용됩니다.
|
|
166
|
-
- `seo.locale`: 선택. `og:locale` 값으로 출력됩니다 (예: `ko_KR`).
|
|
167
|
-
- `seo.twitterCard`: 선택. `summary` 또는 `summary_large_image`.
|
|
168
|
-
- `seo.twitterSite`, `seo.twitterCreator`: 선택. 각각 `twitter:site`, `twitter:creator`로 출력됩니다.
|
|
169
|
-
- `seo.defaultSocialImage`: 선택. OG/Twitter 공통 기본 이미지.
|
|
170
|
-
- `seo.defaultOgImage`, `seo.defaultTwitterImage`: 선택. 채널별 이미지 우선값(없으면 `defaultSocialImage` 사용).
|
|
171
|
-
- `seo.siteUrl`이 없으면 `robots.txt`, `sitemap.xml`은 생성되지 않습니다.
|
|
172
|
-
|
|
173
|
-
**콘텐츠 작성 규칙**
|
|
174
|
-
- `publish: true`인 문서만 출력됩니다.
|
|
175
|
-
- `draft: true`면 출력에서 제외됩니다.
|
|
176
|
-
- `branch`를 지정하면 해당 브랜치 필터에서만 노출됩니다.
|
|
177
|
-
- `branch`가 없으면 "브랜치 분류 없음"으로 간주되어 기본 브랜치에서만 노출됩니다.
|
|
178
|
-
- 기본 브랜치 뷰는 `dev + 분류 없음`이며, 다른 브랜치는 해당 브랜치 글만 노출됩니다.
|
|
179
|
-
- `title`이 없으면 파일명에서 자동 생성됩니다.
|
|
180
|
-
- 생성일은 `date` 또는 `createdDate`를 사용합니다.
|
|
181
|
-
- 수정일은 `updatedDate`(`modifiedDate`/`lastModified`도 허용)를 사용합니다.
|
|
182
|
-
- 생성/수정일은 frontmatter에 값이 있을 때만 본문 메타에 표시됩니다.
|
|
183
|
-
- `tags`는 문자열 배열로 작성합니다.
|
|
81
|
+
## Frontmatter Examples
|
|
82
|
+
|
|
83
|
+
Published document:
|
|
184
84
|
|
|
185
85
|
```md
|
|
186
86
|
---
|
|
187
87
|
publish: true
|
|
88
|
+
prefix: "DEV-01"
|
|
188
89
|
branch: dev
|
|
189
|
-
title:
|
|
190
|
-
date: "2024-
|
|
191
|
-
updatedDate: "2024-
|
|
192
|
-
description:
|
|
193
|
-
tags: ["
|
|
90
|
+
title: Setup Guide
|
|
91
|
+
date: "2024-09-15"
|
|
92
|
+
updatedDate: "2024-09-20T09:30:00"
|
|
93
|
+
description: How to set up your development environment
|
|
94
|
+
tags: ["tutorial", "setup"]
|
|
194
95
|
---
|
|
195
96
|
|
|
196
|
-
#
|
|
197
|
-
본문 내용...
|
|
97
|
+
# Setup Guide
|
|
198
98
|
```
|
|
199
99
|
|
|
200
|
-
|
|
201
|
-
- `dist/manifest.json`: 트리, 문서 메타, 라우팅 정보
|
|
202
|
-
- `dist/content/*.html`: 각 문서 본문 HTML
|
|
203
|
-
- `dist/_app/index.html`: 앱 셸
|
|
204
|
-
- `dist/<문서 slug 경로>/index.html`: 각 문서 경로
|
|
205
|
-
- `dist/assets/app.js`, `dist/assets/app.css`: 런타임 UI
|
|
206
|
-
|
|
207
|
-
**SEO/A11y 생성 결과**
|
|
208
|
-
- 라우트별 HTML(`index.html`, `about/index.html`, `posts/2024/setup-guide/index.html` 등)에 route-specific `<title>`, description, canonical, Open Graph/Twitter meta가 주입됩니다.
|
|
209
|
-
- 각 라우트에 JSON-LD(`application/ld+json`)가 포함됩니다.
|
|
210
|
-
- `seo.siteUrl` 설정 시에만 `robots.txt`, `sitemap.xml`이 생성됩니다.
|
|
211
|
-
- `robots.txt`는 `Sitemap: <canonical sitemap url>`을 포함합니다.
|
|
212
|
-
- `sitemap.xml`은 `/` + 게시된 문서 라우트들을 canonical URL로 직렬화합니다 (`seo.pathBase` 반영).
|
|
213
|
-
- 접근성 기본값:
|
|
214
|
-
- skip link: `본문으로 건너뛰기` (`href="#viewer-panel"`)
|
|
215
|
-
- live region: `#a11y-status` (`aria-live="polite"`, `aria-atomic="true"`)
|
|
216
|
-
- reduced motion: `prefers-reduced-motion: reduce`에서 `.status-dot` pulse 애니메이션 비활성화
|
|
100
|
+
Private document (excluded):
|
|
217
101
|
|
|
218
|
-
|
|
102
|
+
```md
|
|
103
|
+
---
|
|
104
|
+
publish: false
|
|
105
|
+
title: Internal Notes
|
|
106
|
+
---
|
|
107
|
+
```
|
|
219
108
|
|
|
220
|
-
|
|
109
|
+
Draft document:
|
|
221
110
|
|
|
222
|
-
```
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
# SEO OFF (blog.config.* 없음)
|
|
229
|
-
(cd "$TMP_NO" && bun "$REPO/src/cli.ts" build --vault "$REPO/test-vault" --out "$TMP_OUT/no-seo-out")
|
|
230
|
-
|
|
231
|
-
# SEO ON (temp blog.config.mjs 사용)
|
|
232
|
-
cat > "$TMP_YES/blog.config.mjs" <<'EOF'
|
|
233
|
-
export default {
|
|
234
|
-
seo: {
|
|
235
|
-
siteUrl: "https://docs.example.com",
|
|
236
|
-
pathBase: "/kb"
|
|
237
|
-
}
|
|
238
|
-
};
|
|
239
|
-
EOF
|
|
240
|
-
(cd "$TMP_YES" && bun "$REPO/src/cli.ts" build --vault "$REPO/test-vault" --out "$TMP_OUT/with-seo-out")
|
|
111
|
+
```md
|
|
112
|
+
---
|
|
113
|
+
publish: true
|
|
114
|
+
draft: true
|
|
115
|
+
title: Work In Progress
|
|
116
|
+
---
|
|
241
117
|
```
|
|
242
118
|
|
|
243
|
-
|
|
119
|
+
## bunx (Optional)
|
|
244
120
|
|
|
245
121
|
```bash
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
const noSeoOut = `${out}/no-seo-out`;
|
|
249
|
-
const withSeoOut = `${out}/with-seo-out`;
|
|
250
|
-
const pages = [
|
|
251
|
-
["index", `${withSeoOut}/index.html`, "https://docs.example.com/kb/"],
|
|
252
|
-
["about", `${withSeoOut}/about/index.html`, "https://docs.example.com/kb/about/"],
|
|
253
|
-
["post", `${withSeoOut}/posts/2024/setup-guide/index.html`, "https://docs.example.com/kb/posts/2024/setup-guide/"],
|
|
254
|
-
];
|
|
255
|
-
for (const [name, file, canonical] of pages) {
|
|
256
|
-
const html = readFileSync(file, "utf8");
|
|
257
|
-
console.log(`${name}: canonical=${html.includes(`<link rel="canonical" href="${canonical}" />`)} og:url=${html.includes(`<meta property="og:url" content="${canonical}" />`)} jsonld=${/<script type="application\/ld\+json">[\s\S]*<\/script>/.test(html)}`);
|
|
258
|
-
}
|
|
259
|
-
console.log(`no-seo robots=${existsSync(`${noSeoOut}/robots.txt`)}`);
|
|
260
|
-
console.log(`no-seo sitemap=${existsSync(`${noSeoOut}/sitemap.xml`)}`);
|
|
261
|
-
console.log(`with-seo robots=${existsSync(`${withSeoOut}/robots.txt`)}`);
|
|
262
|
-
console.log(`with-seo sitemap=${existsSync(`${withSeoOut}/sitemap.xml`)}`);
|
|
263
|
-
' OUT="$TMP_OUT"
|
|
122
|
+
bunx @limcpf/everything-is-a-markdown build --vault ./vault --out ./dist
|
|
123
|
+
bunx @limcpf/everything-is-a-markdown dev --port 3000
|
|
264
124
|
```
|
|
265
125
|
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
```text
|
|
269
|
-
static server: (cd "$TMP_OUT/with-seo-out" && python3 -m http.server 4173 --bind 127.0.0.1)
|
|
270
|
-
|
|
271
|
-
browser_navigate: http://127.0.0.1:4173/
|
|
272
|
-
|
|
273
|
-
browser_run_code:
|
|
274
|
-
async (page) => {
|
|
275
|
-
await page.keyboard.press('Tab');
|
|
276
|
-
const skip = await page.evaluate(() => document.activeElement?.className);
|
|
277
|
-
await page.keyboard.press('Enter');
|
|
278
|
-
const focus = await page.evaluate(() => ({ hash: window.location.hash, id: document.activeElement?.id }));
|
|
279
|
-
await page.emulateMedia({ reducedMotion: 'no-preference' });
|
|
280
|
-
const normal = await page.evaluate(() => getComputedStyle(document.querySelector('.status-dot')).animationName);
|
|
281
|
-
await page.emulateMedia({ reducedMotion: 'reduce' });
|
|
282
|
-
const reduced = await page.evaluate(() => getComputedStyle(document.querySelector('.status-dot')).animationName);
|
|
283
|
-
return { skip, focus, normal, reduced };
|
|
284
|
-
}
|
|
285
|
-
```
|
|
126
|
+
## License
|
|
286
127
|
|
|
287
|
-
|
|
288
|
-
- `LICENSE`: 라이선스 명시
|
|
289
|
-
- `CHANGELOG.md`: 버전별 변경 이력
|
|
290
|
-
- `CONTRIBUTING.md`: 개발/기여 가이드와 브랜치 규칙
|
|
291
|
-
- `CODE_OF_CONDUCT.md`: 커뮤니티 행동 강령
|
|
292
|
-
- `SECURITY.md`: 보안 취약점 신고 절차
|
|
293
|
-
- `docs/CONFIG.md`: 설정 옵션 상세 레퍼런스
|
|
294
|
-
- `docs/ARCHITECTURE.md`: 빌드 파이프라인과 런타임 구조 설명
|
|
295
|
-
- `docs/DEPLOYMENT.md`: 정적 호스팅 배포 가이드
|
|
296
|
-
- `docs/TROUBLESHOOTING.md`: 자주 발생하는 문제와 해결 방법
|
|
297
|
-
- `docs/FAQ.md`: 사용자 FAQ
|
|
128
|
+
MIT. See `LICENSE`.
|
package/package.json
CHANGED
package/src/build.ts
CHANGED
|
@@ -20,7 +20,7 @@ import {
|
|
|
20
20
|
toRoute,
|
|
21
21
|
} from "./utils";
|
|
22
22
|
|
|
23
|
-
const CACHE_VERSION =
|
|
23
|
+
const CACHE_VERSION = 3;
|
|
24
24
|
const CACHE_DIR_NAME = ".cache";
|
|
25
25
|
const CACHE_FILE_NAME = "build-index.json";
|
|
26
26
|
const DEFAULT_BRANCH = "dev";
|
|
@@ -121,6 +121,7 @@ function normalizeCachedSourceEntry(value: unknown): CachedSourceEntry | null {
|
|
|
121
121
|
const publish = value.publish === true;
|
|
122
122
|
const draft = value.draft === true;
|
|
123
123
|
const title = typeof value.title === "string" && value.title.trim().length > 0 ? value.title.trim() : undefined;
|
|
124
|
+
const prefix = typeof value.prefix === "string" && value.prefix.trim().length > 0 ? value.prefix.trim() : undefined;
|
|
124
125
|
const date = typeof value.date === "string" && value.date.trim().length > 0 ? value.date.trim() : undefined;
|
|
125
126
|
const updatedDate =
|
|
126
127
|
typeof value.updatedDate === "string" && value.updatedDate.trim().length > 0 ? value.updatedDate.trim() : undefined;
|
|
@@ -142,6 +143,7 @@ function normalizeCachedSourceEntry(value: unknown): CachedSourceEntry | null {
|
|
|
142
143
|
publish,
|
|
143
144
|
draft,
|
|
144
145
|
title,
|
|
146
|
+
prefix,
|
|
145
147
|
date,
|
|
146
148
|
updatedDate,
|
|
147
149
|
description,
|
|
@@ -329,6 +331,25 @@ function pickDocUpdatedDate(frontmatter: Record<string, unknown>, raw: string):
|
|
|
329
331
|
return pickFrontmatterDate(frontmatter, raw, ["updatedDate", "modifiedDate", "lastModified"]);
|
|
330
332
|
}
|
|
331
333
|
|
|
334
|
+
function pickDocPrefix(frontmatter: Record<string, unknown>, raw: string): string | undefined {
|
|
335
|
+
const literal = extractFrontmatterScalar(raw, "prefix");
|
|
336
|
+
if (literal) {
|
|
337
|
+
return literal;
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
const value = frontmatter.prefix;
|
|
341
|
+
if (typeof value === "string") {
|
|
342
|
+
const trimmed = value.trim();
|
|
343
|
+
return trimmed.length > 0 ? trimmed : undefined;
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
if (typeof value === "number" && Number.isFinite(value)) {
|
|
347
|
+
return String(value);
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
return undefined;
|
|
351
|
+
}
|
|
352
|
+
|
|
332
353
|
function appendRouteSuffix(route: string, suffix: string): string {
|
|
333
354
|
const clean = route.replace(/^\/+/, "").replace(/\/+$/, "");
|
|
334
355
|
if (!clean) {
|
|
@@ -431,6 +452,7 @@ function toCachedSourceEntry(raw: string, parsed: matter.GrayMatterFile<string>)
|
|
|
431
452
|
publish: parsed.data.publish === true,
|
|
432
453
|
draft: parsed.data.draft === true,
|
|
433
454
|
title: typeof parsed.data.title === "string" && parsed.data.title.trim().length > 0 ? parsed.data.title.trim() : undefined,
|
|
455
|
+
prefix: pickDocPrefix(parsed.data as Record<string, unknown>, raw),
|
|
434
456
|
date: pickDocDate(parsed.data as Record<string, unknown>, raw),
|
|
435
457
|
updatedDate: pickDocUpdatedDate(parsed.data as Record<string, unknown>, raw),
|
|
436
458
|
description: typeof parsed.data.description === "string" ? parsed.data.description.trim() || undefined : undefined,
|
|
@@ -460,6 +482,7 @@ function toDocRecord(
|
|
|
460
482
|
contentUrl: `/content/${toContentFileName(id)}`,
|
|
461
483
|
fileName,
|
|
462
484
|
title: entry.title ?? makeTitleFromFileName(fileName),
|
|
485
|
+
prefix: entry.prefix,
|
|
463
486
|
date: entry.date,
|
|
464
487
|
updatedDate: entry.updatedDate,
|
|
465
488
|
description: entry.description,
|
|
@@ -589,9 +612,9 @@ function fileNodeFromDoc(doc: DocRecord): FileNode {
|
|
|
589
612
|
name: doc.fileName,
|
|
590
613
|
id: doc.id,
|
|
591
614
|
title: doc.title,
|
|
615
|
+
prefix: doc.prefix,
|
|
592
616
|
route: doc.route,
|
|
593
617
|
contentUrl: doc.contentUrl,
|
|
594
|
-
mtime: doc.mtimeMs,
|
|
595
618
|
isNew: doc.isNew,
|
|
596
619
|
tags: doc.tags,
|
|
597
620
|
description: doc.description,
|
|
@@ -633,7 +656,32 @@ function isNewByFrontmatterDate(date: string | undefined, newThreshold: number):
|
|
|
633
656
|
}
|
|
634
657
|
|
|
635
658
|
function getRecentSortEpochMs(doc: DocRecord): number {
|
|
636
|
-
return parseDateToEpochMs(doc.
|
|
659
|
+
return parseDateToEpochMs(doc.updatedDate) ?? parseDateToEpochMs(doc.date);
|
|
660
|
+
}
|
|
661
|
+
|
|
662
|
+
function compareByRecentDateThenPath(left: DocRecord, right: DocRecord): number {
|
|
663
|
+
const leftEpoch = getRecentSortEpochMs(left);
|
|
664
|
+
const rightEpoch = getRecentSortEpochMs(right);
|
|
665
|
+
|
|
666
|
+
if (leftEpoch != null && rightEpoch != null) {
|
|
667
|
+
const byDate = rightEpoch - leftEpoch;
|
|
668
|
+
if (byDate !== 0) {
|
|
669
|
+
return byDate;
|
|
670
|
+
}
|
|
671
|
+
} else if (leftEpoch != null && rightEpoch == null) {
|
|
672
|
+
return -1;
|
|
673
|
+
} else if (leftEpoch == null && rightEpoch != null) {
|
|
674
|
+
return 1;
|
|
675
|
+
}
|
|
676
|
+
|
|
677
|
+
return left.relNoExt.localeCompare(right.relNoExt, "ko-KR");
|
|
678
|
+
}
|
|
679
|
+
|
|
680
|
+
function pickHomeDoc(docs: DocRecord[]): DocRecord | null {
|
|
681
|
+
const inDefaultBranch = docs.filter((doc) => doc.branch == null || doc.branch === DEFAULT_BRANCH);
|
|
682
|
+
const candidates = inDefaultBranch.length > 0 ? inDefaultBranch : docs;
|
|
683
|
+
const byRoute = candidates.find((doc) => doc.route === "/index/");
|
|
684
|
+
return byRoute ?? candidates[0] ?? null;
|
|
637
685
|
}
|
|
638
686
|
|
|
639
687
|
function buildPinnedMenuFolder(docs: DocRecord[], options: BuildOptions): FolderNode | null {
|
|
@@ -697,19 +745,7 @@ function buildTree(docs: DocRecord[], options: BuildOptions): TreeNode[] {
|
|
|
697
745
|
sortTree(root.children);
|
|
698
746
|
|
|
699
747
|
const recentChildren = [...docs]
|
|
700
|
-
.sort(
|
|
701
|
-
const byDate = getRecentSortEpochMs(right) - getRecentSortEpochMs(left);
|
|
702
|
-
if (byDate !== 0) {
|
|
703
|
-
return byDate;
|
|
704
|
-
}
|
|
705
|
-
|
|
706
|
-
const byMtime = right.mtimeMs - left.mtimeMs;
|
|
707
|
-
if (byMtime !== 0) {
|
|
708
|
-
return byMtime;
|
|
709
|
-
}
|
|
710
|
-
|
|
711
|
-
return left.relNoExt.localeCompare(right.relNoExt, "ko-KR");
|
|
712
|
-
})
|
|
748
|
+
.sort(compareByRecentDateThenPath)
|
|
713
749
|
.slice(0, options.recentLimit)
|
|
714
750
|
.map((doc) => fileNodeFromDoc(doc));
|
|
715
751
|
|
|
@@ -735,21 +771,19 @@ function buildManifest(docs: DocRecord[], tree: TreeNode[], options: BuildOption
|
|
|
735
771
|
routeMap[doc.route] = doc.id;
|
|
736
772
|
}
|
|
737
773
|
|
|
738
|
-
const docsForManifest =
|
|
739
|
-
.
|
|
740
|
-
.
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
branch: doc.branch,
|
|
752
|
-
}));
|
|
774
|
+
const docsForManifest = docs.map((doc) => ({
|
|
775
|
+
id: doc.id,
|
|
776
|
+
route: doc.route,
|
|
777
|
+
title: doc.title,
|
|
778
|
+
prefix: doc.prefix,
|
|
779
|
+
date: doc.date,
|
|
780
|
+
updatedDate: doc.updatedDate,
|
|
781
|
+
tags: doc.tags,
|
|
782
|
+
description: doc.description,
|
|
783
|
+
isNew: doc.isNew,
|
|
784
|
+
contentUrl: doc.contentUrl,
|
|
785
|
+
branch: doc.branch,
|
|
786
|
+
}));
|
|
753
787
|
|
|
754
788
|
const branchSet = new Set<string>([DEFAULT_BRANCH]);
|
|
755
789
|
for (const doc of docs) {
|
|
@@ -984,6 +1018,10 @@ function normalizeTags(tags: string[]): string[] {
|
|
|
984
1018
|
function renderInitialMeta(doc: DocRecord): string {
|
|
985
1019
|
const items: string[] = [];
|
|
986
1020
|
|
|
1021
|
+
if (doc.prefix) {
|
|
1022
|
+
items.push(`<span class="meta-item meta-prefix">${escapeHtmlAttribute(doc.prefix)}</span>`);
|
|
1023
|
+
}
|
|
1024
|
+
|
|
987
1025
|
const createdAt = formatMetaDateTime(doc.date);
|
|
988
1026
|
if (createdAt) {
|
|
989
1027
|
items.push(
|
|
@@ -991,13 +1029,6 @@ function renderInitialMeta(doc: DocRecord): string {
|
|
|
991
1029
|
);
|
|
992
1030
|
}
|
|
993
1031
|
|
|
994
|
-
const updatedAt = formatMetaDateTime(doc.updatedDate);
|
|
995
|
-
if (updatedAt) {
|
|
996
|
-
items.push(
|
|
997
|
-
`<span class="meta-item"><span class="material-symbols-outlined">schedule</span>updated ${escapeHtmlAttribute(updatedAt)}</span>`,
|
|
998
|
-
);
|
|
999
|
-
}
|
|
1000
|
-
|
|
1001
1032
|
const tags = normalizeTags(doc.tags);
|
|
1002
1033
|
if (tags.length > 0) {
|
|
1003
1034
|
const tagsStr = tags.map((tag) => `#${escapeHtmlAttribute(tag)}`).join(" ");
|
|
@@ -1047,7 +1078,7 @@ async function writeShellPages(
|
|
|
1047
1078
|
runtimeAssets: RuntimeAssets,
|
|
1048
1079
|
contentByDocId: Map<string, string>,
|
|
1049
1080
|
): Promise<void> {
|
|
1050
|
-
const indexDoc = docs
|
|
1081
|
+
const indexDoc = pickHomeDoc(docs);
|
|
1051
1082
|
const indexOutputPath = "index.html";
|
|
1052
1083
|
const indexInitialView = indexDoc ? buildInitialView(indexDoc, docs, contentByDocId.get(indexDoc.id) ?? "") : null;
|
|
1053
1084
|
const shell = renderAppShellHtml(
|
package/src/runtime/app.css
CHANGED
|
@@ -478,6 +478,14 @@ a:hover {
|
|
|
478
478
|
color: var(--latte-overlay0);
|
|
479
479
|
}
|
|
480
480
|
|
|
481
|
+
.tree-prefix {
|
|
482
|
+
flex: 0 0 auto;
|
|
483
|
+
font-size: 0.68rem;
|
|
484
|
+
font-weight: 600;
|
|
485
|
+
letter-spacing: 0.02em;
|
|
486
|
+
color: var(--latte-overlay1);
|
|
487
|
+
}
|
|
488
|
+
|
|
481
489
|
.tree-label {
|
|
482
490
|
flex: 1;
|
|
483
491
|
overflow: hidden;
|
|
@@ -843,6 +851,13 @@ body.mobile-toggle-left .mobile-menu-toggle {
|
|
|
843
851
|
color: var(--latte-overlay0);
|
|
844
852
|
}
|
|
845
853
|
|
|
854
|
+
.meta-prefix {
|
|
855
|
+
font-size: 0.74rem;
|
|
856
|
+
font-weight: 700;
|
|
857
|
+
letter-spacing: 0.03em;
|
|
858
|
+
color: var(--latte-overlay1);
|
|
859
|
+
}
|
|
860
|
+
|
|
846
861
|
.meta-tags {
|
|
847
862
|
color: var(--latte-lavender);
|
|
848
863
|
}
|
package/src/runtime/app.js
CHANGED
|
@@ -217,6 +217,13 @@ function buildBranchView(manifest, branch, defaultBranch) {
|
|
|
217
217
|
};
|
|
218
218
|
}
|
|
219
219
|
|
|
220
|
+
function pickHomeRoute(view) {
|
|
221
|
+
if (view.routeMap["/index/"]) {
|
|
222
|
+
return "/index/";
|
|
223
|
+
}
|
|
224
|
+
return view.docs[0]?.route || "/";
|
|
225
|
+
}
|
|
226
|
+
|
|
220
227
|
function loadExpandedSet() {
|
|
221
228
|
try {
|
|
222
229
|
const raw = localStorage.getItem(EXPANDED_KEY);
|
|
@@ -614,8 +621,11 @@ function createFileNode(node, fileRowsById, depth = 0) {
|
|
|
614
621
|
row.dataset.fileId = node.id;
|
|
615
622
|
row.style.setProperty("--tree-depth", String(depth));
|
|
616
623
|
|
|
624
|
+
const prefix = typeof node.prefix === "string" ? node.prefix.trim() : "";
|
|
625
|
+
const prefixHtml = prefix ? `<span class="tree-prefix">${escapeHtmlAttr(prefix)}</span>` : "";
|
|
626
|
+
const label = escapeHtmlAttr(node.title || node.name);
|
|
617
627
|
const newBadge = node.isNew ? `<span class="badge-new">NEW</span>` : "";
|
|
618
|
-
row.innerHTML = `<span class="material-symbols-outlined">article</span
|
|
628
|
+
row.innerHTML = `<span class="material-symbols-outlined">article</span>${prefixHtml}<span class="tree-label">${label}</span>${newBadge}`;
|
|
619
629
|
fileRowsById.set(node.id, row);
|
|
620
630
|
|
|
621
631
|
return row;
|
|
@@ -684,6 +694,10 @@ function renderBreadcrumb(route) {
|
|
|
684
694
|
function renderMeta(doc) {
|
|
685
695
|
const items = [];
|
|
686
696
|
|
|
697
|
+
if (typeof doc.prefix === "string" && doc.prefix.trim().length > 0) {
|
|
698
|
+
items.push(`<span class="meta-item meta-prefix">${escapeHtmlAttr(doc.prefix)}</span>`);
|
|
699
|
+
}
|
|
700
|
+
|
|
687
701
|
const createdAt = formatMetaDateTime(doc.date);
|
|
688
702
|
if (createdAt) {
|
|
689
703
|
items.push(
|
|
@@ -691,13 +705,6 @@ function renderMeta(doc) {
|
|
|
691
705
|
);
|
|
692
706
|
}
|
|
693
707
|
|
|
694
|
-
const updatedAt = formatMetaDateTime(doc.updatedDate);
|
|
695
|
-
if (updatedAt) {
|
|
696
|
-
items.push(
|
|
697
|
-
`<span class="meta-item"><span class="material-symbols-outlined">schedule</span>updated ${escapeHtmlAttr(updatedAt)}</span>`,
|
|
698
|
-
);
|
|
699
|
-
}
|
|
700
|
-
|
|
701
708
|
const tags = normalizeTags(doc.tags);
|
|
702
709
|
if (tags.length > 0) {
|
|
703
710
|
const tagsStr = tags.map((tag) => `#${escapeHtmlAttr(tag)}`).join(" ");
|
|
@@ -1452,7 +1459,7 @@ async function start() {
|
|
|
1452
1459
|
return;
|
|
1453
1460
|
}
|
|
1454
1461
|
|
|
1455
|
-
const fallbackRoute = view
|
|
1462
|
+
const fallbackRoute = pickHomeRoute(view);
|
|
1456
1463
|
await state.navigate(fallbackRoute, true);
|
|
1457
1464
|
};
|
|
1458
1465
|
|
|
@@ -1461,7 +1468,7 @@ async function start() {
|
|
|
1461
1468
|
renderTree(state);
|
|
1462
1469
|
|
|
1463
1470
|
const currentRoute = resolveRouteFromLocation(view.routeMap);
|
|
1464
|
-
const initialRoute = currentRoute === "/" ? view
|
|
1471
|
+
const initialRoute = currentRoute === "/" ? pickHomeRoute(view) : currentRoute;
|
|
1465
1472
|
handleLayoutChange();
|
|
1466
1473
|
await state.navigate(initialRoute, currentRoute === "/" && initialRoute !== "/");
|
|
1467
1474
|
|
package/src/types.ts
CHANGED
|
@@ -82,6 +82,7 @@ export interface DocRecord {
|
|
|
82
82
|
contentUrl: string;
|
|
83
83
|
fileName: string;
|
|
84
84
|
title: string;
|
|
85
|
+
prefix?: string;
|
|
85
86
|
date?: string;
|
|
86
87
|
updatedDate?: string;
|
|
87
88
|
description?: string;
|
|
@@ -99,9 +100,9 @@ export interface FileNode {
|
|
|
99
100
|
name: string;
|
|
100
101
|
id: string;
|
|
101
102
|
title: string;
|
|
103
|
+
prefix?: string;
|
|
102
104
|
route: string;
|
|
103
105
|
contentUrl: string;
|
|
104
|
-
mtime: number;
|
|
105
106
|
isNew: boolean;
|
|
106
107
|
tags: string[];
|
|
107
108
|
description?: string;
|
|
@@ -134,8 +135,8 @@ export interface Manifest {
|
|
|
134
135
|
id: string;
|
|
135
136
|
route: string;
|
|
136
137
|
title: string;
|
|
138
|
+
prefix?: string;
|
|
137
139
|
contentUrl: string;
|
|
138
|
-
mtime: number;
|
|
139
140
|
date?: string;
|
|
140
141
|
updatedDate?: string;
|
|
141
142
|
tags: string[];
|
|
@@ -156,6 +157,7 @@ export interface BuildCache {
|
|
|
156
157
|
publish: boolean;
|
|
157
158
|
draft: boolean;
|
|
158
159
|
title?: string;
|
|
160
|
+
prefix?: string;
|
|
159
161
|
date?: string;
|
|
160
162
|
updatedDate?: string;
|
|
161
163
|
description?: string;
|