@sprig-and-prose/tutorial-svelte 0.2.1 → 0.2.3
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/package.json
CHANGED
package/src/kit/Tutorial.svelte
CHANGED
|
@@ -37,6 +37,7 @@
|
|
|
37
37
|
|
|
38
38
|
let tutorialPageElement: HTMLDivElement | null = null;
|
|
39
39
|
let experientialPrevious: string | null = null;
|
|
40
|
+
let experientialNext: string | null = null;
|
|
40
41
|
|
|
41
42
|
function getCurrentPath(): string {
|
|
42
43
|
if (typeof window === 'undefined') return '';
|
|
@@ -56,6 +57,47 @@
|
|
|
56
57
|
return normalized.startsWith(normalizedBase + '/') || normalized === normalizedBase;
|
|
57
58
|
}
|
|
58
59
|
|
|
60
|
+
function extractInternalTutorialLinks(html: string, routeBase: string): string[] {
|
|
61
|
+
if (typeof window === 'undefined' || !routeBase) return [];
|
|
62
|
+
|
|
63
|
+
try {
|
|
64
|
+
const parser = new DOMParser();
|
|
65
|
+
const doc = parser.parseFromString(html, 'text/html');
|
|
66
|
+
const anchors = doc.querySelectorAll('a');
|
|
67
|
+
const links: string[] = [];
|
|
68
|
+
const currentUrl = new URL(window.location.href);
|
|
69
|
+
|
|
70
|
+
for (const anchor of anchors) {
|
|
71
|
+
const href = anchor.getAttribute('href');
|
|
72
|
+
if (!href) continue;
|
|
73
|
+
|
|
74
|
+
if (anchor.hasAttribute('download')) continue;
|
|
75
|
+
if (anchor.getAttribute('target') === '_blank') continue;
|
|
76
|
+
|
|
77
|
+
try {
|
|
78
|
+
const targetUrl = new URL(href, currentUrl);
|
|
79
|
+
|
|
80
|
+
if (targetUrl.origin !== currentUrl.origin) continue;
|
|
81
|
+
|
|
82
|
+
const targetPath = normalizePath(targetUrl.pathname + targetUrl.search + targetUrl.hash);
|
|
83
|
+
const currentPath = normalizePath(currentUrl.pathname + currentUrl.search + currentUrl.hash);
|
|
84
|
+
|
|
85
|
+
if (targetPath === currentPath && targetUrl.hash) continue;
|
|
86
|
+
|
|
87
|
+
if (isInternalTutorialPath(targetPath, routeBase)) {
|
|
88
|
+
links.push(targetPath);
|
|
89
|
+
}
|
|
90
|
+
} catch {
|
|
91
|
+
continue;
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
return links;
|
|
96
|
+
} catch {
|
|
97
|
+
return [];
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
59
101
|
function handleClickCapture(event: MouseEvent, routeBase: string): void {
|
|
60
102
|
if (typeof window === 'undefined') return;
|
|
61
103
|
|
|
@@ -66,14 +108,27 @@
|
|
|
66
108
|
const anchor = (event.target as HTMLElement)?.closest('a');
|
|
67
109
|
if (!anchor) return;
|
|
68
110
|
|
|
69
|
-
if (anchor.closest('.tutorial-nav')) return;
|
|
70
|
-
|
|
71
111
|
const href = anchor.getAttribute('href');
|
|
72
112
|
if (!href) return;
|
|
73
113
|
|
|
74
114
|
if (anchor.hasAttribute('download')) return;
|
|
75
115
|
if (anchor.getAttribute('target') === '_blank') return;
|
|
76
116
|
|
|
117
|
+
const isInNav = anchor.closest('.tutorial-nav');
|
|
118
|
+
if (isInNav) {
|
|
119
|
+
try {
|
|
120
|
+
const currentUrl = new URL(window.location.href);
|
|
121
|
+
const targetUrl = new URL(href, currentUrl);
|
|
122
|
+
const targetPath = normalizePath(targetUrl.pathname + targetUrl.search + targetUrl.hash);
|
|
123
|
+
|
|
124
|
+
if (targetPath !== experientialPrevious && targetPath !== experientialNext) {
|
|
125
|
+
return;
|
|
126
|
+
}
|
|
127
|
+
} catch {
|
|
128
|
+
return;
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
|
|
77
132
|
try {
|
|
78
133
|
const currentUrl = new URL(window.location.href);
|
|
79
134
|
const targetUrl = new URL(href, currentUrl);
|
|
@@ -111,6 +166,24 @@
|
|
|
111
166
|
}
|
|
112
167
|
}
|
|
113
168
|
|
|
169
|
+
$: if (data.kind === 'page' && typeof window !== 'undefined' && $page.url && data.html) {
|
|
170
|
+
if (!data.nav.next) {
|
|
171
|
+
const routeBase = data.routeBase;
|
|
172
|
+
if (routeBase) {
|
|
173
|
+
const links = extractInternalTutorialLinks(data.html, routeBase);
|
|
174
|
+
if (links.length === 1) {
|
|
175
|
+
experientialNext = links[0];
|
|
176
|
+
} else {
|
|
177
|
+
experientialNext = null;
|
|
178
|
+
}
|
|
179
|
+
} else {
|
|
180
|
+
experientialNext = null;
|
|
181
|
+
}
|
|
182
|
+
} else {
|
|
183
|
+
experientialNext = null;
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
|
|
114
187
|
onMount(() => {
|
|
115
188
|
if (typeof window === 'undefined') return;
|
|
116
189
|
if (data.kind !== 'page' || !data.routeBase) return;
|
|
@@ -124,7 +197,7 @@
|
|
|
124
197
|
|
|
125
198
|
return () => {
|
|
126
199
|
if (tutorialPageElement) {
|
|
127
|
-
|
|
200
|
+
tutorialPageElement.removeEventListener('click', clickHandler);
|
|
128
201
|
}
|
|
129
202
|
};
|
|
130
203
|
});
|
|
@@ -147,6 +220,8 @@
|
|
|
147
220
|
{/if}
|
|
148
221
|
{#if data.nav.next}
|
|
149
222
|
<a href={data.nav.next} class="nav-link nav-next">Next</a>
|
|
223
|
+
{:else if experientialNext}
|
|
224
|
+
<a href={experientialNext} class="nav-link nav-next">Next</a>
|
|
150
225
|
{:else}
|
|
151
226
|
<span class="nav-link nav-next nav-placeholder"></span>
|
|
152
227
|
{/if}
|
|
@@ -17,7 +17,6 @@ export function createTutorialEntries({ routeBase, modules, enableDirectoryIndex
|
|
|
17
17
|
const entries = generateEntries(modules, routeBase, enableDirectoryIndex);
|
|
18
18
|
// Ensure we always return an array, even if empty
|
|
19
19
|
if (!Array.isArray(entries)) {
|
|
20
|
-
console.warn('[tutorial-svelte] generateEntries did not return an array:', entries);
|
|
21
20
|
return [];
|
|
22
21
|
}
|
|
23
22
|
// Ensure all entries have the correct structure
|
|
@@ -31,27 +31,10 @@ export function createTutorialLoad({ routeBase, modules, renderMarkdown, enableD
|
|
|
31
31
|
? pathParam.split('/').filter(Boolean)
|
|
32
32
|
: [];
|
|
33
33
|
|
|
34
|
-
// Debug logging
|
|
35
|
-
if (process.env.NODE_ENV === 'development') {
|
|
36
|
-
console.log('[tutorial-svelte] pathArray:', pathArray);
|
|
37
|
-
console.log('[tutorial-svelte] modules keys:', Object.keys(modules));
|
|
38
|
-
const discovered = discoverContent(modules);
|
|
39
|
-
console.log('[tutorial-svelte] discovered:', discovered);
|
|
40
|
-
}
|
|
41
|
-
|
|
42
34
|
const resolved = resolveRoute(pathArray, modules, routeBase);
|
|
43
|
-
|
|
44
|
-
if (process.env.NODE_ENV === 'development') {
|
|
45
|
-
console.log('[tutorial-svelte] resolved:', resolved);
|
|
46
|
-
}
|
|
47
35
|
|
|
48
36
|
// Handle error case (not found)
|
|
49
37
|
if (resolved.type === 'error') {
|
|
50
|
-
const discovered = discoverContent(modules);
|
|
51
|
-
if (process.env.NODE_ENV === 'development') {
|
|
52
|
-
console.log('[tutorial-svelte] Path not found. Looking for:', pathArray.join('/'));
|
|
53
|
-
console.log('[tutorial-svelte] Available routes:', discovered.map(d => d.routePath));
|
|
54
|
-
}
|
|
55
38
|
return {
|
|
56
39
|
kind: 'error',
|
|
57
40
|
code: 'not_found',
|