@sprig-and-prose/tutorial-svelte 0.2.1 → 0.2.2

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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sprig-and-prose/tutorial-svelte",
3
- "version": "0.2.1",
3
+ "version": "0.2.2",
4
4
  "type": "module",
5
5
  "description": "A calm SvelteKit package that transforms markdown files into paged tutorial routes",
6
6
  "main": "src/index.js",
@@ -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
 
@@ -111,6 +153,24 @@
111
153
  }
112
154
  }
113
155
 
156
+ $: if (data.kind === 'page' && typeof window !== 'undefined' && $page.url && data.html) {
157
+ if (!data.nav.next) {
158
+ const routeBase = data.routeBase;
159
+ if (routeBase) {
160
+ const links = extractInternalTutorialLinks(data.html, routeBase);
161
+ if (links.length === 1) {
162
+ experientialNext = links[0];
163
+ } else {
164
+ experientialNext = null;
165
+ }
166
+ } else {
167
+ experientialNext = null;
168
+ }
169
+ } else {
170
+ experientialNext = null;
171
+ }
172
+ }
173
+
114
174
  onMount(() => {
115
175
  if (typeof window === 'undefined') return;
116
176
  if (data.kind !== 'page' || !data.routeBase) return;
@@ -124,7 +184,7 @@
124
184
 
125
185
  return () => {
126
186
  if (tutorialPageElement) {
127
- tutorialPageElement.removeEventListener('click', clickHandler);
187
+ tutorialPageElement.removeEventListener('click', clickHandler);
128
188
  }
129
189
  };
130
190
  });
@@ -147,6 +207,8 @@
147
207
  {/if}
148
208
  {#if data.nav.next}
149
209
  <a href={data.nav.next} class="nav-link nav-next">Next</a>
210
+ {:else if experientialNext}
211
+ <a href={experientialNext} class="nav-link nav-next">Next</a>
150
212
  {:else}
151
213
  <span class="nav-link nav-next nav-placeholder"></span>
152
214
  {/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',