doc-repo 0.1.0-alpha.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.ja.md +162 -0
- package/README.md +162 -0
- package/dist/cli/exitCode.d.ts +2 -0
- package/dist/cli/exitCode.js +3 -0
- package/dist/cli/formatResultMessage.d.ts +2 -0
- package/dist/cli/formatResultMessage.js +18 -0
- package/dist/cli/index.d.ts +2 -0
- package/dist/cli/index.js +43 -0
- package/dist/core/parser/convertMarkdown.d.ts +4 -0
- package/dist/core/parser/convertMarkdown.js +127 -0
- package/dist/core/scanner/detectRoot.d.ts +2 -0
- package/dist/core/scanner/detectRoot.js +22 -0
- package/dist/core/scanner/scanMarkdown.d.ts +2 -0
- package/dist/core/scanner/scanMarkdown.js +22 -0
- package/dist/core/site/atomicPublish.d.ts +1 -0
- package/dist/core/site/atomicPublish.js +21 -0
- package/dist/core/site/buildSiteBundle.d.ts +2 -0
- package/dist/core/site/buildSiteBundle.js +79 -0
- package/dist/core/site/copyAssets.d.ts +1 -0
- package/dist/core/site/copyAssets.js +8 -0
- package/dist/core/site/generateSite.d.ts +2 -0
- package/dist/core/site/generateSite.js +101 -0
- package/dist/core/site/renderPages.d.ts +2 -0
- package/dist/core/site/renderPages.js +82 -0
- package/dist/shared/errors.d.ts +9 -0
- package/dist/shared/errors.js +28 -0
- package/dist/shared/logger.d.ts +7 -0
- package/dist/shared/logger.js +16 -0
- package/dist/shared/sitePaths.d.ts +5 -0
- package/dist/shared/sitePaths.js +28 -0
- package/dist/shared/types.d.ts +45 -0
- package/dist/shared/types.js +1 -0
- package/package.json +64 -0
- package/templates/app.js +236 -0
- package/templates/page.html +23 -0
- package/templates/styles.css +157 -0
package/templates/app.js
ADDED
|
@@ -0,0 +1,236 @@
|
|
|
1
|
+
const treeEl = document.getElementById("tree");
|
|
2
|
+
const articleEl = document.getElementById("article");
|
|
3
|
+
|
|
4
|
+
const uiState = {
|
|
5
|
+
expandedDirs: new Set(),
|
|
6
|
+
selectedId: null,
|
|
7
|
+
};
|
|
8
|
+
|
|
9
|
+
const readPageIdFromHash = () => {
|
|
10
|
+
const raw = window.location.hash.replace(/^#/, "");
|
|
11
|
+
if (!raw) {
|
|
12
|
+
return null;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
const encoded = raw.startsWith("doc=") ? raw.slice(4) : raw;
|
|
16
|
+
try {
|
|
17
|
+
return decodeURIComponent(encoded);
|
|
18
|
+
} catch {
|
|
19
|
+
return encoded;
|
|
20
|
+
}
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
const parseDocHashToId = (rawHash) => {
|
|
24
|
+
if (!rawHash) {
|
|
25
|
+
return null;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const raw = rawHash.replace(/^#/, "");
|
|
29
|
+
const encoded = raw.startsWith("doc=") ? raw.slice(4) : raw;
|
|
30
|
+
try {
|
|
31
|
+
return decodeURIComponent(encoded);
|
|
32
|
+
} catch {
|
|
33
|
+
return encoded;
|
|
34
|
+
}
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
const writePageIdToHash = (id) => {
|
|
38
|
+
const nextHash = `#doc=${encodeURIComponent(id)}`;
|
|
39
|
+
if (window.location.hash !== nextHash) {
|
|
40
|
+
window.location.hash = nextHash;
|
|
41
|
+
}
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
const hasPage = (id) => state.pages.some((page) => page.id === id);
|
|
45
|
+
|
|
46
|
+
const expandDirsForPageId = (id) => {
|
|
47
|
+
const segments = id.split("/");
|
|
48
|
+
segments.pop();
|
|
49
|
+
|
|
50
|
+
let key = "";
|
|
51
|
+
for (const segment of segments) {
|
|
52
|
+
key = key ? `${key}/${segment}` : segment;
|
|
53
|
+
uiState.expandedDirs.add(key);
|
|
54
|
+
}
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
const selectPage = (id, options = {}) => {
|
|
58
|
+
const { syncHash = false } = options;
|
|
59
|
+
if (!hasPage(id)) {
|
|
60
|
+
return;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
uiState.selectedId = id;
|
|
64
|
+
expandDirsForPageId(id);
|
|
65
|
+
renderTree(state.tree);
|
|
66
|
+
renderArticle(id);
|
|
67
|
+
|
|
68
|
+
if (syncHash) {
|
|
69
|
+
writePageIdToHash(id);
|
|
70
|
+
}
|
|
71
|
+
};
|
|
72
|
+
|
|
73
|
+
const collectDirKeys = (nodes, parentKey = "", out = new Set()) => {
|
|
74
|
+
for (const node of nodes) {
|
|
75
|
+
if (node.type !== "dir") {
|
|
76
|
+
continue;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
const key = parentKey ? `${parentKey}/${node.name}` : node.name;
|
|
80
|
+
out.add(key);
|
|
81
|
+
collectDirKeys(node.children || [], key, out);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
return out;
|
|
85
|
+
};
|
|
86
|
+
|
|
87
|
+
const renderTree = (nodes) => {
|
|
88
|
+
const createList = (items, parentKey = "") => {
|
|
89
|
+
const ul = document.createElement("ul");
|
|
90
|
+
|
|
91
|
+
for (const item of items) {
|
|
92
|
+
const li = document.createElement("li");
|
|
93
|
+
const row = document.createElement("div");
|
|
94
|
+
row.className = "tree-row";
|
|
95
|
+
|
|
96
|
+
const marker = document.createElement("span");
|
|
97
|
+
marker.className = "tree-marker";
|
|
98
|
+
marker.textContent = "•";
|
|
99
|
+
row.appendChild(marker);
|
|
100
|
+
|
|
101
|
+
if (item.type === "dir") {
|
|
102
|
+
const dirKey = parentKey ? `${parentKey}/${item.name}` : item.name;
|
|
103
|
+
const isExpanded = uiState.expandedDirs.has(dirKey);
|
|
104
|
+
|
|
105
|
+
const button = document.createElement("button");
|
|
106
|
+
button.type = "button";
|
|
107
|
+
button.className = "dir-toggle";
|
|
108
|
+
button.textContent = `${isExpanded ? "▾" : "▸"} ${item.name}`;
|
|
109
|
+
button.setAttribute("aria-expanded", String(isExpanded));
|
|
110
|
+
button.addEventListener("click", () => {
|
|
111
|
+
if (uiState.expandedDirs.has(dirKey)) {
|
|
112
|
+
uiState.expandedDirs.delete(dirKey);
|
|
113
|
+
} else {
|
|
114
|
+
uiState.expandedDirs.add(dirKey);
|
|
115
|
+
}
|
|
116
|
+
renderTree(state.tree);
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
const children = createList(item.children || [], dirKey);
|
|
120
|
+
children.hidden = !isExpanded;
|
|
121
|
+
|
|
122
|
+
row.appendChild(button);
|
|
123
|
+
li.appendChild(row);
|
|
124
|
+
li.appendChild(children);
|
|
125
|
+
} else {
|
|
126
|
+
const link = document.createElement("a");
|
|
127
|
+
link.href = "#";
|
|
128
|
+
link.textContent = item.name;
|
|
129
|
+
if (item.id === uiState.selectedId) {
|
|
130
|
+
link.classList.add("selected");
|
|
131
|
+
link.setAttribute("aria-current", "page");
|
|
132
|
+
}
|
|
133
|
+
link.addEventListener("click", (event) => {
|
|
134
|
+
event.preventDefault();
|
|
135
|
+
selectPage(item.id, { syncHash: true });
|
|
136
|
+
});
|
|
137
|
+
row.appendChild(link);
|
|
138
|
+
li.appendChild(row);
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
ul.appendChild(li);
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
return ul;
|
|
145
|
+
};
|
|
146
|
+
|
|
147
|
+
treeEl.replaceChildren(createList(nodes));
|
|
148
|
+
};
|
|
149
|
+
|
|
150
|
+
let state = { pages: [], tree: [] };
|
|
151
|
+
|
|
152
|
+
const loadEmbeddedState = () => {
|
|
153
|
+
const dataEl = document.getElementById("doc-repo-data");
|
|
154
|
+
if (!dataEl || !dataEl.textContent) {
|
|
155
|
+
return null;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
try {
|
|
159
|
+
return JSON.parse(dataEl.textContent);
|
|
160
|
+
} catch {
|
|
161
|
+
return null;
|
|
162
|
+
}
|
|
163
|
+
};
|
|
164
|
+
|
|
165
|
+
const renderArticle = (id) => {
|
|
166
|
+
const page = state.pages.find((value) => value.id === id);
|
|
167
|
+
if (!page) {
|
|
168
|
+
articleEl.innerHTML = '<p class="muted">Document not found.</p>';
|
|
169
|
+
return;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
articleEl.innerHTML = page.html;
|
|
173
|
+
};
|
|
174
|
+
|
|
175
|
+
const bindArticleInternalLinks = () => {
|
|
176
|
+
articleEl.addEventListener("click", (event) => {
|
|
177
|
+
const target = event.target;
|
|
178
|
+
if (!(target instanceof Element)) {
|
|
179
|
+
return;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
const anchor = target.closest("a");
|
|
183
|
+
if (!(anchor instanceof HTMLAnchorElement)) {
|
|
184
|
+
return;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
const href = anchor.getAttribute("href");
|
|
188
|
+
if (!href?.startsWith("#doc=")) {
|
|
189
|
+
return;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
// 生成時に実在ページと突き合わせ済みの #doc= リンクのみを内部遷移として扱う。
|
|
193
|
+
const docId = parseDocHashToId(href);
|
|
194
|
+
if (!docId || !hasPage(docId)) {
|
|
195
|
+
return;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
event.preventDefault();
|
|
199
|
+
selectPage(docId, { syncHash: true });
|
|
200
|
+
});
|
|
201
|
+
};
|
|
202
|
+
|
|
203
|
+
const bootstrap = async () => {
|
|
204
|
+
const embedded = loadEmbeddedState();
|
|
205
|
+
if (embedded) {
|
|
206
|
+
state = embedded;
|
|
207
|
+
} else {
|
|
208
|
+
const response = await fetch("./content.json");
|
|
209
|
+
state = await response.json();
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
if (state.pages[0]) {
|
|
213
|
+
bindArticleInternalLinks();
|
|
214
|
+
uiState.expandedDirs = collectDirKeys(state.tree);
|
|
215
|
+
const hashPageId = readPageIdFromHash();
|
|
216
|
+
if (hashPageId && hasPage(hashPageId)) {
|
|
217
|
+
selectPage(hashPageId);
|
|
218
|
+
} else {
|
|
219
|
+
selectPage(state.pages[0].id, { syncHash: true });
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
window.addEventListener("hashchange", () => {
|
|
223
|
+
const nextId = readPageIdFromHash();
|
|
224
|
+
if (!nextId || nextId === uiState.selectedId || !hasPage(nextId)) {
|
|
225
|
+
return;
|
|
226
|
+
}
|
|
227
|
+
selectPage(nextId);
|
|
228
|
+
});
|
|
229
|
+
} else {
|
|
230
|
+
articleEl.innerHTML = '<p class="muted">No Markdown files found.</p>';
|
|
231
|
+
}
|
|
232
|
+
};
|
|
233
|
+
|
|
234
|
+
bootstrap().catch((error) => {
|
|
235
|
+
articleEl.innerHTML = `<p class="muted">Failed to load site data: ${String(error)}</p>`;
|
|
236
|
+
});
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
<!doctype html>
|
|
2
|
+
<html lang="ja">
|
|
3
|
+
|
|
4
|
+
<head>
|
|
5
|
+
<meta charset="utf-8" />
|
|
6
|
+
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
7
|
+
<title>__TITLE__</title>
|
|
8
|
+
<link rel="stylesheet" href="__STYLES_HREF__" />
|
|
9
|
+
</head>
|
|
10
|
+
|
|
11
|
+
<body>
|
|
12
|
+
<div class="layout">
|
|
13
|
+
<aside class="sidebar">
|
|
14
|
+
<h1><a href="__HOME_HREF__">Doc Repo</a></h1>
|
|
15
|
+
<nav id="tree">__SIDEBAR__</nav>
|
|
16
|
+
</aside>
|
|
17
|
+
<main class="content">
|
|
18
|
+
<article id="article">__ARTICLE__</article>
|
|
19
|
+
</main>
|
|
20
|
+
</div>
|
|
21
|
+
</body>
|
|
22
|
+
|
|
23
|
+
</html>
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
:root {
|
|
2
|
+
--bg: #f4f7f2;
|
|
3
|
+
--panel: #ffffff;
|
|
4
|
+
--line: #d8dfd2;
|
|
5
|
+
--ink: #1f2a1f;
|
|
6
|
+
--muted: #5a6758;
|
|
7
|
+
--accent: #2f6f4f;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
* {
|
|
11
|
+
box-sizing: border-box;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
body {
|
|
15
|
+
margin: 0;
|
|
16
|
+
font-family: "Hiragino Sans", "Noto Sans JP", sans-serif;
|
|
17
|
+
background: linear-gradient(135deg, #f4f7f2 0%, #edf3ea 100%);
|
|
18
|
+
color: var(--ink);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
.layout {
|
|
22
|
+
display: grid;
|
|
23
|
+
grid-template-columns: 320px 1fr;
|
|
24
|
+
min-height: 100vh;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
.sidebar {
|
|
28
|
+
border-right: 1px solid var(--line);
|
|
29
|
+
padding: 20px;
|
|
30
|
+
background: var(--panel);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
.sidebar h1 {
|
|
34
|
+
margin: 0 0 16px;
|
|
35
|
+
font-size: 18px;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
.sidebar h1 a {
|
|
39
|
+
color: inherit;
|
|
40
|
+
text-decoration: none;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
.sidebar a {
|
|
44
|
+
color: var(--accent);
|
|
45
|
+
text-decoration: none;
|
|
46
|
+
display: block;
|
|
47
|
+
line-height: 1.3;
|
|
48
|
+
overflow-wrap: anywhere;
|
|
49
|
+
word-break: break-word;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
.sidebar a.selected {
|
|
53
|
+
font-weight: 700;
|
|
54
|
+
text-decoration: underline;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
.sidebar summary {
|
|
58
|
+
cursor: pointer;
|
|
59
|
+
color: var(--ink);
|
|
60
|
+
line-height: 1.3;
|
|
61
|
+
overflow-wrap: anywhere;
|
|
62
|
+
word-break: break-word;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
.sidebar summary:hover {
|
|
66
|
+
color: var(--accent);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
.sidebar ul {
|
|
70
|
+
margin: 0;
|
|
71
|
+
padding-left: 0;
|
|
72
|
+
list-style: none;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
.sidebar ul ul {
|
|
76
|
+
padding-left: 1.15rem;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
.sidebar details > ul {
|
|
80
|
+
padding-left: 1.15rem;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
.sidebar li {
|
|
84
|
+
margin: 0.2rem 0;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
.content {
|
|
88
|
+
padding: 24px;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
.content article {
|
|
92
|
+
max-width: 1120px;
|
|
93
|
+
background: var(--panel);
|
|
94
|
+
border: 1px solid var(--line);
|
|
95
|
+
border-radius: 12px;
|
|
96
|
+
padding: 24px;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
.content article img {
|
|
100
|
+
max-width: 100%;
|
|
101
|
+
height: auto;
|
|
102
|
+
display: block;
|
|
103
|
+
margin: 1rem 0;
|
|
104
|
+
border-radius: 8px;
|
|
105
|
+
border: 1px solid #dbe3d6;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
.content article blockquote {
|
|
109
|
+
margin: 1rem 0;
|
|
110
|
+
padding: 0.75rem 1rem;
|
|
111
|
+
border-left: 4px solid var(--accent);
|
|
112
|
+
background: #f3f7f2;
|
|
113
|
+
color: #324233;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
.content article blockquote p {
|
|
117
|
+
margin: 0.25rem 0;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
.content article pre {
|
|
121
|
+
margin: 1rem 0;
|
|
122
|
+
padding: 1rem;
|
|
123
|
+
overflow-x: auto;
|
|
124
|
+
border-radius: 10px;
|
|
125
|
+
border: 1px solid #2d3a31;
|
|
126
|
+
background: #1f2823;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
.content article pre code {
|
|
130
|
+
color: #e9f2ec;
|
|
131
|
+
background: transparent;
|
|
132
|
+
padding: 0;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
.content article :not(pre) > code {
|
|
136
|
+
background: #edf2ed;
|
|
137
|
+
border: 1px solid #d8e1d8;
|
|
138
|
+
border-radius: 6px;
|
|
139
|
+
padding: 0.12rem 0.35rem;
|
|
140
|
+
font-family: "SFMono-Regular", Menlo, Consolas, monospace;
|
|
141
|
+
font-size: 0.92em;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
.muted {
|
|
145
|
+
color: var(--muted);
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
@media (max-width: 900px) {
|
|
149
|
+
.layout {
|
|
150
|
+
grid-template-columns: 1fr;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
.sidebar {
|
|
154
|
+
border-right: none;
|
|
155
|
+
border-bottom: 1px solid var(--line);
|
|
156
|
+
}
|
|
157
|
+
}
|