docia 0.0.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/README.md +71 -0
- package/package.json +35 -0
- package/src/book/index.ts +11 -0
- package/src/book/summary.ts +327 -0
- package/src/book/types.ts +48 -0
- package/src/build/build-site.ts +277 -0
- package/src/build/client-assets.ts +109 -0
- package/src/build/index.ts +12 -0
- package/src/build/seo.ts +114 -0
- package/src/cli-types.ts +16 -0
- package/src/cli.ts +111 -0
- package/src/client/main.ts +28 -0
- package/src/client/router.ts +244 -0
- package/src/client/search.ts +619 -0
- package/src/client/styles.css +811 -0
- package/src/commands/build.ts +194 -0
- package/src/commands/check.ts +208 -0
- package/src/commands/dev.ts +33 -0
- package/src/commands/index.ts +59 -0
- package/src/commands/init.ts +80 -0
- package/src/commands/new.ts +125 -0
- package/src/commands/serve.ts +69 -0
- package/src/config/defaults.ts +42 -0
- package/src/config/define-config.ts +5 -0
- package/src/config/load-config.ts +473 -0
- package/src/config/types.ts +76 -0
- package/src/dev/index.ts +1 -0
- package/src/dev/start-dev-server.ts +213 -0
- package/src/errors.ts +16 -0
- package/src/index.ts +13 -0
- package/src/markdown/engine.ts +277 -0
- package/src/markdown/index.ts +7 -0
- package/src/render/index.ts +3 -0
- package/src/render/layout.tsx +616 -0
- package/src/search/index.ts +40 -0
- package/src/search/types.ts +7 -0
- package/src/server/static.ts +191 -0
- package/src/templates/init-template.ts +87 -0
- package/src/utils/args.ts +148 -0
- package/src/utils/html.ts +39 -0
- package/src/utils/process.ts +23 -0
- package/src/utils/strings.ts +42 -0
|
@@ -0,0 +1,244 @@
|
|
|
1
|
+
/// <reference lib="dom" />
|
|
2
|
+
|
|
3
|
+
const NAVIGATE_EVENT_NAME = "docia:navigate";
|
|
4
|
+
|
|
5
|
+
interface NavigateEventDetail {
|
|
6
|
+
href: string;
|
|
7
|
+
replace?: boolean;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export interface SpaRouterOptions {
|
|
11
|
+
onAfterNavigate?: () => void | Promise<void>;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
interface NavigateOptions {
|
|
15
|
+
replace?: boolean;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
const HEAD_SYNC_SELECTORS = [
|
|
19
|
+
"meta[name='description']",
|
|
20
|
+
"meta[property='og:title']",
|
|
21
|
+
"meta[name='docia-base-path']",
|
|
22
|
+
"meta[name='docia-search-index']",
|
|
23
|
+
"meta[name='docia-markdown-url']",
|
|
24
|
+
"meta[name='docia-llms-url']",
|
|
25
|
+
"link[rel='canonical']",
|
|
26
|
+
"script[type='application/ld+json']",
|
|
27
|
+
] as const;
|
|
28
|
+
|
|
29
|
+
function isModifiedClick(event: MouseEvent): boolean {
|
|
30
|
+
return event.metaKey || event.ctrlKey || event.shiftKey || event.altKey;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function isSamePageHashNavigation(url: URL): boolean {
|
|
34
|
+
return (
|
|
35
|
+
url.hash.length > 0 &&
|
|
36
|
+
url.pathname === window.location.pathname &&
|
|
37
|
+
url.search === window.location.search
|
|
38
|
+
);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function normalizeMarkdownMirrorPath(pathname: string): string {
|
|
42
|
+
if (!pathname.endsWith(".md")) {
|
|
43
|
+
return pathname;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
let normalized = pathname.slice(0, -".md".length);
|
|
47
|
+
if (normalized.endsWith("/index.html")) {
|
|
48
|
+
normalized = normalized.slice(0, -"index.html".length);
|
|
49
|
+
if (!normalized.endsWith("/")) {
|
|
50
|
+
normalized = `${normalized}/`;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
return normalized.length > 0 ? normalized : "/";
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
if (normalized.endsWith(".html")) {
|
|
57
|
+
return normalized;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
if (!normalized.endsWith("/")) {
|
|
61
|
+
normalized = `${normalized}/`;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
return normalized.length > 0 ? normalized : "/";
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
function isLikelyAssetPath(pathname: string): boolean {
|
|
68
|
+
return /\.(?:png|jpe?g|webp|gif|svg|ico|txt|xml|json|css|js|map|pdf|zip|gz|wasm)$/i.test(
|
|
69
|
+
pathname,
|
|
70
|
+
);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
function syncHead(incomingDocument: Document): void {
|
|
74
|
+
document.title = incomingDocument.title;
|
|
75
|
+
|
|
76
|
+
if (incomingDocument.documentElement.lang.trim().length > 0) {
|
|
77
|
+
document.documentElement.lang = incomingDocument.documentElement.lang;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
for (const selector of HEAD_SYNC_SELECTORS) {
|
|
81
|
+
document.head.querySelectorAll(selector).forEach((element) => element.remove());
|
|
82
|
+
|
|
83
|
+
incomingDocument.head.querySelectorAll(selector).forEach((element) => {
|
|
84
|
+
document.head.append(element.cloneNode(true));
|
|
85
|
+
});
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
function scrollToHash(hash: string): void {
|
|
90
|
+
if (hash.length <= 1) {
|
|
91
|
+
window.scrollTo({ top: 0, left: 0, behavior: "auto" });
|
|
92
|
+
return;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
const id = decodeURIComponent(hash.slice(1));
|
|
96
|
+
const target = document.getElementById(id);
|
|
97
|
+
if (target) {
|
|
98
|
+
target.scrollIntoView();
|
|
99
|
+
return;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
window.scrollTo({ top: 0, left: 0, behavior: "auto" });
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
export function initSpaRouter(options: SpaRouterOptions = {}): () => void {
|
|
106
|
+
let navigationRequestId = 0;
|
|
107
|
+
|
|
108
|
+
const navigate = async (targetUrl: URL, navigateOptions: NavigateOptions = {}): Promise<void> => {
|
|
109
|
+
const url = new URL(targetUrl.toString());
|
|
110
|
+
url.pathname = normalizeMarkdownMirrorPath(url.pathname);
|
|
111
|
+
|
|
112
|
+
if (url.origin !== window.location.origin) {
|
|
113
|
+
window.location.assign(url.toString());
|
|
114
|
+
return;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
if (isSamePageHashNavigation(url)) {
|
|
118
|
+
history.replaceState({}, "", url.toString());
|
|
119
|
+
scrollToHash(url.hash);
|
|
120
|
+
return;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
const requestId = navigationRequestId + 1;
|
|
124
|
+
navigationRequestId = requestId;
|
|
125
|
+
|
|
126
|
+
try {
|
|
127
|
+
const response = await fetch(url.toString(), {
|
|
128
|
+
headers: {
|
|
129
|
+
Accept: "text/html",
|
|
130
|
+
},
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
if (!response.ok) {
|
|
134
|
+
window.location.assign(url.toString());
|
|
135
|
+
return;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
const contentType = response.headers.get("content-type") ?? "";
|
|
139
|
+
if (!contentType.includes("text/html")) {
|
|
140
|
+
window.location.assign(url.toString());
|
|
141
|
+
return;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
const html = await response.text();
|
|
145
|
+
if (navigationRequestId !== requestId) {
|
|
146
|
+
return;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
const parsed = new DOMParser().parseFromString(html, "text/html");
|
|
150
|
+
const incomingApp = parsed.querySelector<HTMLElement>(".app");
|
|
151
|
+
const currentApp = document.querySelector<HTMLElement>(".app");
|
|
152
|
+
|
|
153
|
+
if (!incomingApp || !currentApp) {
|
|
154
|
+
window.location.assign(url.toString());
|
|
155
|
+
return;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
currentApp.replaceWith(incomingApp);
|
|
159
|
+
syncHead(parsed);
|
|
160
|
+
document.body.classList.remove("command-open");
|
|
161
|
+
|
|
162
|
+
if (navigateOptions.replace) {
|
|
163
|
+
history.replaceState({}, "", url.toString());
|
|
164
|
+
} else {
|
|
165
|
+
history.pushState({}, "", url.toString());
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
scrollToHash(url.hash);
|
|
169
|
+
if (options.onAfterNavigate) {
|
|
170
|
+
await options.onAfterNavigate();
|
|
171
|
+
}
|
|
172
|
+
} catch {
|
|
173
|
+
window.location.assign(url.toString());
|
|
174
|
+
}
|
|
175
|
+
};
|
|
176
|
+
|
|
177
|
+
const onDocumentClick = (event: MouseEvent): void => {
|
|
178
|
+
if (event.defaultPrevented || event.button !== 0 || isModifiedClick(event)) {
|
|
179
|
+
return;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
const target = event.target;
|
|
183
|
+
if (!(target instanceof Element)) {
|
|
184
|
+
return;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
const anchor = target.closest<HTMLAnchorElement>("a[href]");
|
|
188
|
+
if (!anchor) {
|
|
189
|
+
return;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
if (anchor.target && anchor.target !== "_self") {
|
|
193
|
+
return;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
if (anchor.hasAttribute("download") || anchor.getAttribute("rel") === "external") {
|
|
197
|
+
return;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
const url = new URL(anchor.href, window.location.href);
|
|
201
|
+
if (url.origin !== window.location.origin) {
|
|
202
|
+
return;
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
if (isSamePageHashNavigation(url)) {
|
|
206
|
+
return;
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
if (isLikelyAssetPath(url.pathname) && !url.pathname.endsWith(".md")) {
|
|
210
|
+
return;
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
event.preventDefault();
|
|
214
|
+
void navigate(url);
|
|
215
|
+
};
|
|
216
|
+
|
|
217
|
+
const onPopState = (): void => {
|
|
218
|
+
void navigate(new URL(window.location.href), { replace: true });
|
|
219
|
+
};
|
|
220
|
+
|
|
221
|
+
const onProgrammaticNavigate = (event: Event): void => {
|
|
222
|
+
const detail = (event as CustomEvent<NavigateEventDetail>).detail;
|
|
223
|
+
if (!detail || typeof detail.href !== "string") {
|
|
224
|
+
return;
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
void navigate(new URL(detail.href, window.location.href), {
|
|
228
|
+
replace: detail.replace === true,
|
|
229
|
+
});
|
|
230
|
+
};
|
|
231
|
+
|
|
232
|
+
document.addEventListener("click", onDocumentClick);
|
|
233
|
+
window.addEventListener("popstate", onPopState);
|
|
234
|
+
window.addEventListener(NAVIGATE_EVENT_NAME, onProgrammaticNavigate as EventListener);
|
|
235
|
+
|
|
236
|
+
return () => {
|
|
237
|
+
document.removeEventListener("click", onDocumentClick);
|
|
238
|
+
window.removeEventListener("popstate", onPopState);
|
|
239
|
+
window.removeEventListener(
|
|
240
|
+
NAVIGATE_EVENT_NAME,
|
|
241
|
+
onProgrammaticNavigate as EventListener,
|
|
242
|
+
);
|
|
243
|
+
};
|
|
244
|
+
}
|