@abreen/tada 1.5.2 → 1.5.5
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/build/apply-base-path-plugin.ts +15 -0
- package/build/globals.ts +1 -1
- package/build/utils/markdown.ts +3 -0
- package/build/utils/render.ts +2 -0
- package/build/validate-internal-links-plugin.ts +9 -0
- package/init/public/Test.java +9 -0
- package/package.json +1 -1
- package/src/code/index.ts +5 -4
- package/src/index.ts +1 -1
- package/src/navigate/index.ts +27 -21
- package/src/navigate/lifecycle.ts +1 -1
- package/src/navigate/style.scss +2 -2
- package/src/search/index.ts +19 -16
- package/src/toc/index.ts +1 -1
- package/src/top/index.ts +3 -3
- package/src/util.ts +7 -6
- package/templates/_nav.html +1 -0
- package/fonts/google-sans-code/ttf/GoogleSansCodeVariable-Italic.ttf +0 -0
- package/fonts/google-sans-code/ttf/GoogleSansCodeVariable.ttf +0 -0
- package/fonts/inter/ttf/InterVariable-Italic.ttf +0 -0
|
@@ -11,6 +11,7 @@ const log = makeLogger(import.meta.url);
|
|
|
11
11
|
interface ApplyBasePathOptions {
|
|
12
12
|
literateJavaOutputPaths?: Set<string>;
|
|
13
13
|
sourceUrlPath?: string;
|
|
14
|
+
validTargets?: Set<string>;
|
|
14
15
|
}
|
|
15
16
|
|
|
16
17
|
export default function applyBasePathPlugin(
|
|
@@ -22,6 +23,7 @@ export default function applyBasePathPlugin(
|
|
|
22
23
|
const rewriteCodeLinks = isFeatureEnabled(siteVariables, 'code');
|
|
23
24
|
const literateJavaOutputPaths = pluginOptions.literateJavaOutputPaths;
|
|
24
25
|
const sourceUrlPath = pluginOptions.sourceUrlPath;
|
|
26
|
+
const validTargets = pluginOptions.validTargets;
|
|
25
27
|
|
|
26
28
|
function rewriteInternalHref(href: string): string {
|
|
27
29
|
const match = href.match(/^([^?#]*)(.*)$/);
|
|
@@ -43,6 +45,19 @@ export default function applyBasePathPlugin(
|
|
|
43
45
|
break;
|
|
44
46
|
}
|
|
45
47
|
}
|
|
48
|
+
// Only rewrite if the .html version exists. Public files with
|
|
49
|
+
// code extensions are copied as-is and have no .html page.
|
|
50
|
+
if (validTargets && sourceUrlPath) {
|
|
51
|
+
const resolved = modifiedPath.startsWith('/')
|
|
52
|
+
? modifiedPath
|
|
53
|
+
: path.posix.join(
|
|
54
|
+
path.posix.dirname(sourceUrlPath),
|
|
55
|
+
modifiedPath,
|
|
56
|
+
);
|
|
57
|
+
if (!validTargets.has(`${resolved}.html`)) {
|
|
58
|
+
break;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
46
61
|
modifiedPath += '.html';
|
|
47
62
|
break;
|
|
48
63
|
}
|
package/build/globals.ts
CHANGED
|
@@ -85,7 +85,7 @@ function readableDate(date: string | Date | null | undefined): string {
|
|
|
85
85
|
return `${months[month]} ${day}, ${year}`;
|
|
86
86
|
}
|
|
87
87
|
|
|
88
|
-
function classNames(obj: Record<string, unknown>): string {
|
|
88
|
+
export function classNames(obj: Record<string, unknown>): string {
|
|
89
89
|
const names: string[] = [];
|
|
90
90
|
for (const key in obj) {
|
|
91
91
|
if (obj[key]) {
|
package/build/utils/markdown.ts
CHANGED
|
@@ -25,6 +25,7 @@ interface CreateMarkdownOptions {
|
|
|
25
25
|
validatorOptions?: Record<string, unknown>;
|
|
26
26
|
literateJavaOutputPaths?: Set<string>;
|
|
27
27
|
sourceUrlPath?: string;
|
|
28
|
+
validTargets?: Set<string>;
|
|
28
29
|
}
|
|
29
30
|
|
|
30
31
|
function capitalize(str: string): string {
|
|
@@ -43,6 +44,7 @@ export function createMarkdown(
|
|
|
43
44
|
validatorOptions = {},
|
|
44
45
|
literateJavaOutputPaths,
|
|
45
46
|
sourceUrlPath,
|
|
47
|
+
validTargets,
|
|
46
48
|
} = options;
|
|
47
49
|
const markdown = new MarkdownIt({ html: true, typographer: true })
|
|
48
50
|
.use(headingSubtitlePlugin)
|
|
@@ -55,6 +57,7 @@ export function createMarkdown(
|
|
|
55
57
|
.use(applyBasePathPlugin, siteVariables, {
|
|
56
58
|
literateJavaOutputPaths,
|
|
57
59
|
sourceUrlPath,
|
|
60
|
+
validTargets,
|
|
58
61
|
})
|
|
59
62
|
.use(tocPlugin)
|
|
60
63
|
.use(columnsPlugin)
|
package/build/utils/render.ts
CHANGED
|
@@ -377,6 +377,7 @@ function renderPlainTextContent(
|
|
|
377
377
|
},
|
|
378
378
|
literateJavaOutputPaths,
|
|
379
379
|
sourceUrlPath,
|
|
380
|
+
validTargets: validInternalTargets,
|
|
380
381
|
});
|
|
381
382
|
|
|
382
383
|
const ext = path.extname(filePath);
|
|
@@ -559,6 +560,7 @@ export function renderLiterateJavaPageAsset({
|
|
|
559
560
|
},
|
|
560
561
|
literateJavaOutputPaths,
|
|
561
562
|
sourceUrlPath,
|
|
563
|
+
validTargets: validInternalTargets,
|
|
562
564
|
});
|
|
563
565
|
let fenceIndex = 0;
|
|
564
566
|
const defaultFence = md.renderer.rules.fence!;
|
|
@@ -150,6 +150,15 @@ export default function validateInternalLinks(
|
|
|
150
150
|
}
|
|
151
151
|
|
|
152
152
|
if (!validTargets!.has(resolvedPath)) {
|
|
153
|
+
// If code extension rewriting changed the path, check the original
|
|
154
|
+
// path too. Public files with code extensions are copied as-is and
|
|
155
|
+
// do not produce .html versions.
|
|
156
|
+
if (codeExtPattern) {
|
|
157
|
+
const unrewritten = resolveLinkPath(sourceUrlPath!, href, null);
|
|
158
|
+
if (unrewritten && validTargets!.has(unrewritten)) {
|
|
159
|
+
return;
|
|
160
|
+
}
|
|
161
|
+
}
|
|
153
162
|
reportBrokenLink(rawHref, resolvedPath);
|
|
154
163
|
}
|
|
155
164
|
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/// A source code file which is simply copied into the built site.
|
|
2
|
+
/// Tada will not process this file: it won't create an HTML file for it
|
|
3
|
+
/// (even if `features.code` is `true`) and it won't appear in search results
|
|
4
|
+
/// (even if `features.search` is `true`).
|
|
5
|
+
public class Test {
|
|
6
|
+
public static void main(String[] args) {
|
|
7
|
+
System.out.println("hello");
|
|
8
|
+
}
|
|
9
|
+
}
|
package/package.json
CHANGED
package/src/code/index.ts
CHANGED
|
@@ -28,15 +28,16 @@ export default async function mount(window: Window): Promise<void> {
|
|
|
28
28
|
const container = fragment.querySelector('.code-body');
|
|
29
29
|
const topNodes = container ? container.childNodes : fragment.childNodes;
|
|
30
30
|
topNodes.forEach(node => {
|
|
31
|
-
if (node.nodeType === Node.TEXT_NODE) {
|
|
31
|
+
if (node.nodeType === 3 /* Node.TEXT_NODE */) {
|
|
32
32
|
const text = node.textContent ?? '';
|
|
33
33
|
if (text.trim()) {
|
|
34
34
|
lines.push(text);
|
|
35
35
|
}
|
|
36
|
-
} else if (
|
|
37
|
-
const
|
|
36
|
+
} else if ('querySelectorAll' in node) {
|
|
37
|
+
const el = node as Element;
|
|
38
|
+
const codeRows = el.querySelectorAll('.code-row');
|
|
38
39
|
if (codeRows.length > 0) {
|
|
39
|
-
codeRows.forEach(row => {
|
|
40
|
+
codeRows.forEach((row: Element) => {
|
|
40
41
|
const codeEl = row.querySelector('code');
|
|
41
42
|
if (codeEl) {
|
|
42
43
|
const text = codeEl.textContent ?? '';
|
package/src/index.ts
CHANGED
|
@@ -42,7 +42,7 @@ document.addEventListener('DOMContentLoaded', async () => {
|
|
|
42
42
|
const persistentEntries = Object.entries(PERSISTENT_COMPONENTS);
|
|
43
43
|
const persistentPromises = persistentEntries.map(([name, mount]) => {
|
|
44
44
|
return new Promise<void>((resolve, reject) => {
|
|
45
|
-
scheduleTask(async () => {
|
|
45
|
+
scheduleTask(window, async () => {
|
|
46
46
|
try {
|
|
47
47
|
mount(window);
|
|
48
48
|
resolve();
|
package/src/navigate/index.ts
CHANGED
|
@@ -78,7 +78,8 @@ function swapContent(newDoc: Document): void {
|
|
|
78
78
|
const newContainer = newDoc.querySelector('.container');
|
|
79
79
|
const oldContainer = document.querySelector('.container');
|
|
80
80
|
if (newContainer && oldContainer) {
|
|
81
|
-
|
|
81
|
+
const imported = document.importNode(newContainer, true);
|
|
82
|
+
oldContainer.replaceChildren(...imported.childNodes);
|
|
82
83
|
}
|
|
83
84
|
|
|
84
85
|
// Update body class (carries template name and toc-is-active)
|
|
@@ -146,7 +147,7 @@ async function performNavigation(
|
|
|
146
147
|
updateHead(newDoc);
|
|
147
148
|
if (isPush) {
|
|
148
149
|
historyIndex++;
|
|
149
|
-
history.pushState({ navIndex: historyIndex }, '', url);
|
|
150
|
+
window.history.pushState({ navIndex: historyIndex }, '', url);
|
|
150
151
|
}
|
|
151
152
|
const parsed = new URL(url);
|
|
152
153
|
currentPath = parsed.pathname + parsed.search;
|
|
@@ -177,7 +178,7 @@ async function performNavigation(
|
|
|
177
178
|
const headerHeight =
|
|
178
179
|
document.querySelector('header')?.getBoundingClientRect().height ?? 0;
|
|
179
180
|
const titleVisible =
|
|
180
|
-
titleEl != null && titleEl.getBoundingClientRect().
|
|
181
|
+
titleEl != null && titleEl.getBoundingClientRect().top >= headerHeight;
|
|
181
182
|
|
|
182
183
|
if (titleVisible) {
|
|
183
184
|
const h1 = titleEl.querySelector('h1') as HTMLElement | null;
|
|
@@ -202,24 +203,29 @@ async function performNavigation(
|
|
|
202
203
|
|
|
203
204
|
const transition = doc.startViewTransition(() => {
|
|
204
205
|
doSwap();
|
|
205
|
-
//
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
206
|
+
// Check title visibility on the new page independently (scroll
|
|
207
|
+
// has been restored by doSwap, so the position reflects the
|
|
208
|
+
// destination page)
|
|
209
|
+
const newTitleEl = document.querySelector('.title-and-info');
|
|
210
|
+
const newHeaderHeight =
|
|
211
|
+
document.querySelector('header')?.getBoundingClientRect().height ?? 0;
|
|
212
|
+
const newTitleVisible =
|
|
213
|
+
newTitleEl != null &&
|
|
214
|
+
newTitleEl.getBoundingClientRect().top >= newHeaderHeight;
|
|
215
|
+
if (newTitleVisible && newTitleEl) {
|
|
216
|
+
const h1 = newTitleEl.querySelector('h1') as HTMLElement | null;
|
|
217
|
+
const info = newTitleEl.querySelector('.info') as HTMLElement | null;
|
|
218
|
+
const breadcrumb = newTitleEl.querySelector(
|
|
219
|
+
'a.breadcrumb',
|
|
220
|
+
) as HTMLElement | null;
|
|
221
|
+
if (h1) {
|
|
222
|
+
h1.style.viewTransitionName = 'page-title';
|
|
223
|
+
}
|
|
224
|
+
if (info) {
|
|
225
|
+
info.style.viewTransitionName = 'page-info';
|
|
226
|
+
}
|
|
227
|
+
if (breadcrumb) {
|
|
228
|
+
breadcrumb.style.viewTransitionName = 'page-breadcrumb';
|
|
223
229
|
}
|
|
224
230
|
}
|
|
225
231
|
});
|
|
@@ -31,7 +31,7 @@ export async function mountPerPageComponents(
|
|
|
31
31
|
|
|
32
32
|
const mountPromises = entries.map(([name, mount]) => {
|
|
33
33
|
return new Promise<void>((resolve, reject) => {
|
|
34
|
-
scheduleTask(async () => {
|
|
34
|
+
scheduleTask(window, async () => {
|
|
35
35
|
try {
|
|
36
36
|
const cleanup = await mount(window);
|
|
37
37
|
if (typeof cleanup === 'function') {
|
package/src/navigate/style.scss
CHANGED
package/src/search/index.ts
CHANGED
|
@@ -106,6 +106,8 @@ function render(
|
|
|
106
106
|
state: State,
|
|
107
107
|
loading = false,
|
|
108
108
|
) {
|
|
109
|
+
const doc = resultsContainer.ownerDocument;
|
|
110
|
+
|
|
109
111
|
if (state.showResults) {
|
|
110
112
|
resultsContainer.removeAttribute('inert');
|
|
111
113
|
}
|
|
@@ -114,12 +116,12 @@ function render(
|
|
|
114
116
|
'.results',
|
|
115
117
|
) as HTMLElement | null;
|
|
116
118
|
if (!resultsDiv) {
|
|
117
|
-
resultsDiv =
|
|
119
|
+
resultsDiv = doc.createElement('div');
|
|
118
120
|
resultsDiv.className = 'results';
|
|
119
121
|
resultsContainer.appendChild(resultsDiv);
|
|
120
122
|
}
|
|
121
123
|
|
|
122
|
-
const ol =
|
|
124
|
+
const ol = doc.createElement('ol');
|
|
123
125
|
ol.id = `${input.name}-results`;
|
|
124
126
|
ol.role = 'listbox';
|
|
125
127
|
ol.tabIndex = -1;
|
|
@@ -128,29 +130,29 @@ function render(
|
|
|
128
130
|
const totalVisible = Math.min(state.results.length, MAX_RESULTS);
|
|
129
131
|
|
|
130
132
|
state.results.slice(0, MAX_RESULTS).forEach((result, i) => {
|
|
131
|
-
const a =
|
|
133
|
+
const a = doc.createElement('a');
|
|
132
134
|
a.id = `result-${i}`;
|
|
133
135
|
a.className = 'result';
|
|
134
136
|
a.href = result.url;
|
|
135
137
|
a.tabIndex = 0;
|
|
136
138
|
|
|
137
|
-
const titleEl =
|
|
139
|
+
const titleEl = doc.createElement('div');
|
|
138
140
|
titleEl.id = `title-${i}`;
|
|
139
141
|
titleEl.className = 'title';
|
|
140
142
|
titleEl.textContent = result.title;
|
|
141
143
|
a.appendChild(titleEl);
|
|
142
144
|
|
|
143
|
-
const subtitle =
|
|
145
|
+
const subtitle = doc.createElement('div');
|
|
144
146
|
subtitle.className = 'subtitle';
|
|
145
147
|
subtitle.innerText = result.url;
|
|
146
148
|
a.appendChild(subtitle);
|
|
147
149
|
|
|
148
|
-
const excerpt =
|
|
150
|
+
const excerpt = doc.createElement('div');
|
|
149
151
|
excerpt.className = 'excerpt';
|
|
150
152
|
excerpt.innerHTML = result.excerpt;
|
|
151
153
|
a.appendChild(excerpt);
|
|
152
154
|
|
|
153
|
-
const li =
|
|
155
|
+
const li = doc.createElement('li');
|
|
154
156
|
li.id = `option-${i}`;
|
|
155
157
|
li.setAttribute('role', 'option');
|
|
156
158
|
li.setAttribute('aria-selected', 'false');
|
|
@@ -161,26 +163,26 @@ function render(
|
|
|
161
163
|
|
|
162
164
|
const subsToShow = result.subResults.slice(0, 5);
|
|
163
165
|
if (subsToShow.length > 0) {
|
|
164
|
-
const subList =
|
|
166
|
+
const subList = doc.createElement('ul');
|
|
165
167
|
subList.className = 'sub-results';
|
|
166
168
|
for (const sub of subsToShow) {
|
|
167
|
-
const subA =
|
|
169
|
+
const subA = doc.createElement('a');
|
|
168
170
|
subA.href = sub.url;
|
|
169
171
|
subA.className = 'sub-result';
|
|
170
172
|
|
|
171
|
-
const subTitle =
|
|
173
|
+
const subTitle = doc.createElement('div');
|
|
172
174
|
subTitle.className = 'title';
|
|
173
175
|
subTitle.textContent = sub.title;
|
|
174
176
|
subA.appendChild(subTitle);
|
|
175
177
|
|
|
176
178
|
if (sub.excerpt) {
|
|
177
|
-
const subExcerpt =
|
|
179
|
+
const subExcerpt = doc.createElement('div');
|
|
178
180
|
subExcerpt.className = 'excerpt';
|
|
179
181
|
subExcerpt.innerHTML = sub.excerpt;
|
|
180
182
|
subA.appendChild(subExcerpt);
|
|
181
183
|
}
|
|
182
184
|
|
|
183
|
-
const subLi =
|
|
185
|
+
const subLi = doc.createElement('li');
|
|
184
186
|
subLi.appendChild(subA);
|
|
185
187
|
subList.appendChild(subLi);
|
|
186
188
|
}
|
|
@@ -196,12 +198,12 @@ function render(
|
|
|
196
198
|
'.results-info',
|
|
197
199
|
) as HTMLSpanElement | null;
|
|
198
200
|
if (!infoSpan) {
|
|
199
|
-
infoSpan =
|
|
201
|
+
infoSpan = doc.createElement('span');
|
|
200
202
|
infoSpan.className = 'results-info';
|
|
201
203
|
|
|
202
|
-
const hint =
|
|
204
|
+
const hint = doc.createElement('span');
|
|
203
205
|
hint.className = 'search-hint';
|
|
204
|
-
const kbd1 =
|
|
206
|
+
const kbd1 = doc.createElement('kbd');
|
|
205
207
|
kbd1.textContent = '/';
|
|
206
208
|
hint.append('Press\u00a0', kbd1, '\u00a0to search');
|
|
207
209
|
infoSpan.appendChild(hint);
|
|
@@ -213,7 +215,7 @@ function render(
|
|
|
213
215
|
'.results-count',
|
|
214
216
|
) as HTMLSpanElement | null;
|
|
215
217
|
if (!countSpan) {
|
|
216
|
-
countSpan =
|
|
218
|
+
countSpan = doc.createElement('span');
|
|
217
219
|
countSpan.className = 'results-count';
|
|
218
220
|
infoSpan.insertBefore(countSpan, infoSpan.firstChild);
|
|
219
221
|
}
|
|
@@ -248,6 +250,7 @@ function render(
|
|
|
248
250
|
}
|
|
249
251
|
|
|
250
252
|
export default (window: Window) => {
|
|
253
|
+
const { document } = window;
|
|
251
254
|
const input = document.querySelector(
|
|
252
255
|
'input.quick-search',
|
|
253
256
|
) as HTMLInputElement | null;
|
package/src/toc/index.ts
CHANGED
|
@@ -270,7 +270,7 @@ export default (window: Window) => {
|
|
|
270
270
|
scrollIfNeeded(nextItem, window.document);
|
|
271
271
|
}
|
|
272
272
|
}
|
|
273
|
-
const debounced = debounce(handleScroll, LATENCY_MS);
|
|
273
|
+
const debounced = debounce(window, handleScroll, LATENCY_MS);
|
|
274
274
|
window.addEventListener('scroll', debounced, { passive: true });
|
|
275
275
|
|
|
276
276
|
// Call after load to set current item
|
package/src/top/index.ts
CHANGED
|
@@ -30,9 +30,9 @@ export default (window: Window) => {
|
|
|
30
30
|
e.preventDefault();
|
|
31
31
|
const cleanUrl = window.location.pathname + window.location.search;
|
|
32
32
|
if (window.location.hash) {
|
|
33
|
-
history.pushState(null, '', cleanUrl);
|
|
33
|
+
window.history.pushState(null, '', cleanUrl);
|
|
34
34
|
} else {
|
|
35
|
-
history.replaceState(null, '', cleanUrl);
|
|
35
|
+
window.history.replaceState(null, '', cleanUrl);
|
|
36
36
|
}
|
|
37
37
|
window.scrollTo({ top: 0, behavior: 'smooth' });
|
|
38
38
|
const toc = window.document.querySelector('nav.toc') as HTMLElement | null;
|
|
@@ -65,7 +65,7 @@ export default (window: Window) => {
|
|
|
65
65
|
}
|
|
66
66
|
}
|
|
67
67
|
|
|
68
|
-
const debounced = debounce(updateVisibility, LATENCY_MS);
|
|
68
|
+
const debounced = debounce(window, updateVisibility, LATENCY_MS);
|
|
69
69
|
window.addEventListener('scroll', debounced, { passive: true });
|
|
70
70
|
updateVisibility();
|
|
71
71
|
|
package/src/util.ts
CHANGED
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
export function debounce<T extends (...args: unknown[]) => void>(
|
|
2
|
+
win: Window,
|
|
2
3
|
fn: T,
|
|
3
4
|
time: number,
|
|
4
5
|
) {
|
|
5
6
|
let timer: number;
|
|
6
7
|
return (...args: Parameters<T>) => {
|
|
7
|
-
|
|
8
|
-
timer =
|
|
8
|
+
win.clearTimeout(timer);
|
|
9
|
+
timer = win.setTimeout(() => {
|
|
9
10
|
fn(...args);
|
|
10
11
|
}, time);
|
|
11
12
|
};
|
|
@@ -41,11 +42,11 @@ export function applyBasePath(subPath: string): string {
|
|
|
41
42
|
return path + subPath;
|
|
42
43
|
}
|
|
43
44
|
|
|
44
|
-
export function scheduleTask(fn: () => void) {
|
|
45
|
-
if (typeof
|
|
46
|
-
|
|
45
|
+
export function scheduleTask(win: Window, fn: () => void) {
|
|
46
|
+
if (typeof win.requestIdleCallback === 'function') {
|
|
47
|
+
win.requestIdleCallback(fn);
|
|
47
48
|
} else {
|
|
48
|
-
return setTimeout(fn, 0);
|
|
49
|
+
return win.setTimeout(fn, 0);
|
|
49
50
|
}
|
|
50
51
|
}
|
|
51
52
|
|
package/templates/_nav.html
CHANGED
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
<a
|
|
9
9
|
class="<%= cx({disabled: link.disabled, external: link.external}) %>"
|
|
10
10
|
<% if (!link.disabled) { %>href="<%= link.internal ? applyBasePath(link.internal) : link.external %>"<% } %>
|
|
11
|
+
<% if (link.external) { %>target="_blank"<% } %>
|
|
11
12
|
>
|
|
12
13
|
<%= link.text %></a>
|
|
13
14
|
</li>
|
|
Binary file
|
|
Binary file
|
|
Binary file
|