lildocs 0.1.14 → 0.1.16

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.
@@ -0,0 +1,362 @@
1
+ import type { Heading, Page } from "../core/content.js";
2
+ import type { ResolvedLogo } from "../core/logo.js";
3
+ import type { NavItem } from "../core/nav.js";
4
+ import type { NavigationOptions } from "../core/theme.js";
5
+ import { relativeUrl, rootRelativeUrl } from "./paths.js";
6
+ import type { PageNavigation } from "./types.js";
7
+
8
+ export type LayoutProps = {
9
+ page: Page;
10
+ nav: NavItem[];
11
+ pageNavigation?: PageNavigation;
12
+ css: string;
13
+ searchIndexJson: string;
14
+ logo: ResolvedLogo;
15
+ favicon?: string;
16
+ repositoryUrl?: string;
17
+ projectName?: string;
18
+ navigation?: NavigationOptions;
19
+ clientScriptPath: string;
20
+ clientStylePaths: string[];
21
+ };
22
+
23
+ export function Layout({
24
+ page,
25
+ nav,
26
+ pageNavigation,
27
+ searchIndexJson,
28
+ logo,
29
+ favicon,
30
+ repositoryUrl,
31
+ projectName,
32
+ navigation,
33
+ clientScriptPath,
34
+ clientStylePaths,
35
+ }: LayoutProps) @{
36
+ const transition = navigation?.transition ?? "fade";
37
+ const repoIconUrl = rootRelativeUrl(page.route, "assets/github-icon.svg");
38
+ const issueUrl = issueUrlForRepository(repositoryUrl);
39
+ const repositoryLabel = repositoryLabelForUrl(repositoryUrl);
40
+
41
+ <>
42
+ <meta charSet="utf-8" />
43
+ <meta name="viewport" content="width=device-width, initial-scale=1" />
44
+ <title>
45
+ {(projectName ? `${page.title} • ${projectName}` : page.title) as string}
46
+ </title>
47
+ @if (favicon) {
48
+ <link rel="icon" href={assetSrc(page.route, favicon)} />
49
+ }
50
+ <link
51
+ rel="stylesheet"
52
+ href={rootRelativeUrl(page.route, "assets/tabler-icons.css")}
53
+ />
54
+ <link
55
+ rel="stylesheet"
56
+ href={rootRelativeUrl(page.route, "assets/lildocs.css")}
57
+ />
58
+ @for (const stylePath of clientStylePaths; key stylePath) {
59
+ <link rel="stylesheet" href={assetSrc(page.route, stylePath)} />
60
+ }
61
+ <div class="pageShell">
62
+ <aside id="lildocs-sidebar" class="sidebar">
63
+ <div class="sidebarContents">
64
+ <div class="sidebarHeader">
65
+ <a class="brand" href={relativeUrl(page.route, "index.html")}>
66
+ @if (logo.image) {
67
+ <img
68
+ class="brandLogo"
69
+ src={assetSrc(page.route, logo.image)}
70
+ alt=""
71
+ />
72
+ }
73
+ @if (logo.text) {
74
+ <span>
75
+ {logo.text as string}
76
+ </span>
77
+ }
78
+ </a>
79
+ <button
80
+ id="lildocs-sidebar-toggle"
81
+ class="sidebarToggle"
82
+ type="button"
83
+ aria-label="Collapse sidebar"
84
+ aria-controls="lildocs-sidebar"
85
+ >
86
+ <span
87
+ class="ti ti-layout-sidebar-left-collapse"
88
+ aria-hidden="true"
89
+ />
90
+ </button>
91
+ </div>
92
+ <div id="lildocs-search-root" class="searchBox">
93
+ <span class="searchIcon ti ti-search" aria-hidden="true" />
94
+ <input
95
+ id="lildocs-search-input"
96
+ type="search"
97
+ placeholder="Search docs"
98
+ aria-label="Search docs"
99
+ />
100
+ </div>
101
+ <nav
102
+ id="lildocs-sidebar-navigation"
103
+ aria-label="Documentation navigation"
104
+ >
105
+ <NavList
106
+ items={nav}
107
+ currentRoute={page.route}
108
+ pageRoute={page.route}
109
+ />
110
+ </nav>
111
+ </div>
112
+ </aside>
113
+ <div class="sidebarFloatingControls">
114
+ <button
115
+ id="lildocs-sidebar-expand"
116
+ class="sidebarToggle"
117
+ type="button"
118
+ aria-label="Expand sidebar"
119
+ aria-controls="lildocs-sidebar"
120
+ >
121
+ <span class="ti ti-layout-sidebar-left-expand" aria-hidden="true" />
122
+ </button>
123
+ <button
124
+ id="lildocs-floating-search"
125
+ class="sidebarToggle"
126
+ type="button"
127
+ aria-label="Search docs"
128
+ >
129
+ <span class="ti ti-search" aria-hidden="true" />
130
+ </button>
131
+ </div>
132
+ <div id="swup" class="contentGrid">
133
+ <main class={`content transition-${transition}`}>
134
+ <article>
135
+ <GroupBreadcrumbs route={page.route} />
136
+ <div dangerouslySetInnerHTML={{ __html: page.html ?? "" }} />
137
+ </article>
138
+ <PageNav pageNavigation={pageNavigation} />
139
+ </main>
140
+ <aside class={`toc transition-${transition}`}>
141
+ <Toc
142
+ headings={page.headings}
143
+ repositoryUrl={repositoryUrl}
144
+ repositoryLabel={repositoryLabel}
145
+ repoIconUrl={repoIconUrl}
146
+ />
147
+ </aside>
148
+ </div>
149
+ </div>
150
+ <script
151
+ dangerouslySetInnerHTML={{
152
+ __html: `window.lildocsSearchUrl = ${JSON.stringify(
153
+ rootRelativeUrl(page.route, "search-index.json"),
154
+ )};${
155
+ issueUrl
156
+ ? `window.lildocsIssueUrl = ${JSON.stringify(issueUrl)};`
157
+ : ""
158
+ }`,
159
+ }}
160
+ />
161
+ <script
162
+ type="application/json"
163
+ id="lildocs-search-index"
164
+ dangerouslySetInnerHTML={{ __html: searchIndexJson }}
165
+ />
166
+ <div id="lildocs-overlay-root" />
167
+ <script type="module" src={assetSrc(page.route, clientScriptPath)} />
168
+ </>
169
+ }
170
+
171
+ function issueUrlForRepository(repositoryUrl: string | undefined) {
172
+ if (!repositoryUrl) {
173
+ return undefined;
174
+ }
175
+
176
+ let url: URL;
177
+ try {
178
+ url = new URL(repositoryUrl);
179
+ } catch {
180
+ return undefined;
181
+ }
182
+
183
+ if (url.hostname !== "github.com") {
184
+ return undefined;
185
+ }
186
+
187
+ const [owner, repo] = url.pathname.split("/").filter(Boolean);
188
+ if (!owner || !repo) {
189
+ return undefined;
190
+ }
191
+
192
+ return `https://github.com/${owner}/${repo}/issues/new`;
193
+ }
194
+
195
+ function repositoryLabelForUrl(repositoryUrl: string | undefined) {
196
+ if (!repositoryUrl) {
197
+ return undefined;
198
+ }
199
+
200
+ try {
201
+ const url = new URL(repositoryUrl);
202
+ if (url.hostname !== "github.com") {
203
+ return undefined;
204
+ }
205
+
206
+ const [owner, repo] = url.pathname.split("/").filter(Boolean);
207
+ return owner && repo ? `${owner}/${repo}` : undefined;
208
+ } catch {
209
+ return undefined;
210
+ }
211
+ }
212
+
213
+ function assetSrc(pageRoute: string, image: string) {
214
+ if (
215
+ /^(?:[a-z]+:)?\/\//i.test(image) || image.startsWith("data:") ||
216
+ image.startsWith("/")
217
+ ) {
218
+ return image;
219
+ }
220
+
221
+ return rootRelativeUrl(pageRoute, image);
222
+ }
223
+
224
+ function GroupBreadcrumbs({ route }: { route: string }) @{
225
+ const dir = route.split("/").slice(0, -1);
226
+ if (dir.length === 0) {
227
+ return null;
228
+ }
229
+
230
+ <p class="groupBreadcrumbs">
231
+ {dir.map(titleFromDir).join(" / ") as string}
232
+ </p>
233
+ }
234
+
235
+ function titleFromDir(dir: string) {
236
+ return dir.replace(/[-_]+/g, " ").replace(
237
+ /\S+/g,
238
+ (word) => `${word[0]?.toLocaleUpperCase() ?? ""}${word.slice(1)}`,
239
+ );
240
+ }
241
+
242
+ function PageNav({ pageNavigation }: { pageNavigation?: PageNavigation }) @{
243
+ if (!pageNavigation?.next) {
244
+ return null;
245
+ }
246
+
247
+ <nav class="pageNav" aria-label="Page navigation">
248
+ <p>
249
+ {"Next: "}
250
+ <a rel="next" href={pageNavigation.next.href}>
251
+ {pageNavigation.next.title as string}
252
+ </a>
253
+ </p>
254
+ </nav>
255
+ }
256
+
257
+ function NavList({
258
+ items,
259
+ currentRoute,
260
+ pageRoute,
261
+ }: {
262
+ items: NavItem[];
263
+ currentRoute: string;
264
+ pageRoute: string;
265
+ }) @{
266
+ <ul class="navList">
267
+ @for (const item of items; key item.route) {
268
+ <li class={item.children.length > 0 ? "navGroup" : undefined}>
269
+ @if (item.children.length > 0 && item.hasPage) {
270
+ <details
271
+ class="navDisclosure"
272
+ open={isActiveBranch(item, currentRoute)}
273
+ >
274
+ <summary class={item.route === currentRoute ? "active" : undefined}>
275
+ {item.title as string}
276
+ </summary>
277
+ <NavList
278
+ items={item.children}
279
+ currentRoute={currentRoute}
280
+ pageRoute={pageRoute}
281
+ />
282
+ </details>
283
+ } @else if (item.children.length > 0) {
284
+ <span class="navFolder">
285
+ {item.title as string}
286
+ </span>
287
+ } @else {
288
+ <a
289
+ class={item.route === currentRoute ? "active" : undefined}
290
+ href={relativeUrl(pageRoute, item.route)}
291
+ >
292
+ {item.title as string}
293
+ </a>
294
+ }
295
+ @if (item.children.length > 0 && !item.hasPage) {
296
+ <NavList
297
+ items={item.children}
298
+ currentRoute={currentRoute}
299
+ pageRoute={pageRoute}
300
+ />
301
+ }
302
+ </li>
303
+ }
304
+ </ul>
305
+ }
306
+
307
+ function isActiveBranch(item: NavItem, currentRoute: string): boolean {
308
+ return item.route === currentRoute ||
309
+ item.children.some((child) => isActiveBranch(child, currentRoute));
310
+ }
311
+
312
+ function Toc({
313
+ headings,
314
+ repositoryUrl,
315
+ repositoryLabel,
316
+ repoIconUrl,
317
+ }: {
318
+ headings: Heading[];
319
+ repositoryUrl?: string;
320
+ repositoryLabel?: string;
321
+ repoIconUrl: string;
322
+ }) @{
323
+ const tocHeadings = headings.filter(
324
+ (heading) => heading.depth > 1 && heading.depth < 4,
325
+ );
326
+ if (tocHeadings.length === 0 && !repositoryLabel) {
327
+ return null;
328
+ }
329
+
330
+ <nav aria-label="Table of contents">
331
+ @if (tocHeadings.length > 0) {
332
+ <>
333
+ <p>On this page</p>
334
+ <ul>
335
+ @for (const heading of tocHeadings; key heading.id) {
336
+ <li class={`tocDepth${heading.depth}`}>
337
+ <a href={`#${heading.id}`}>
338
+ {heading.text as string}
339
+ </a>
340
+ </li>
341
+ }
342
+ </ul>
343
+ </>
344
+ }
345
+ @if (repositoryUrl && repositoryLabel) {
346
+ <a
347
+ class="tocRepoLink"
348
+ href={repositoryUrl}
349
+ aria-label="View repository on GitHub"
350
+ >
351
+ <span
352
+ class="repoIcon"
353
+ aria-hidden="true"
354
+ style={`--ld-repo-icon: url(${repoIconUrl})`}
355
+ />
356
+ <span>
357
+ {repositoryLabel as string}
358
+ </span>
359
+ </a>
360
+ }
361
+ </nav>
362
+ }