@abreen/tada 1.0.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.
Files changed (125) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +290 -0
  3. package/bin/tada.js +361 -0
  4. package/config/authors.json +1 -0
  5. package/config/nav.json +28 -0
  6. package/content/index.md +19 -0
  7. package/content/lectures/01/Pair.java.md +296 -0
  8. package/content/lectures/01/Rectangle.java +80 -0
  9. package/content/lectures/01/demo.py +9 -0
  10. package/content/lectures/01/index.md +39 -0
  11. package/content/lectures/01/lecture1.pdf +0 -0
  12. package/content/lectures/index.md +25 -0
  13. package/content/markdown.md +379 -0
  14. package/content/problem_sets/index.md +6 -0
  15. package/fonts/google-sans-code/GoogleSansCodeVariable-Italic.ttf +0 -0
  16. package/fonts/google-sans-code/GoogleSansCodeVariable.ttf +0 -0
  17. package/fonts/google-sans-code/LICENSE.txt +93 -0
  18. package/fonts/inter/InterVariable-Italic.ttf +0 -0
  19. package/fonts/inter/InterVariable.ttf +0 -0
  20. package/fonts/inter/LICENSE.txt +92 -0
  21. package/package.json +70 -0
  22. package/public/avatars/alex.jpg +0 -0
  23. package/public/test.txt +1 -0
  24. package/src/_mixins.scss +4 -0
  25. package/src/anchor/README.md +6 -0
  26. package/src/anchor/index.ts +34 -0
  27. package/src/anchor/style.scss +48 -0
  28. package/src/code/README.md +5 -0
  29. package/src/code/index.ts +113 -0
  30. package/src/code/style.scss +101 -0
  31. package/src/code.scss +54 -0
  32. package/src/header/README.md +8 -0
  33. package/src/header/index.ts +43 -0
  34. package/src/header/style.scss +228 -0
  35. package/src/index.ts +73 -0
  36. package/src/layout.scss +144 -0
  37. package/src/literate/style.scss +60 -0
  38. package/src/print/README.md +4 -0
  39. package/src/print/index.ts +32 -0
  40. package/src/print/style.scss +82 -0
  41. package/src/question/README.md +3 -0
  42. package/src/question/index.ts +25 -0
  43. package/src/question/style.scss +116 -0
  44. package/src/search/README.md +6 -0
  45. package/src/search/index.ts +574 -0
  46. package/src/search/style.scss +217 -0
  47. package/src/style.scss +815 -0
  48. package/src/timezone/index.test.ts +100 -0
  49. package/src/timezone/index.ts +298 -0
  50. package/src/timezone/style.scss +16 -0
  51. package/src/timezone/timezones.json +58 -0
  52. package/src/toc/README.md +3 -0
  53. package/src/toc/index.ts +322 -0
  54. package/src/toc/style.scss +203 -0
  55. package/src/top/README.md +4 -0
  56. package/src/top/index.ts +75 -0
  57. package/src/util.ts +122 -0
  58. package/templates/_author.html +27 -0
  59. package/templates/_bottom.html +3 -0
  60. package/templates/_download.html +1 -0
  61. package/templates/_heading.html +19 -0
  62. package/templates/_nav.html +18 -0
  63. package/templates/_theme.scss +97 -0
  64. package/templates/_top.html +87 -0
  65. package/templates/authors.schema.json +13 -0
  66. package/templates/code.html +31 -0
  67. package/templates/default.html +13 -0
  68. package/templates/literate.html +16 -0
  69. package/templates/nav.schema.json +27 -0
  70. package/tsconfig.json +15 -0
  71. package/types/dev.ts +3 -0
  72. package/types/sass.d.ts +1 -0
  73. package/types/site-variables.d.ts +16 -0
  74. package/webpack/apply-base-path-plugin.js +78 -0
  75. package/webpack/build-state.js +97 -0
  76. package/webpack/code.test.js +162 -0
  77. package/webpack/colors.js +15 -0
  78. package/webpack/config.base.js +147 -0
  79. package/webpack/config.dev.js +23 -0
  80. package/webpack/config.prod.js +32 -0
  81. package/webpack/content-watch-plugin.js +153 -0
  82. package/webpack/deflist-id-plugin.js +62 -0
  83. package/webpack/external-links-plugin.js +37 -0
  84. package/webpack/features.js +5 -0
  85. package/webpack/flair.json +1 -0
  86. package/webpack/generate-content-assets-plugin.js +308 -0
  87. package/webpack/generate-favicon-plugin.js +198 -0
  88. package/webpack/generate-fonts-plugin.js +69 -0
  89. package/webpack/generate-manifest-plugin.js +116 -0
  90. package/webpack/globals.js +74 -0
  91. package/webpack/heading-subtitle-plugin.js +80 -0
  92. package/webpack/json-schema.js +19 -0
  93. package/webpack/log.js +143 -0
  94. package/webpack/markdown-plugins.test.js +203 -0
  95. package/webpack/pagefind-plugin.js +379 -0
  96. package/webpack/pagefind-plugin.test.js +131 -0
  97. package/webpack/pdf-text.js +163 -0
  98. package/webpack/print-flair-plugin.js +22 -0
  99. package/webpack/reachability.js +273 -0
  100. package/webpack/reachability.test.js +80 -0
  101. package/webpack/serve.js +104 -0
  102. package/webpack/site-variables.js +53 -0
  103. package/webpack/site.schema.json +67 -0
  104. package/webpack/templates.js +128 -0
  105. package/webpack/text-to-id.js +8 -0
  106. package/webpack/toc-plugin.js +167 -0
  107. package/webpack/util.js +49 -0
  108. package/webpack/utils/code.js +439 -0
  109. package/webpack/utils/content-files.js +147 -0
  110. package/webpack/utils/define-plugin.js +20 -0
  111. package/webpack/utils/file-types.js +26 -0
  112. package/webpack/utils/front-matter.js +57 -0
  113. package/webpack/utils/jdi-runner/LiterateRunner.class +0 -0
  114. package/webpack/utils/jdi-runner/LiterateRunner.java +241 -0
  115. package/webpack/utils/literate-java.js +153 -0
  116. package/webpack/utils/markdown.js +244 -0
  117. package/webpack/utils/parse-hsl.js +8 -0
  118. package/webpack/utils/paths.js +58 -0
  119. package/webpack/utils/render.js +466 -0
  120. package/webpack/utils/shiki-highlighter.js +26 -0
  121. package/webpack/validate-internal-links-plugin.js +155 -0
  122. package/webpack/watch-reachability-state.js +273 -0
  123. package/webpack/watch-reachability-state.test.js +198 -0
  124. package/webpack/watch-reload-client.js +54 -0
  125. package/webpack/watch.js +166 -0
@@ -0,0 +1,34 @@
1
+ function getElements(parent: HTMLElement): HTMLHeadingElement[] {
2
+ return Array.from(parent.querySelectorAll('h1, h2, h3, h4, h5, h6'));
3
+ }
4
+
5
+ export default (window: Window) => {
6
+ const elements = getElements(window.document.body);
7
+ if (elements == null) {
8
+ return;
9
+ }
10
+
11
+ elements.forEach(el => {
12
+ if (!el.id) {
13
+ return;
14
+ }
15
+
16
+ const link = window.document.createElement('a');
17
+ link.className = 'heading-anchor';
18
+ link.href = `#${el.id}`;
19
+ link.title = 'Link to this heading';
20
+ link.setAttribute('aria-label', 'Link to this heading');
21
+
22
+ // Move all existing child nodes into the link
23
+ while (el.firstChild) {
24
+ link.appendChild(el.firstChild);
25
+ }
26
+ el.appendChild(link);
27
+
28
+ link.addEventListener('click', () => {
29
+ el.focus();
30
+ });
31
+ });
32
+
33
+ return () => {};
34
+ };
@@ -0,0 +1,48 @@
1
+ @import '../_mixins.scss';
2
+
3
+ .heading-anchor {
4
+ position: relative;
5
+ display: inline-block;
6
+ color: var(--fg-color);
7
+ text-decoration: none;
8
+ }
9
+
10
+ .heading-anchor::after {
11
+ content: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path fill="hsl(20 8% 8%)" fill-rule="evenodd" clip-rule="evenodd" d="M10.975 14.51a1.05 1.05 0 0 0 0-1.485 2.95 2.95 0 0 1 0-4.172l3.536-3.535a2.95 2.95 0 1 1 4.172 4.172l-1.093 1.092a1.05 1.05 0 0 0 1.485 1.485l1.093-1.092a5.05 5.05 0 0 0-7.142-7.142L9.49 7.368a5.05 5.05 0 0 0 0 7.142c.41.41 1.075.41 1.485 0zm2.05-5.02a1.05 1.05 0 0 0 0 1.485 2.95 2.95 0 0 1 0 4.172l-3.5 3.5a2.95 2.95 0 1 1-4.171-4.172l1.025-1.025a1.05 1.05 0 0 0-1.485-1.485L3.87 12.99a5.05 5.05 0 0 0 7.142 7.142l3.5-3.5a5.05 5.05 0 0 0 0-7.142 1.05 1.05 0 0 0-1.485 0z"/></svg>');
12
+ display: inline-block;
13
+ height: 0.75em;
14
+ width: 0.75em;
15
+ margin-left: 4px;
16
+ opacity: 0;
17
+ }
18
+
19
+ @media (prefers-color-scheme: dark) {
20
+ .heading-anchor::after {
21
+ content: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path fill="hsl(20 20% 90%)" fill-rule="evenodd" clip-rule="evenodd" d="M10.975 14.51a1.05 1.05 0 0 0 0-1.485 2.95 2.95 0 0 1 0-4.172l3.536-3.535a2.95 2.95 0 1 1 4.172 4.172l-1.093 1.092a1.05 1.05 0 0 0 1.485 1.485l1.093-1.092a5.05 5.05 0 0 0-7.142-7.142L9.49 7.368a5.05 5.05 0 0 0 0 7.142c.41.41 1.075.41 1.485 0zm2.05-5.02a1.05 1.05 0 0 0 0 1.485 2.95 2.95 0 0 1 0 4.172l-3.5 3.5a2.95 2.95 0 1 1-4.171-4.172l1.025-1.025a1.05 1.05 0 0 0-1.485-1.485L3.87 12.99a5.05 5.05 0 0 0 7.142 7.142l3.5-3.5a5.05 5.05 0 0 0 0-7.142 1.05 1.05 0 0 0-1.485 0z"/></svg>');
22
+ }
23
+ }
24
+
25
+ .heading-anchor:focus::after,
26
+ .heading-anchor:focus-visible::after {
27
+ opacity: 1;
28
+ }
29
+
30
+ @media (hover: hover) {
31
+ h1:hover .heading-anchor::after,
32
+ h2:hover .heading-anchor::after,
33
+ h3:hover .heading-anchor::after,
34
+ h4:hover .heading-anchor::after,
35
+ h5:hover .heading-anchor::after,
36
+ h6:hover .heading-anchor::after {
37
+ opacity: 1;
38
+ }
39
+
40
+ h1:hover .heading-anchor,
41
+ h2:hover .heading-anchor,
42
+ h3:hover .heading-anchor,
43
+ h4:hover .heading-anchor,
44
+ h5:hover .heading-anchor,
45
+ h6:hover .heading-anchor {
46
+ color: var(--fg-color);
47
+ }
48
+ }
@@ -0,0 +1,5 @@
1
+ # The `code` component
2
+
3
+ At build time, Markdown comments in Java (`///`) are rendered to HTML,
4
+ but with the original source code hidden in a data attribute, allowing
5
+ client-side JavaScript to restore them when a selection is copied.
@@ -0,0 +1,113 @@
1
+ export default async function mount(window: Window): Promise<void> {
2
+ const { document } = window;
3
+ if (!document.body.classList.contains('code')) {
4
+ return;
5
+ }
6
+
7
+ document.addEventListener('copy', (e: ClipboardEvent) => {
8
+ const selection = window.getSelection();
9
+ if (!selection || selection.rangeCount === 0) {
10
+ return;
11
+ }
12
+
13
+ const fragment = selection.getRangeAt(0).cloneContents();
14
+ const proseEls = fragment.querySelectorAll('[data-prose-source]');
15
+ if (proseEls.length === 0) {
16
+ return;
17
+ }
18
+
19
+ proseEls.forEach(el => {
20
+ const pre = document.createElement('pre');
21
+ pre.textContent = el.getAttribute('data-prose-source');
22
+ el.replaceWith(pre);
23
+ });
24
+
25
+ fragment.querySelectorAll('.line-number').forEach(el => el.remove());
26
+
27
+ const lines: string[] = [];
28
+ const container = fragment.querySelector('.code-body');
29
+ const topNodes = container ? container.childNodes : fragment.childNodes;
30
+ topNodes.forEach(node => {
31
+ if (node.nodeType === Node.TEXT_NODE) {
32
+ const text = node.textContent ?? '';
33
+ if (text.trim()) {
34
+ lines.push(text);
35
+ }
36
+ } else if (node instanceof Element) {
37
+ const codeRows = node.querySelectorAll('.code-row');
38
+ if (codeRows.length > 0) {
39
+ codeRows.forEach(row => {
40
+ const codeEl = row.querySelector('code');
41
+ if (codeEl) {
42
+ const text = codeEl.textContent ?? '';
43
+ lines.push(text === '\u00A0' ? '' : text);
44
+ }
45
+ });
46
+ } else {
47
+ lines.push(node.textContent ?? '');
48
+ }
49
+ }
50
+ });
51
+
52
+ e.clipboardData!.setData('text/plain', lines.join('\n'));
53
+ e.preventDefault();
54
+ });
55
+
56
+ const downloadLink = document.querySelector<HTMLAnchorElement>(
57
+ '.file-header a[download]',
58
+ );
59
+ if (downloadLink && 'showSaveFilePicker' in window) {
60
+ downloadLink.addEventListener('click', async (e: MouseEvent) => {
61
+ e.preventDefault();
62
+ try {
63
+ const handle = await (window as any).showSaveFilePicker({
64
+ suggestedName: downloadLink.download,
65
+ });
66
+ const response = await fetch(downloadLink.href);
67
+ const writable = await handle.createWritable();
68
+ await writable.write(await response.blob());
69
+ await writable.close();
70
+ } catch (err: any) {
71
+ if (err.name !== 'AbortError') {
72
+ throw err;
73
+ }
74
+ }
75
+ });
76
+ }
77
+
78
+ const codeBody = document.querySelector<HTMLElement>('.code-body');
79
+ const scrollbar = document.querySelector<HTMLElement>('.code-scrollbar');
80
+ if (!codeBody || !scrollbar) {
81
+ return;
82
+ }
83
+
84
+ const inner = scrollbar.firstElementChild as HTMLElement;
85
+ let syncing = false;
86
+
87
+ function updateScrollbar(): void {
88
+ const hasOverflow = codeBody!.scrollWidth > codeBody!.clientWidth;
89
+ scrollbar!.style.display = hasOverflow ? '' : 'none';
90
+ inner.style.width = codeBody!.scrollWidth + 'px';
91
+ }
92
+
93
+ codeBody.addEventListener('scroll', () => {
94
+ if (syncing) {
95
+ return;
96
+ }
97
+ syncing = true;
98
+ scrollbar!.scrollLeft = codeBody!.scrollLeft;
99
+ syncing = false;
100
+ });
101
+
102
+ scrollbar.addEventListener('scroll', () => {
103
+ if (syncing) {
104
+ return;
105
+ }
106
+ syncing = true;
107
+ codeBody!.scrollLeft = scrollbar!.scrollLeft;
108
+ syncing = false;
109
+ });
110
+
111
+ new ResizeObserver(updateScrollbar).observe(codeBody);
112
+ updateScrollbar();
113
+ }
@@ -0,0 +1,101 @@
1
+ body.code .file-header {
2
+ h1 {
3
+ margin: 0;
4
+ padding: 0;
5
+ font-size: inherit;
6
+ font-weight: inherit;
7
+ }
8
+ }
9
+
10
+ body.code .code-body {
11
+ background: var(--bg2-color);
12
+ border-radius: var(--border-radius);
13
+ display: grid;
14
+ grid-template-columns: minmax(100%, max-content);
15
+ overflow-x: auto;
16
+ overscroll-behavior-x: none;
17
+ scrollbar-width: none;
18
+ &::-webkit-scrollbar {
19
+ display: none;
20
+ }
21
+ -webkit-text-size-adjust: 100%;
22
+ text-size-adjust: 100%;
23
+ }
24
+
25
+ body.code .code-scrollbar {
26
+ position: sticky;
27
+ bottom: 0;
28
+ overflow-x: auto;
29
+ overscroll-behavior-x: none;
30
+ z-index: 10;
31
+
32
+ > div {
33
+ height: 1px;
34
+ }
35
+ }
36
+
37
+ body.code .code-body > pre {
38
+ margin: 0;
39
+ }
40
+
41
+ body.code pre {
42
+ display: block;
43
+ padding: 0;
44
+ overflow: visible;
45
+ }
46
+
47
+ body.code pre code {
48
+ padding-right: var(--gap);
49
+ }
50
+
51
+ body.code .code-prose {
52
+ background-color: var(--bg2-color);
53
+ display: grid;
54
+ grid-template-columns: var(--code-gutter-width) 1fr;
55
+
56
+ .code-prose-gutter {
57
+ position: sticky;
58
+ left: 0;
59
+ z-index: 1;
60
+ background: var(--bg2-color);
61
+ border-right: 1px solid var(--fg2-color);
62
+ margin-right: var(--gap);
63
+ }
64
+
65
+ .code-prose-content {
66
+ color: var(--fg2-color);
67
+ min-width: 0;
68
+ position: relative;
69
+ padding: var(--gap) var(--gap) var(--gap)
70
+ calc(var(--prose-indent, 0ch) - 0.2em);
71
+
72
+ *:last-of-type {
73
+ margin-bottom: 0;
74
+ }
75
+
76
+ ol,
77
+ ul,
78
+ li {
79
+ margin: 0;
80
+ }
81
+
82
+ dfn {
83
+ color: inherit;
84
+ }
85
+
86
+ code {
87
+ background: inherit;
88
+ padding: inherit;
89
+ }
90
+ }
91
+ }
92
+
93
+ body.code .code-search-index {
94
+ position: absolute;
95
+ width: 1px;
96
+ height: 1px;
97
+ overflow: hidden;
98
+ clip: rect(0, 0, 0, 0);
99
+ clip-path: inset(50%);
100
+ white-space: nowrap;
101
+ }
package/src/code.scss ADDED
@@ -0,0 +1,54 @@
1
+ @import './_mixins.scss';
2
+
3
+ body.code,
4
+ body.literate {
5
+ --code-gutter-width: 4rem;
6
+ }
7
+
8
+ .file-header {
9
+ display: flex;
10
+ align-items: center;
11
+ justify-content: center;
12
+ gap: var(--gap);
13
+ border: 1px solid var(--fg2-color);
14
+ border-radius: var(--border-radius);
15
+ padding: var(--gap);
16
+ margin-bottom: var(--gap);
17
+
18
+ tt,
19
+ code {
20
+ background: none;
21
+ padding: 0;
22
+ }
23
+ }
24
+
25
+ .code-row {
26
+ display: grid;
27
+ grid-template-columns: var(--code-gutter-width) 1fr;
28
+ }
29
+
30
+ .line-number {
31
+ cursor: pointer;
32
+ display: block;
33
+ padding: 0 0.75em 0 0.75em;
34
+ text-align: right;
35
+ @include user-select(none);
36
+ color: var(--fg2-color);
37
+ font-family: var(--mono-font);
38
+ text-decoration: none;
39
+ position: sticky;
40
+ left: 0;
41
+ z-index: 1;
42
+ background: var(--bg2-color);
43
+ border-right: 1px solid var(--fg2-color);
44
+ margin-right: var(--gap);
45
+
46
+ &:hover {
47
+ color: var(--fg-color);
48
+ }
49
+
50
+ &:target {
51
+ background: var(--fg2-color);
52
+ color: var(--bg-color);
53
+ }
54
+ }
@@ -0,0 +1,8 @@
1
+ # The `header` component
2
+
3
+ Use the [Web Animations API](https://developer.mozilla.org/en-US/docs/Web/API/Web_Animations_API)
4
+ to animate the `<details>` element in the header.
5
+
6
+ This component doesn't provide any other functionality related to the header.
7
+ If JavaScript is turned off in the web browser, the `<details>` element is still
8
+ functional but with no animation.
@@ -0,0 +1,43 @@
1
+ import { getElement } from '../util';
2
+
3
+ export default (window: Window) => {
4
+ const header: HTMLElement = getElement(window.document, 'header');
5
+ const details = getElement(header, 'details') as HTMLDetailsElement;
6
+ const summary = getElement(details, 'summary') as HTMLElement;
7
+
8
+ function close() {
9
+ details.open = false;
10
+ }
11
+
12
+ function handleDetailsClick(e: MouseEvent) {
13
+ if (details.open) {
14
+ e.stopPropagation();
15
+ }
16
+ }
17
+ details.addEventListener('click', handleDetailsClick);
18
+
19
+ function handleWindowClick() {
20
+ if (details.open) {
21
+ close();
22
+ }
23
+ }
24
+ window.addEventListener('click', handleWindowClick);
25
+
26
+ function handleWindowKeyDown(e: KeyboardEvent) {
27
+ if (
28
+ e.key === 'Escape' &&
29
+ details.open &&
30
+ details.contains(document.activeElement)
31
+ ) {
32
+ close();
33
+ summary.focus();
34
+ }
35
+ }
36
+ window.addEventListener('keydown', handleWindowKeyDown);
37
+
38
+ return () => {
39
+ window.removeEventListener('keydown', handleWindowKeyDown);
40
+ window.removeEventListener('click', handleWindowClick);
41
+ details.removeEventListener('click', handleDetailsClick);
42
+ };
43
+ };
@@ -0,0 +1,228 @@
1
+ @import '../_mixins.scss';
2
+
3
+ :root {
4
+ --header-collapsed-height: 45px;
5
+ }
6
+
7
+ header {
8
+ backdrop-filter: var(--backdrop-filter);
9
+ background: var(--bg-color-translucent);
10
+
11
+ width: 100%;
12
+ position: fixed;
13
+ top: 0;
14
+
15
+ z-index: 1000;
16
+
17
+ display: grid;
18
+ grid-template-columns: 1fr;
19
+
20
+ view-transition-name: site-header;
21
+ }
22
+
23
+ header details summary {
24
+ display: flex;
25
+ align-items: center;
26
+ height: var(--header-collapsed-height);
27
+ padding: 0;
28
+ list-style-type: none;
29
+ cursor: pointer;
30
+ position: relative;
31
+ container-type: inline-size;
32
+
33
+ @media (hover: hover) {
34
+ &:hover {
35
+ color: inherit;
36
+ }
37
+ }
38
+
39
+ &:focus-visible {
40
+ outline-offset: -2px;
41
+ }
42
+ }
43
+
44
+ header nav {
45
+ display: flex;
46
+ flex-wrap: wrap;
47
+ @include user-select(none);
48
+ gap: var(--gap);
49
+ margin: var(--gap);
50
+ }
51
+
52
+ header nav div {
53
+ flex: 1 1 150px;
54
+ }
55
+
56
+ header nav ul {
57
+ list-style-type: none;
58
+ padding: 0;
59
+ margin: 8px 0 0 0;
60
+ }
61
+
62
+ header nav ul li {
63
+ margin: 0;
64
+ }
65
+
66
+ header nav a {
67
+ border: none;
68
+ display: inline;
69
+ color: var(--fg-color);
70
+ text-decoration: none;
71
+ }
72
+
73
+ @media (hover: hover) {
74
+ header nav a {
75
+ &:hover {
76
+ color: var(--fg2-color);
77
+ }
78
+
79
+ &:active {
80
+ color: var(--link-active-color);
81
+ }
82
+ }
83
+ }
84
+
85
+ header nav a.external {
86
+ border: none;
87
+ }
88
+
89
+ @media only screen and (min-width: 740px) {
90
+ header details nav {
91
+ min-height: 100px;
92
+ }
93
+ }
94
+
95
+ header nav div > p {
96
+ font-style: italic;
97
+ @include user-select(none);
98
+ border-bottom: 1px solid var(--fg-color);
99
+ padding-bottom: 2px;
100
+ }
101
+
102
+ header details summary::-webkit-details-marker {
103
+ display: none;
104
+ }
105
+
106
+ header details summary::marker {
107
+ content: '';
108
+ }
109
+
110
+ header summary::after {
111
+ order: -1;
112
+ height: 24px;
113
+ content: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24'><polyline points='9 18 15 12 9 6' stroke='hsl(20deg 8% 8%)' stroke-width='2' fill='none' stroke-linecap='round' stroke-linejoin='round'/></svg>");
114
+ margin-left: var(--gap);
115
+ margin-right: 8px;
116
+ }
117
+
118
+ @media (prefers-color-scheme: dark) {
119
+ header summary::after {
120
+ content: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24'><polyline points='9 18 15 12 9 6' stroke='hsl(20deg 20% 90%)' stroke-width='2' fill='none' stroke-linecap='round' stroke-linejoin='round'/></svg>");
121
+ }
122
+ }
123
+
124
+ @media (hover: hover) {
125
+ header details summary:hover:not(:has(.results-container:hover)) {
126
+ background: var(--bg2-color);
127
+ }
128
+
129
+ header:has(details[open]):hover {
130
+ background: var(--bg-color-translucent);
131
+ }
132
+ }
133
+
134
+ header details[open] summary::after {
135
+ transform: rotate(90deg);
136
+ }
137
+
138
+ header .content {
139
+ position: relative;
140
+ }
141
+
142
+ header summary .logo {
143
+ flex: 0 0 auto;
144
+ color: #fff;
145
+ background: var(--theme-color);
146
+ padding: 1px 6px;
147
+ margin: 0 8px 0 0;
148
+ font-size: var(--font-size-smaller);
149
+ font-weight: 600;
150
+ pointer-events: none;
151
+ }
152
+
153
+ header summary .site-title {
154
+ flex: 1 1 0;
155
+ overflow: hidden;
156
+ text-overflow: ellipsis;
157
+ white-space: nowrap;
158
+ min-width: 0;
159
+ font-weight: 500;
160
+ pointer-events: none;
161
+ margin: 0 8px 0 0;
162
+ }
163
+
164
+ @container (max-width: 520px) {
165
+ header summary .site-title {
166
+ display: none;
167
+ }
168
+ }
169
+
170
+ header summary .to-top-container {
171
+ display: none;
172
+ align-items: center;
173
+ height: var(--header-collapsed-height);
174
+ pointer-events: none;
175
+ white-space: nowrap;
176
+
177
+ &:has(.is-visible) {
178
+ display: flex;
179
+ pointer-events: auto;
180
+ }
181
+ }
182
+
183
+ header summary .search-controls {
184
+ margin-left: auto;
185
+ pointer-events: auto;
186
+ display: flex;
187
+ align-items: center;
188
+ height: var(--header-collapsed-height);
189
+ padding: 0 var(--gap);
190
+ min-width: 0;
191
+
192
+ input.search {
193
+ width: 180px;
194
+
195
+ @container (max-width: 280px) {
196
+ display: none;
197
+ }
198
+ }
199
+ }
200
+
201
+ header .results-container {
202
+ position: absolute;
203
+ top: var(--header-collapsed-height);
204
+ left: 0;
205
+ right: 0;
206
+ box-sizing: border-box;
207
+ max-width: 640px;
208
+ margin: 0 auto;
209
+ padding: var(--gap);
210
+ pointer-events: none;
211
+ z-index: 1000;
212
+
213
+ @container (max-width: 260px) {
214
+ display: none;
215
+ }
216
+ }
217
+
218
+ header ol {
219
+ padding: 0;
220
+ flex: 1;
221
+ min-height: 0;
222
+ overflow-y: auto;
223
+
224
+ .excerpt {
225
+ line-height: 2.5ex;
226
+ height: 5ex;
227
+ }
228
+ }
package/src/index.ts ADDED
@@ -0,0 +1,73 @@
1
+ import './style.scss';
2
+ import './layout.scss';
3
+
4
+ import './anchor/style.scss';
5
+ import './code.scss';
6
+ import './code/style.scss';
7
+ import './literate/style.scss';
8
+ import './header/style.scss';
9
+ import './print/style.scss';
10
+ import './question/style.scss';
11
+ import './search/style.scss';
12
+ import './timezone/style.scss';
13
+ import './toc/style.scss';
14
+
15
+ import mountTableOfContents from './toc';
16
+ import mountSearch from './search';
17
+ import mountHeader from './header';
18
+ import mountPrint from './print';
19
+ import mountTop from './top';
20
+ import mountAnchor from './anchor';
21
+ import mountQuestion from './question';
22
+ import mountTimezone from './timezone';
23
+ import mountCode from './code';
24
+
25
+ import { scheduleTask, formatDuration } from './util';
26
+
27
+ const COMPONENTS = {
28
+ toc: mountTableOfContents,
29
+ search: mountSearch,
30
+ header: mountHeader,
31
+ print: mountPrint,
32
+ top: mountTop,
33
+ anchor: mountAnchor,
34
+ question: mountQuestion,
35
+ timezone: mountTimezone,
36
+ code: mountCode,
37
+ };
38
+
39
+ let startTime = -1;
40
+
41
+ document.addEventListener('DOMContentLoaded', async () => {
42
+ startTime = window.performance.now();
43
+
44
+ const entries = Object.entries(COMPONENTS);
45
+
46
+ const failed: Record<string, any> = {};
47
+
48
+ const mountPromises = entries.map(([name, mount]) => {
49
+ return new Promise<void>((resolve, reject) => {
50
+ scheduleTask(async () => {
51
+ try {
52
+ mount(window);
53
+ resolve();
54
+ return;
55
+ } catch (err) {
56
+ failed[name] = String(err);
57
+ }
58
+ reject();
59
+ });
60
+ });
61
+ });
62
+
63
+ await Promise.allSettled(mountPromises);
64
+
65
+ for (const [name, reason] of Object.entries(failed)) {
66
+ console.error(`Failed to mount ${name} component:`, reason);
67
+ }
68
+
69
+ if (window.IS_DEV) {
70
+ const diff = window.performance.now() - startTime;
71
+ console.info(`Components mounted in ${formatDuration(diff)}`);
72
+ }
73
+ });