lildocs 0.1.19 → 0.1.21

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 CHANGED
@@ -31,7 +31,11 @@ disk or from any static file host.
31
31
  ## Documentation
32
32
 
33
33
  - [Getting started](https://aleclarson.github.io/lildocs/getting-started.html)
34
- - [Authoring docs](https://aleclarson.github.io/lildocs/guides/authoring.html)
34
+ - [Writing content](https://aleclarson.github.io/lildocs/features/content.html)
35
+ - [Navigation and page structure](https://aleclarson.github.io/lildocs/features/navigation.html)
36
+ - [Local search](https://aleclarson.github.io/lildocs/features/search.html)
37
+ - [Mermaid diagrams](https://aleclarson.github.io/lildocs/features/mermaid.html)
38
+ - [Generated API reference](https://aleclarson.github.io/lildocs/features/api-reference.html)
35
39
  - [GitHub Pages deployment](https://aleclarson.github.io/lildocs/guides/github-pages.html)
36
40
  - [CLI reference](https://aleclarson.github.io/lildocs/reference/cli.html)
37
41
  - [Configuration reference](https://aleclarson.github.io/lildocs/reference/configuration.html)
package/dist/cli.mjs CHANGED
@@ -1714,7 +1714,8 @@ async function buildSite(options) {
1714
1714
  favicon: configOptions.favicon
1715
1715
  });
1716
1716
  const mermaid = await createMermaidRenderer({ themeConfig: themeToMermaidConfig(theme, fontResolution.themeFonts) });
1717
- const css = `${resolveThemeFontImports(theme, configOptions.fonts)}${fontResolution.css}${themeToCssVariables(theme, fontResolution.themeFonts, configOptions.link, configOptions.navigation)}\n${mermaid.css}${backgroundResolution.css}${logoResolution.css}${baseCss}`;
1717
+ const configuredCss = `${resolveThemeFontImports(theme, configOptions.fonts)}${fontResolution.css}${themeToCssVariables(theme, fontResolution.themeFonts, configOptions.link, configOptions.navigation)}\n${mermaid.css}${backgroundResolution.css}${logoResolution.css}`;
1718
+ const css = `${configuredCss}${baseCss}`;
1718
1719
  const assets = [
1719
1720
  ...fontResolution.assets,
1720
1721
  ...backgroundResolution.assets,
@@ -1761,7 +1762,7 @@ async function buildSite(options) {
1761
1762
  page,
1762
1763
  nav,
1763
1764
  pageNavigation: pageNavigation.get(page.route),
1764
- css,
1765
+ css: configuredCss,
1765
1766
  searchIndexJson,
1766
1767
  logo: logoResolution.logo,
1767
1768
  favicon: logoResolution.favicon,
@@ -34,7 +34,6 @@ export function Layout({
34
34
  clientStylePaths,
35
35
  }: LayoutProps) @{
36
36
  const transition = navigation?.transition ?? "fade";
37
- const repoIconUrl = rootRelativeUrl(page.route, "assets/github-icon.svg");
38
37
  const issueUrl = issueUrlForRepository(repositoryUrl);
39
38
  const repositoryLabel = repositoryLabelForUrl(repositoryUrl);
40
39
 
@@ -82,11 +81,13 @@ export function Layout({
82
81
  type="button"
83
82
  aria-label="Collapse sidebar"
84
83
  aria-controls="lildocs-sidebar"
84
+ aria-expanded="false"
85
85
  >
86
86
  <span
87
- class="ti ti-layout-sidebar-left-collapse"
87
+ class="sidebarCollapseIcon ti ti-layout-sidebar-left-collapse"
88
88
  aria-hidden="true"
89
89
  />
90
+ <span class="sidebarMenuIcon ti ti-menu-2" aria-hidden="true" />
90
91
  </button>
91
92
  </div>
92
93
  <div id="lildocs-search-root" class="searchBox">
@@ -108,6 +109,18 @@ export function Layout({
108
109
  pageRoute={page.route}
109
110
  />
110
111
  </nav>
112
+ @if (repositoryUrl && repositoryLabel) {
113
+ <a
114
+ class="sidebarRepoLink"
115
+ href={repositoryUrl}
116
+ aria-label="View repository on GitHub"
117
+ >
118
+ <span class="repoIcon" aria-hidden="true" />
119
+ <span>
120
+ {repositoryLabel as string}
121
+ </span>
122
+ </a>
123
+ }
111
124
  </div>
112
125
  </aside>
113
126
  <div class="sidebarFloatingControls">
@@ -142,7 +155,6 @@ export function Layout({
142
155
  headings={page.headings}
143
156
  repositoryUrl={repositoryUrl}
144
157
  repositoryLabel={repositoryLabel}
145
- repoIconUrl={repoIconUrl}
146
158
  />
147
159
  </aside>
148
160
  </div>
@@ -265,8 +277,13 @@ function NavList({
265
277
  }) @{
266
278
  <ul class="navList">
267
279
  @for (const item of items; key item.route) {
280
+ const isReferenceGroup = item.route === "reference/index.html";
268
281
  <li class={item.children.length > 0 ? "navGroup" : undefined}>
269
- @if (item.children.length > 0 && item.hasPage) {
282
+ @if (isReferenceGroup) {
283
+ <span class="navFolder">
284
+ {item.title as string}
285
+ </span>
286
+ } @else if (item.children.length > 0 && item.hasPage) {
270
287
  <details
271
288
  class="navDisclosure"
272
289
  open={isActiveBranch(item, currentRoute)}
@@ -292,7 +309,7 @@ function NavList({
292
309
  {item.title as string}
293
310
  </a>
294
311
  }
295
- @if (item.children.length > 0 && !item.hasPage) {
312
+ @if (item.children.length > 0 && !item.hasPage && !isReferenceGroup) {
296
313
  <NavList
297
314
  items={item.children}
298
315
  currentRoute={currentRoute}
@@ -313,12 +330,10 @@ function Toc({
313
330
  headings,
314
331
  repositoryUrl,
315
332
  repositoryLabel,
316
- repoIconUrl,
317
333
  }: {
318
334
  headings: Heading[];
319
335
  repositoryUrl?: string;
320
336
  repositoryLabel?: string;
321
- repoIconUrl: string;
322
337
  }) @{
323
338
  const tocHeadings = headings.filter(
324
339
  (heading) => heading.depth > 1 && heading.depth < 4,
@@ -330,16 +345,23 @@ function Toc({
330
345
  <nav aria-label="Table of contents">
331
346
  @if (tocHeadings.length > 0) {
332
347
  <>
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>
348
+ <p class="tocTitle">
349
+ <span class="ti ti-align-left" aria-hidden="true" />
350
+ On this page
351
+ </p>
352
+ <div class="tocLinks" data-toc-links>
353
+ <span class="tocRail" aria-hidden="true" />
354
+ <span class="tocVisibility" data-toc-visibility aria-hidden="true" />
355
+ <ul>
356
+ @for (const heading of tocHeadings; key heading.id) {
357
+ <li class={`tocDepth${heading.depth}`}>
358
+ <a href={`#${heading.id}`}>
359
+ {heading.text as string}
360
+ </a>
361
+ </li>
362
+ }
363
+ </ul>
364
+ </div>
343
365
  </>
344
366
  }
345
367
  @if (repositoryUrl && repositoryLabel) {
@@ -348,11 +370,7 @@ function Toc({
348
370
  href={repositoryUrl}
349
371
  aria-label="View repository on GitHub"
350
372
  >
351
- <span
352
- class="repoIcon"
353
- aria-hidden="true"
354
- style={`--ld-repo-icon: url(${repoIconUrl})`}
355
- />
373
+ <span class="repoIcon" aria-hidden="true" />
356
374
  <span>
357
375
  {repositoryLabel as string}
358
376
  </span>
@@ -73,11 +73,16 @@ export function SearchBox({
73
73
  }
74
74
 
75
75
  document.documentElement.classList.remove("sidebar-collapsed");
76
+ document.dispatchEvent(new CustomEvent("lildocs:sidebar-menu-open"));
76
77
  inputRef.current?.focus();
77
78
  setOpen(Boolean(query.trim()));
78
79
  };
79
80
  const handleShortcut = (event: KeyboardEvent) => {
80
- if ((event.metaKey || event.ctrlKey) && event.key.toLowerCase() === "k") {
81
+ const key = event.key.toLowerCase();
82
+ const hasCommandModifier = event.metaKey || event.ctrlKey;
83
+ if (
84
+ hasCommandModifier && (key === "k" || event.shiftKey && key === "f")
85
+ ) {
81
86
  event.preventDefault();
82
87
  toggleSearch();
83
88
  }
@@ -6,6 +6,7 @@ import { preloadRelativeUrl, SearchBox } from "./Search.tsrx";
6
6
  import { initSectionHighlights } from "./section-highlight.js";
7
7
  import { initSidebar } from "./sidebar.js";
8
8
  import { initTableViewer } from "./table-viewer.js";
9
+ import { initTocVisibility } from "./toc-visibility.js";
9
10
  import type { SearchEntry } from "./Search.tsrx";
10
11
 
11
12
  initCopyCode();
@@ -14,6 +15,7 @@ initNavigation();
14
15
  initSectionHighlights();
15
16
  initSidebar();
16
17
  initTableViewer();
18
+ initTocVisibility();
17
19
 
18
20
  void mountSearch();
19
21
 
@@ -1,7 +1,13 @@
1
1
  const headingSelector =
2
2
  ".content article h1[id], .content article h2[id], .content article h3[id], .content article h4[id], .content article h5[id], .content article h6[id]";
3
+ const headingLinkIcons = {
4
+ link: '<span class="ti ti-link" aria-hidden="true"></span>',
5
+ check: '<span class="ti ti-check" aria-hidden="true"></span>',
6
+ };
3
7
 
4
8
  export function initHeadingLinks() {
9
+ enhanceHeadingLinks();
10
+ document.addEventListener("lildocs:page-view", enhanceHeadingLinks);
5
11
  document.addEventListener("click", (event) => {
6
12
  if (!(event.target instanceof Element) || event.target.closest("a")) {
7
13
  return;
@@ -16,17 +22,46 @@ export function initHeadingLinks() {
16
22
  });
17
23
  }
18
24
 
25
+ function enhanceHeadingLinks() {
26
+ const headings =
27
+ document.querySelectorAll<HTMLHeadingElement>(headingSelector);
28
+
29
+ for (const heading of Array.from(headings)) {
30
+ if (heading.querySelector(".headingLinkIcon")) {
31
+ continue;
32
+ }
33
+
34
+ const icon = document.createElement("button");
35
+ icon.type = "button";
36
+ icon.className = "headingLinkIcon";
37
+ icon.setAttribute("aria-label", "Copy link to heading");
38
+ icon.title = "Copy link to heading";
39
+ icon.innerHTML = headingLinkIcons.link;
40
+ heading.append(icon);
41
+ }
42
+ }
43
+
19
44
  async function copyHeadingLink(heading: HTMLHeadingElement) {
20
45
  const url = new URL(window.location.href);
21
46
  url.hash = heading.tagName === "H1" ? "" : heading.id;
22
47
 
23
48
  try {
24
49
  await writeClipboard(url.href);
25
- heading.classList.add("headingLinkCopied");
26
- window.setTimeout(
27
- () => heading.classList.remove("headingLinkCopied"),
28
- 1600,
29
- );
50
+ const icon = heading.querySelector<HTMLButtonElement>(".headingLinkIcon");
51
+ if (!icon) {
52
+ return;
53
+ }
54
+
55
+ icon.classList.add("headingLinkCopied");
56
+ icon.innerHTML = headingLinkIcons.check;
57
+ icon.setAttribute("aria-label", "Copied");
58
+ icon.title = "Copied";
59
+ window.setTimeout(() => {
60
+ icon.classList.remove("headingLinkCopied");
61
+ icon.innerHTML = headingLinkIcons.link;
62
+ icon.setAttribute("aria-label", "Copy link to heading");
63
+ icon.title = "Copy link to heading";
64
+ }, 1600);
30
65
  } catch {}
31
66
  }
32
67
 
@@ -14,6 +14,17 @@ export function initSidebar() {
14
14
  return;
15
15
  }
16
16
 
17
+ const mobileQuery = window.matchMedia("(max-width: 860px)");
18
+
19
+ const setMenuOpen = (open: boolean) => {
20
+ document.documentElement.classList.toggle("sidebar-menu-open", open);
21
+ collapseButton.setAttribute("aria-expanded", String(open));
22
+ collapseButton.setAttribute(
23
+ "aria-label",
24
+ open ? "Close navigation menu" : "Open navigation menu",
25
+ );
26
+ };
27
+
17
28
  const setCollapsed = (collapsed: boolean) => {
18
29
  document.documentElement.classList.toggle("sidebar-collapsed", collapsed);
19
30
  collapseButton.setAttribute(
@@ -22,10 +33,61 @@ export function initSidebar() {
22
33
  );
23
34
  };
24
35
 
25
- collapseButton.addEventListener("click", () => setCollapsed(true));
36
+ const syncResponsiveState = () => {
37
+ document.documentElement.classList.remove("sidebar-menu-open");
38
+ collapseButton.setAttribute("aria-expanded", "false");
39
+ collapseButton.setAttribute(
40
+ "aria-label",
41
+ mobileQuery.matches ? "Open navigation menu" : "Collapse sidebar",
42
+ );
43
+ };
44
+
45
+ collapseButton.addEventListener("click", () => {
46
+ if (mobileQuery.matches) {
47
+ setMenuOpen(
48
+ !document.documentElement.classList.contains("sidebar-menu-open"),
49
+ );
50
+ return;
51
+ }
52
+ setCollapsed(true);
53
+ });
26
54
  expandButton.addEventListener("click", () => setCollapsed(false));
55
+ document.addEventListener("keydown", (event) => {
56
+ if (event.key === "Escape" && mobileQuery.matches) {
57
+ setMenuOpen(false);
58
+ return;
59
+ }
60
+ if ((event.metaKey || event.ctrlKey) && event.key.toLowerCase() === "b") {
61
+ event.preventDefault();
62
+ if (mobileQuery.matches) {
63
+ setMenuOpen(
64
+ !document.documentElement.classList.contains("sidebar-menu-open"),
65
+ );
66
+ return;
67
+ }
68
+ setCollapsed(
69
+ !document.documentElement.classList.contains("sidebar-collapsed"),
70
+ );
71
+ }
72
+ });
73
+ sidebar.addEventListener("click", (event) => {
74
+ if (
75
+ mobileQuery.matches &&
76
+ event.target instanceof Element &&
77
+ event.target.closest("a[href]")
78
+ ) {
79
+ setMenuOpen(false);
80
+ }
81
+ });
27
82
  searchButton.addEventListener("click", () => {
28
83
  setCollapsed(false);
29
84
  document.dispatchEvent(new CustomEvent("lildocs:search-toggle"));
30
85
  });
86
+ document.addEventListener("lildocs:sidebar-menu-open", () => {
87
+ if (mobileQuery.matches) {
88
+ setMenuOpen(true);
89
+ }
90
+ });
91
+ mobileQuery.addEventListener("change", syncResponsiveState);
92
+ syncResponsiveState();
31
93
  }
@@ -129,6 +129,14 @@ body {
129
129
  outline-offset: 2px;
130
130
  }
131
131
 
132
+ .sidebarMenuIcon {
133
+ display: none;
134
+ }
135
+
136
+ .sidebarRepoLink {
137
+ display: none;
138
+ }
139
+
132
140
  .sidebarFloatingControls {
133
141
  position: fixed;
134
142
  top: 24px;
@@ -185,7 +193,8 @@ html.is-animating .transition-scale {
185
193
  top: 0;
186
194
  align-self: start;
187
195
  max-height: 100vh;
188
- overflow: auto;
196
+ overflow-x: hidden;
197
+ overflow-y: auto;
189
198
  padding-block: 70px 24px;
190
199
  }
191
200
 
@@ -202,6 +211,7 @@ html.is-animating .transition-scale {
202
211
  .content h5,
203
212
  .content h6 {
204
213
  font-family: var(--ld-font-heading);
214
+ line-height: 1.2;
205
215
  }
206
216
 
207
217
  .content article :where(h1, h2, h3, h4, h5, h6) {
@@ -210,19 +220,46 @@ html.is-animating .transition-scale {
210
220
  }
211
221
 
212
222
  .content article :where(h1, h2, h3, h4, h5, h6)[id] {
213
- cursor: copy;
223
+ cursor: pointer;
214
224
  }
215
225
 
216
- .content article :where(h1, h2, h3, h4, h5, h6).headingLinkCopied::after {
217
- margin-left: 0.6em;
218
- color: var(--ld-color-accent);
219
- content: "Link copied";
220
- font-family: var(--ld-font-body);
221
- font-size: 0.7rem;
222
- font-weight: 600;
226
+ .headingLinkIcon {
227
+ appearance: none;
228
+ margin-left: 0.35em;
229
+ border: 0;
230
+ padding: 0;
231
+ background: transparent;
232
+ color: currentColor;
233
+ cursor: pointer;
234
+ font-size: 0.7em;
235
+ line-height: 1;
236
+ opacity: 0;
237
+ transition: opacity 150ms ease;
223
238
  vertical-align: middle;
224
239
  }
225
240
 
241
+ .content
242
+ article
243
+ :where(h1, h2, h3, h4, h5, h6)[id]:is(:hover, :focus-within)
244
+ .headingLinkIcon {
245
+ opacity: 0.55;
246
+ }
247
+
248
+ .content
249
+ article
250
+ :where(h1, h2, h3, h4, h5, h6)[id]
251
+ .headingLinkIcon:is(:hover, :focus-visible) {
252
+ opacity: 1;
253
+ }
254
+
255
+ .content
256
+ article
257
+ :where(h1, h2, h3, h4, h5, h6)[id]
258
+ .headingLinkIcon.headingLinkCopied {
259
+ color: var(--ld-color-accent);
260
+ opacity: 1;
261
+ }
262
+
226
263
  .content article :where(h2, h3).sectionHighlight {
227
264
  animation: section-highlight 2.4s ease-out;
228
265
  border-radius: 6px;
@@ -248,6 +285,7 @@ html.is-animating .transition-scale {
248
285
 
249
286
  .content article p {
250
287
  margin-block: 1.25em;
288
+ line-height: 1.9;
251
289
  }
252
290
 
253
291
  .content img {
@@ -261,7 +299,7 @@ html.is-animating .transition-scale {
261
299
  }
262
300
 
263
301
  .groupBreadcrumbs {
264
- margin-block-end: -4px !important;
302
+ margin-block-end: 0px !important;
265
303
  color: var(--ld-color-accent);
266
304
  font-size: 80%;
267
305
  font-weight: 700;
@@ -269,6 +307,7 @@ html.is-animating .transition-scale {
269
307
 
270
308
  .content article li {
271
309
  margin-block: 1em;
310
+ line-height: 1.9;
272
311
  }
273
312
 
274
313
  .content article > div > ul {
@@ -294,7 +333,7 @@ html.is-animating .transition-scale {
294
333
 
295
334
  .content article h1 + blockquote {
296
335
  margin: 0 0 1.5em;
297
- margin-top: -1.1em;
336
+ margin-top: -0.6rem;
298
337
  padding: 0;
299
338
  border-left: 0;
300
339
  border-radius: 0;
@@ -308,6 +347,10 @@ html.is-animating .transition-scale {
308
347
  line-height: 1.45;
309
348
  }
310
349
 
350
+ .content article h1 + blockquote > p {
351
+ line-height: 1.6;
352
+ }
353
+
311
354
  .content blockquote > :first-child {
312
355
  margin-top: 0;
313
356
  }
@@ -365,13 +408,24 @@ html.is-animating .transition-scale {
365
408
 
366
409
  .navList a,
367
410
  .navDisclosure summary {
368
- padding-left: 7px;
369
- border-left: 1px solid
370
- color-mix(in srgb, var(--ld-color-border) 65%, transparent);
411
+ position: relative;
412
+ padding-left: 9px;
371
413
  color: var(--ld-color-muted-text);
372
414
  text-decoration: none;
373
415
  }
374
416
 
417
+ .navList a::before,
418
+ .navDisclosure summary::before {
419
+ position: absolute;
420
+ top: -1px;
421
+ bottom: -1px;
422
+ left: 0;
423
+ width: 2px;
424
+ border-radius: 1px;
425
+ background: color-mix(in srgb, var(--ld-color-border) 65%, transparent);
426
+ content: "";
427
+ }
428
+
375
429
  .navDisclosure summary {
376
430
  display: flex;
377
431
  align-items: center;
@@ -410,11 +464,15 @@ html.is-animating .transition-scale {
410
464
 
411
465
  .navList a.active,
412
466
  .navDisclosure summary.active {
413
- border-left-color: var(--ld-color-accent);
414
467
  color: var(--ld-color-accent);
415
468
  font-weight: 550;
416
469
  }
417
470
 
471
+ .navList a.active::before,
472
+ .navDisclosure summary.active::before {
473
+ background: var(--ld-color-accent);
474
+ }
475
+
418
476
  .content pre {
419
477
  position: relative;
420
478
  overflow: auto;
@@ -741,16 +799,64 @@ html.mermaidFullscreenOpen {
741
799
  font-weight: 700;
742
800
  }
743
801
 
744
- .toc ul {
802
+ .tocTitle {
803
+ display: flex;
804
+ align-items: center;
805
+ gap: 7px;
806
+ }
807
+
808
+ .tocLinks {
809
+ position: relative;
810
+ margin-block: 1em;
811
+ margin-left: 5px;
812
+ }
813
+
814
+ .tocLinks ul {
745
815
  display: grid;
746
816
  gap: 9px;
817
+ margin: 0;
747
818
  list-style: none;
748
- padding-left: 0;
819
+ padding-left: 16px;
820
+ }
821
+
822
+ .tocRail,
823
+ .tocVisibility {
824
+ position: absolute;
825
+ top: 0;
826
+ left: 0;
827
+ width: 2px;
828
+ border-radius: 999px;
829
+ pointer-events: none;
830
+ }
831
+
832
+ .tocRail {
833
+ bottom: 0;
834
+ background: var(--ld-color-border);
835
+ }
836
+
837
+ .tocVisibility {
838
+ top: var(--ld-toc-visibility-top, 0);
839
+ z-index: 1;
840
+ height: var(--ld-toc-visibility-height, 0);
841
+ background: var(--ld-color-accent);
842
+ opacity: 0;
843
+ transition:
844
+ top 180ms var(--ld-navigation-easing),
845
+ height 180ms var(--ld-navigation-easing),
846
+ opacity 120ms ease;
847
+ }
848
+
849
+ .tocVisibility.isVisible {
850
+ opacity: 1;
749
851
  }
750
852
 
751
853
  .toc a {
854
+ display: block;
855
+ overflow: hidden;
752
856
  color: var(--ld-color-muted-text);
753
857
  text-decoration: none;
858
+ text-overflow: ellipsis;
859
+ white-space: nowrap;
754
860
  }
755
861
 
756
862
  .tocDepth3 {
@@ -762,8 +868,8 @@ html.mermaidFullscreenOpen {
762
868
  width: 20px;
763
869
  height: 20px;
764
870
  background: currentColor;
765
- mask: var(--ld-repo-icon) center / contain no-repeat;
766
- -webkit-mask: var(--ld-repo-icon) center / contain no-repeat;
871
+ mask: url("./github-icon.svg") center / contain no-repeat;
872
+ -webkit-mask: url("./github-icon.svg") center / contain no-repeat;
767
873
  }
768
874
 
769
875
  .searchBox {
@@ -941,6 +1047,10 @@ html.mermaidFullscreenOpen {
941
1047
  text-decoration: none;
942
1048
  }
943
1049
 
1050
+ .tocRepoLink:first-child {
1051
+ border-top: 0;
1052
+ }
1053
+
944
1054
  .tocRepoLink:hover,
945
1055
  .tocRepoLink:focus-visible {
946
1056
  color: var(--ld-color-link);
@@ -980,19 +1090,102 @@ html.mermaidFullscreenOpen {
980
1090
  }
981
1091
  }
982
1092
 
1093
+ @media (prefers-reduced-motion: reduce) {
1094
+ .tocVisibility {
1095
+ transition: none;
1096
+ }
1097
+ }
1098
+
983
1099
  @media (max-width: 860px) {
1100
+ html.sidebar-menu-open,
1101
+ html.sidebar-menu-open body {
1102
+ overflow: hidden;
1103
+ }
1104
+
984
1105
  .pageShell {
985
1106
  display: block;
986
1107
  }
987
1108
 
1109
+ .content {
1110
+ padding-block-start: 25px;
1111
+ }
1112
+
988
1113
  .sidebar {
989
- position: static;
1114
+ position: sticky;
1115
+ top: 0;
1116
+ z-index: 30;
990
1117
  height: auto;
991
1118
  max-height: none;
1119
+ overflow: visible;
1120
+ padding: 0;
992
1121
  border-bottom: 1px solid var(--ld-color-border);
993
1122
  border-right: 0;
994
1123
  }
995
1124
 
1125
+ .sidebarContents {
1126
+ display: block;
1127
+ }
1128
+
1129
+ .sidebarHeader {
1130
+ min-height: 76px;
1131
+ padding: 20px;
1132
+ background: var(--ld-color-sidebar-background);
1133
+ }
1134
+
1135
+ .sidebarCollapseIcon {
1136
+ display: none;
1137
+ }
1138
+
1139
+ .sidebarMenuIcon {
1140
+ display: block;
1141
+ }
1142
+
1143
+ .sidebarContents > .searchBox,
1144
+ #lildocs-sidebar-navigation {
1145
+ display: none;
1146
+ }
1147
+
1148
+ html.sidebar-menu-open .sidebar {
1149
+ position: fixed;
1150
+ inset: 0;
1151
+ width: 100%;
1152
+ height: 100dvh;
1153
+ max-height: 100dvh;
1154
+ overflow: auto;
1155
+ overscroll-behavior: contain;
1156
+ border-bottom: 0;
1157
+ }
1158
+
1159
+ html.sidebar-menu-open .sidebarHeader {
1160
+ position: sticky;
1161
+ top: 0;
1162
+ z-index: 1;
1163
+ border-bottom: 1px solid var(--ld-color-border);
1164
+ }
1165
+
1166
+ html.sidebar-menu-open .sidebarContents > .searchBox {
1167
+ display: block;
1168
+ margin: 20px;
1169
+ }
1170
+
1171
+ html.sidebar-menu-open #lildocs-sidebar-navigation {
1172
+ display: block;
1173
+ padding: 0 20px 32px;
1174
+ }
1175
+
1176
+ html.sidebar-menu-open .sidebarRepoLink {
1177
+ display: flex;
1178
+ align-items: center;
1179
+ gap: 7px;
1180
+ margin: 0 20px 32px;
1181
+ padding-top: 16px;
1182
+ border-top: 1px solid var(--ld-color-border);
1183
+ color: var(--ld-color-text);
1184
+ font-size: 0.8rem;
1185
+ font-weight: 600;
1186
+ text-decoration: none;
1187
+ }
1188
+
996
1189
  .searchResults {
997
1190
  position: absolute;
998
1191
  top: calc(100% + 8px);
@@ -1009,8 +1202,6 @@ html.mermaidFullscreenOpen {
1009
1202
  }
1010
1203
 
1011
1204
  .toc {
1012
- position: static;
1013
- max-height: none;
1014
- border-bottom: 1px solid var(--ld-color-border);
1205
+ display: none;
1015
1206
  }
1016
1207
  }
@@ -17,6 +17,18 @@
17
17
  --ld-tabler-icon: url("data:image/svg+xml,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20width%3D%2224%22%20height%3D%2224%22%20viewBox%3D%220%200%2024%2024%22%20fill%3D%22none%22%20stroke%3D%22black%22%20stroke-width%3D%222%22%20stroke-linecap%3D%22round%22%20stroke-linejoin%3D%22round%22%20%3E%3Cpath%20stroke%3D%22none%22%20d%3D%22M0%200h24v24H0z%22%20fill%3D%22none%22%20%2F%3E%3Cpath%20d%3D%22M3%2010a7%207%200%201%200%2014%200a7%207%200%201%200%20-14%200%22%20%2F%3E%3Cpath%20d%3D%22M21%2021l-6%20-6%22%20%2F%3E%3C%2Fsvg%3E");
18
18
  }
19
19
 
20
+ .ti-link {
21
+ --ld-tabler-icon: url("data:image/svg+xml,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20width%3D%2224%22%20height%3D%2224%22%20viewBox%3D%220%200%2024%2024%22%20fill%3D%22none%22%20stroke%3D%22black%22%20stroke-width%3D%222%22%20stroke-linecap%3D%22round%22%20stroke-linejoin%3D%22round%22%3E%3Cpath%20d%3D%22M9%2015l6%20-6%22%2F%3E%3Cpath%20d%3D%22M11%206l.463%20-.536a5%205%200%200%201%207.071%207.072l-.534%20.464%22%2F%3E%3Cpath%20d%3D%22M13%2018l-.397%20.534a5.068%205.068%200%200%201%20-7.127%200a4.972%204.972%200%200%201%200%20-7.071l.524%20-.463%22%2F%3E%3C%2Fsvg%3E");
22
+ }
23
+
24
+ .ti-check {
25
+ --ld-tabler-icon: url("data:image/svg+xml,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20width%3D%2224%22%20height%3D%2224%22%20viewBox%3D%220%200%2024%2024%22%20fill%3D%22none%22%20stroke%3D%22black%22%20stroke-width%3D%222%22%20stroke-linecap%3D%22round%22%20stroke-linejoin%3D%22round%22%3E%3Cpath%20d%3D%22M5%2012l5%205l10%20-10%22%2F%3E%3C%2Fsvg%3E");
26
+ }
27
+
28
+ .ti-align-left {
29
+ --ld-tabler-icon: url("data:image/svg+xml,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20width%3D%2224%22%20height%3D%2224%22%20viewBox%3D%220%200%2024%2024%22%20fill%3D%22none%22%20stroke%3D%22black%22%20stroke-width%3D%222%22%20stroke-linecap%3D%22round%22%20stroke-linejoin%3D%22round%22%3E%3Cpath%20d%3D%22M4%206l16%200%22%2F%3E%3Cpath%20d%3D%22M4%2012l10%200%22%2F%3E%3Cpath%20d%3D%22M4%2018l14%200%22%2F%3E%3C%2Fsvg%3E");
30
+ }
31
+
20
32
  .ti-arrows-maximize {
21
33
  --ld-tabler-icon: url("data:image/svg+xml,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20width%3D%2224%22%20height%3D%2224%22%20viewBox%3D%220%200%2024%2024%22%20fill%3D%22none%22%20stroke%3D%22black%22%20stroke-width%3D%222%22%20stroke-linecap%3D%22round%22%20stroke-linejoin%3D%22round%22%3E%3Cpath%20d%3D%22M16%204l4%200l0%204%22%2F%3E%3Cpath%20d%3D%22M14%2010l6%20-6%22%2F%3E%3Cpath%20d%3D%22M8%2020l-4%200l0%20-4%22%2F%3E%3Cpath%20d%3D%22M4%2020l6%20-6%22%2F%3E%3Cpath%20d%3D%22M16%2020l4%200l0%20-4%22%2F%3E%3Cpath%20d%3D%22M14%2014l6%206%22%2F%3E%3Cpath%20d%3D%22M8%204l-4%200l0%204%22%2F%3E%3Cpath%20d%3D%22M4%204l6%206%22%2F%3E%3C%2Fsvg%3E");
22
34
  }
@@ -33,6 +45,10 @@
33
45
  --ld-tabler-icon: url("data:image/svg+xml,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20width%3D%2224%22%20height%3D%2224%22%20viewBox%3D%220%200%2024%2024%22%20fill%3D%22none%22%20stroke%3D%22black%22%20stroke-width%3D%222%22%20stroke-linecap%3D%22round%22%20stroke-linejoin%3D%22round%22%3E%3Cpath%20stroke%3D%22none%22%20d%3D%22M0%200h24v24H0z%22%20fill%3D%22none%22%2F%3E%3Cpath%20d%3D%22M4%206a2%202%200%200%201%202%20-2h12a2%202%200%200%201%202%202v12a2%202%200%200%201%20-2%202h-12a2%202%200%200%201%20-2%20-2l0%20-12%22%2F%3E%3Cpath%20d%3D%22M9%204v16%22%2F%3E%3Cpath%20d%3D%22M14%2010l2%202l-2%202%22%2F%3E%3C%2Fsvg%3E");
34
46
  }
35
47
 
48
+ .ti-menu-2 {
49
+ --ld-tabler-icon: url("data:image/svg+xml,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20width%3D%2224%22%20height%3D%2224%22%20viewBox%3D%220%200%2024%2024%22%20fill%3D%22none%22%20stroke%3D%22black%22%20stroke-width%3D%222%22%20stroke-linecap%3D%22round%22%20stroke-linejoin%3D%22round%22%3E%3Cpath%20stroke%3D%22none%22%20d%3D%22M0%200h24v24H0z%22%20fill%3D%22none%22%2F%3E%3Cpath%20d%3D%22M4%206l16%200%22%2F%3E%3Cpath%20d%3D%22M4%2012l16%200%22%2F%3E%3Cpath%20d%3D%22M4%2018l16%200%22%2F%3E%3C%2Fsvg%3E");
50
+ }
51
+
36
52
  .ti-copy {
37
53
  --ld-tabler-icon: url("data:image/svg+xml,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20width%3D%2224%22%20height%3D%2224%22%20viewBox%3D%220%200%2024%2024%22%20fill%3D%22none%22%20stroke%3D%22black%22%20stroke-width%3D%222%22%20stroke-linecap%3D%22round%22%20stroke-linejoin%3D%22round%22%20%3E%3Cpath%20stroke%3D%22none%22%20d%3D%22M0%200h24v24H0z%22%20fill%3D%22none%22%20%2F%3E%3Cpath%20d%3D%22M7%209.667a2.667%202.667%200%200%201%202.667%20-2.667h8.666a2.667%202.667%200%200%201%202.667%202.667v8.666a2.667%202.667%200%200%201%20-2.667%202.667h-8.666a2.667%202.667%200%200%201%20-2.667%20-2.667l0%20-8.666%22%20%2F%3E%3Cpath%20d%3D%22M4.012%2016.737a2.005%202.005%200%200%201%20-1.012%20-1.737v-10c0%20-1.1%20.9%20-2%202%20-2h10c.75%200%201.158%20.385%201.5%201%22%20%2F%3E%3C%2Fsvg%3E");
38
54
  }
@@ -0,0 +1,78 @@
1
+ let animationFrame: number | undefined;
2
+
3
+ export function initTocVisibility() {
4
+ window.addEventListener("scroll", scheduleTocVisibilityUpdate, {
5
+ passive: true,
6
+ });
7
+ window.addEventListener("resize", scheduleTocVisibilityUpdate);
8
+ document.addEventListener("lildocs:page-view", scheduleTocVisibilityUpdate);
9
+ void document.fonts?.ready.then(scheduleTocVisibilityUpdate);
10
+ scheduleTocVisibilityUpdate();
11
+ }
12
+
13
+ function scheduleTocVisibilityUpdate() {
14
+ if (animationFrame !== undefined) {
15
+ return;
16
+ }
17
+
18
+ animationFrame = window.requestAnimationFrame(() => {
19
+ animationFrame = undefined;
20
+ updateTocVisibility();
21
+ });
22
+ }
23
+
24
+ function updateTocVisibility() {
25
+ const container = document.querySelector<HTMLElement>("[data-toc-links]");
26
+ const indicator = container?.querySelector<HTMLElement>(
27
+ "[data-toc-visibility]",
28
+ );
29
+ if (!container || !indicator) {
30
+ return;
31
+ }
32
+
33
+ const items = Array.from(
34
+ container.querySelectorAll<HTMLAnchorElement>('a[href^="#"]'),
35
+ )
36
+ .map((link) => {
37
+ const heading = headingForLink(link);
38
+ return heading ? { heading, link } : undefined;
39
+ })
40
+ .filter((item): item is { heading: HTMLElement; link: HTMLAnchorElement } =>
41
+ Boolean(item),
42
+ );
43
+ const visibleItems = items.filter(({ heading }) => {
44
+ const bounds = heading.getBoundingClientRect();
45
+ return bounds.bottom > 0 && bounds.top < window.innerHeight;
46
+ });
47
+ if (visibleItems.length === 0 || visibleItems.length === items.length) {
48
+ indicator.classList.remove("isVisible");
49
+ return;
50
+ }
51
+
52
+ const containerBounds = container.getBoundingClientRect();
53
+ const firstBounds = visibleItems[0].link.getBoundingClientRect();
54
+ const lastBounds =
55
+ visibleItems.at(-1)?.link.getBoundingClientRect() ?? firstBounds;
56
+ indicator.style.setProperty(
57
+ "--ld-toc-visibility-top",
58
+ `${firstBounds.top - containerBounds.top}px`,
59
+ );
60
+ indicator.style.setProperty(
61
+ "--ld-toc-visibility-height",
62
+ `${lastBounds.bottom - firstBounds.top}px`,
63
+ );
64
+ indicator.classList.add("isVisible");
65
+ }
66
+
67
+ function headingForLink(link: HTMLAnchorElement): HTMLElement | null {
68
+ const id = link.hash.slice(1);
69
+ if (!id) {
70
+ return null;
71
+ }
72
+
73
+ try {
74
+ return document.getElementById(decodeURIComponent(id));
75
+ } catch {
76
+ return document.getElementById(id);
77
+ }
78
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lildocs",
3
- "version": "0.1.19",
3
+ "version": "0.1.21",
4
4
  "description": "A lightweight CLI that turns Markdown docs into a static searchable documentation site.",
5
5
  "homepage": "https://aleclarson.github.io/lildocs/",
6
6
  "repository": {
@@ -28,7 +28,7 @@
28
28
  "cmd-ts": "^0.15.0",
29
29
  "culori": "^4.0.2",
30
30
  "entities": "^8.0.0",
31
- "exports-md": "^0.2.5",
31
+ "exports-md": "^0.2.7",
32
32
  "gray-matter": "^4.0.3",
33
33
  "marked": "^18.0.3",
34
34
  "marked-shiki": "^1.2.1",