@alliance-droid/svelte-docs-system 0.0.1

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 (134) hide show
  1. package/COMPONENTS.md +365 -0
  2. package/COVERAGE_REPORT.md +663 -0
  3. package/README.md +42 -0
  4. package/SEARCH_VERIFICATION.md +229 -0
  5. package/TEST_SUMMARY.md +344 -0
  6. package/bin/init.js +821 -0
  7. package/docs/E2E_TESTS.md +354 -0
  8. package/docs/TESTING.md +754 -0
  9. package/docs/de/index.md +41 -0
  10. package/docs/en/COMPONENTS.md +443 -0
  11. package/docs/en/api/examples.md +100 -0
  12. package/docs/en/api/overview.md +69 -0
  13. package/docs/en/components/index.md +622 -0
  14. package/docs/en/config/navigation.md +505 -0
  15. package/docs/en/config/theme-and-colors.md +395 -0
  16. package/docs/en/getting-started/integration.md +406 -0
  17. package/docs/en/guides/common-setups.md +651 -0
  18. package/docs/en/index.md +243 -0
  19. package/docs/en/markdown.md +102 -0
  20. package/docs/en/routing.md +64 -0
  21. package/docs/en/setup.md +52 -0
  22. package/docs/en/troubleshooting.md +704 -0
  23. package/docs/es/index.md +41 -0
  24. package/docs/fr/index.md +41 -0
  25. package/docs/ja/index.md +41 -0
  26. package/package.json +40 -0
  27. package/pagefind.toml +8 -0
  28. package/postcss.config.js +5 -0
  29. package/src/app.css +119 -0
  30. package/src/app.d.ts +13 -0
  31. package/src/app.html +11 -0
  32. package/src/lib/assets/favicon.svg +1 -0
  33. package/src/lib/components/APITable.svelte +120 -0
  34. package/src/lib/components/APITable.test.ts +153 -0
  35. package/src/lib/components/Breadcrumbs.svelte +85 -0
  36. package/src/lib/components/Breadcrumbs.test.ts +148 -0
  37. package/src/lib/components/Callout.svelte +60 -0
  38. package/src/lib/components/Callout.test.ts +100 -0
  39. package/src/lib/components/CodeBlock.svelte +68 -0
  40. package/src/lib/components/CodeBlock.test.ts +133 -0
  41. package/src/lib/components/DocLayout.svelte +84 -0
  42. package/src/lib/components/Footer.svelte +78 -0
  43. package/src/lib/components/Image.svelte +100 -0
  44. package/src/lib/components/Image.test.ts +163 -0
  45. package/src/lib/components/Navbar.svelte +141 -0
  46. package/src/lib/components/Search.svelte +248 -0
  47. package/src/lib/components/Sidebar.svelte +110 -0
  48. package/src/lib/components/Tabs.svelte +48 -0
  49. package/src/lib/components/Tabs.test.ts +102 -0
  50. package/src/lib/config.test.ts +140 -0
  51. package/src/lib/config.ts +179 -0
  52. package/src/lib/configIntegration.test.ts +272 -0
  53. package/src/lib/configLoader.ts +231 -0
  54. package/src/lib/configParser.test.ts +217 -0
  55. package/src/lib/configParser.ts +234 -0
  56. package/src/lib/index.ts +34 -0
  57. package/src/lib/integration.test.ts +426 -0
  58. package/src/lib/navigationBuilder.test.ts +338 -0
  59. package/src/lib/navigationBuilder.ts +268 -0
  60. package/src/lib/performance.test.ts +369 -0
  61. package/src/lib/routing.test.ts +202 -0
  62. package/src/lib/routing.ts +127 -0
  63. package/src/lib/search-functionality.test.ts +493 -0
  64. package/src/lib/stores/i18n.test.ts +180 -0
  65. package/src/lib/stores/i18n.ts +143 -0
  66. package/src/lib/stores/nav.ts +36 -0
  67. package/src/lib/stores/search.test.ts +140 -0
  68. package/src/lib/stores/search.ts +162 -0
  69. package/src/lib/stores/theme.ts +59 -0
  70. package/src/lib/stores/version.test.ts +139 -0
  71. package/src/lib/stores/version.ts +111 -0
  72. package/src/lib/themeCustomization.test.ts +223 -0
  73. package/src/lib/themeCustomization.ts +212 -0
  74. package/src/lib/utils/highlight.test.ts +136 -0
  75. package/src/lib/utils/highlight.ts +100 -0
  76. package/src/lib/utils/index.ts +7 -0
  77. package/src/lib/utils/markdown.test.ts +357 -0
  78. package/src/lib/utils/markdown.ts +77 -0
  79. package/src/routes/+layout.server.ts +1 -0
  80. package/src/routes/+layout.svelte +28 -0
  81. package/src/routes/+page.svelte +165 -0
  82. package/static/robots.txt +3 -0
  83. package/svelte.config.js +18 -0
  84. package/tailwind.config.ts +55 -0
  85. package/template-starter/.github/workflows/build.yml +40 -0
  86. package/template-starter/.github/workflows/deploy-github-pages.yml +47 -0
  87. package/template-starter/.github/workflows/deploy-netlify.yml +41 -0
  88. package/template-starter/.github/workflows/deploy-vercel.yml +64 -0
  89. package/template-starter/NPM-PACKAGE-SETUP.md +233 -0
  90. package/template-starter/README.md +320 -0
  91. package/template-starter/docs/_config.json +39 -0
  92. package/template-starter/docs/api/components.md +257 -0
  93. package/template-starter/docs/api/overview.md +169 -0
  94. package/template-starter/docs/guides/configuration.md +145 -0
  95. package/template-starter/docs/guides/github-pages-deployment.md +254 -0
  96. package/template-starter/docs/guides/netlify-deployment.md +159 -0
  97. package/template-starter/docs/guides/vercel-deployment.md +131 -0
  98. package/template-starter/docs/index.md +49 -0
  99. package/template-starter/docs/setup.md +149 -0
  100. package/template-starter/package.json +31 -0
  101. package/template-starter/pagefind.toml +3 -0
  102. package/template-starter/postcss.config.js +5 -0
  103. package/template-starter/src/app.css +34 -0
  104. package/template-starter/src/app.d.ts +13 -0
  105. package/template-starter/src/app.html +11 -0
  106. package/template-starter/src/lib/components/APITable.svelte +120 -0
  107. package/template-starter/src/lib/components/APITable.test.ts +19 -0
  108. package/template-starter/src/lib/components/Breadcrumbs.svelte +85 -0
  109. package/template-starter/src/lib/components/Breadcrumbs.test.ts +19 -0
  110. package/template-starter/src/lib/components/Callout.svelte +60 -0
  111. package/template-starter/src/lib/components/Callout.test.ts +16 -0
  112. package/template-starter/src/lib/components/CodeBlock.svelte +68 -0
  113. package/template-starter/src/lib/components/CodeBlock.test.ts +12 -0
  114. package/template-starter/src/lib/components/DocLayout.svelte +84 -0
  115. package/template-starter/src/lib/components/Footer.svelte +78 -0
  116. package/template-starter/src/lib/components/Image.svelte +100 -0
  117. package/template-starter/src/lib/components/Image.test.ts +15 -0
  118. package/template-starter/src/lib/components/Navbar.svelte +141 -0
  119. package/template-starter/src/lib/components/Search.svelte +248 -0
  120. package/template-starter/src/lib/components/Sidebar.svelte +110 -0
  121. package/template-starter/src/lib/components/Tabs.svelte +48 -0
  122. package/template-starter/src/lib/components/Tabs.test.ts +17 -0
  123. package/template-starter/src/lib/index.ts +15 -0
  124. package/template-starter/src/routes/+layout.svelte +28 -0
  125. package/template-starter/src/routes/+page.svelte +92 -0
  126. package/template-starter/svelte.config.js +17 -0
  127. package/template-starter/tailwind.config.ts +17 -0
  128. package/template-starter/tsconfig.json +13 -0
  129. package/template-starter/vite.config.ts +6 -0
  130. package/tests/e2e/example.spec.ts +345 -0
  131. package/tsconfig.json +20 -0
  132. package/vite.config.ts +6 -0
  133. package/vitest.config.ts +34 -0
  134. package/vitest.setup.ts +21 -0
@@ -0,0 +1,100 @@
1
+ /**
2
+ * Highlight search terms in text
3
+ * Returns HTML with highlighted terms wrapped in <mark> tags
4
+ */
5
+ export function highlightSearchTerms(text: string, searchQuery: string): string {
6
+ if (!text || !searchQuery.trim()) {
7
+ return text;
8
+ }
9
+
10
+ // Split search query into individual terms and escape special regex characters
11
+ const terms = searchQuery
12
+ .trim()
13
+ .split(/\s+/)
14
+ .filter((term) => term.length > 0)
15
+ .map((term) => term.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'));
16
+
17
+ if (terms.length === 0) {
18
+ return text;
19
+ }
20
+
21
+ // Create a regex that matches any of the terms (case-insensitive)
22
+ const regex = new RegExp(`\\b(${terms.join('|')})\\b`, 'gi');
23
+
24
+ // Replace matches with highlighted version
25
+ return text.replace(regex, '<mark>$1</mark>');
26
+ }
27
+
28
+ /**
29
+ * Truncate text to a maximum length and ensure it ends at a word boundary
30
+ */
31
+ export function truncateText(text: string, maxLength: number = 150): string {
32
+ if (text.length <= maxLength) {
33
+ return text;
34
+ }
35
+
36
+ // Truncate and find the last space
37
+ const truncated = text.substring(0, maxLength);
38
+ const lastSpace = truncated.lastIndexOf(' ');
39
+
40
+ if (lastSpace > maxLength * 0.7) {
41
+ // If the last space is relatively close to the end, use it
42
+ return truncated.substring(0, lastSpace) + '...';
43
+ }
44
+
45
+ // Otherwise just truncate and add ellipsis
46
+ return truncated + '...';
47
+ }
48
+
49
+ /**
50
+ * Extract excerpt around search terms
51
+ */
52
+ export function extractExcerpt(text: string, searchQuery: string, maxLength: number = 200): string {
53
+ if (!text || !searchQuery.trim()) {
54
+ return truncateText(text, maxLength);
55
+ }
56
+
57
+ const searchTerms = searchQuery
58
+ .trim()
59
+ .split(/\s+/)
60
+ .filter((term) => term.length > 0)
61
+ .map((term) => term.toLowerCase());
62
+
63
+ // Find the position of the first search term in the text
64
+ let earliestIndex = text.length;
65
+ for (const term of searchTerms) {
66
+ const index = text.toLowerCase().indexOf(term);
67
+ if (index !== -1 && index < earliestIndex) {
68
+ earliestIndex = index;
69
+ }
70
+ }
71
+
72
+ // Extract context around the search term
73
+ let startIndex = Math.max(0, earliestIndex - 50);
74
+ let endIndex = Math.min(text.length, startIndex + maxLength);
75
+
76
+ // Adjust if we're too close to the end
77
+ if (endIndex === text.length && startIndex > 0) {
78
+ startIndex = Math.max(0, endIndex - maxLength);
79
+ }
80
+
81
+ let excerpt = text.substring(startIndex, endIndex);
82
+
83
+ // Remove partial words at the beginning
84
+ if (startIndex > 0) {
85
+ const firstSpace = excerpt.indexOf(' ');
86
+ if (firstSpace !== -1 && firstSpace < 20) {
87
+ excerpt = excerpt.substring(firstSpace + 1);
88
+ }
89
+ }
90
+
91
+ // Add ellipsis if needed
92
+ if (startIndex > 0) {
93
+ excerpt = '...' + excerpt;
94
+ }
95
+ if (endIndex < text.length) {
96
+ excerpt = excerpt + '...';
97
+ }
98
+
99
+ return excerpt;
100
+ }
@@ -0,0 +1,7 @@
1
+ export {
2
+ renderMarkdown,
3
+ extractMetadata,
4
+ getExcerpt,
5
+ type MarkdownMetadata,
6
+ type MarkdownResult
7
+ } from './markdown';
@@ -0,0 +1,357 @@
1
+ import { describe, it, expect, beforeEach } from 'vitest';
2
+ import {
3
+ renderMarkdown,
4
+ extractMetadata,
5
+ getExcerpt,
6
+ type MarkdownResult,
7
+ type MarkdownMetadata
8
+ } from './markdown';
9
+
10
+ describe('Markdown Utilities', () => {
11
+ describe('renderMarkdown', () => {
12
+ it('should render basic markdown to HTML', async () => {
13
+ const content = '# Hello World';
14
+ const result = await renderMarkdown(content);
15
+
16
+ expect(result.html).toBeTruthy();
17
+ expect(result.html).toContain('Hello World');
18
+ expect(result.html).toContain('<h1>');
19
+ });
20
+
21
+ it('should parse frontmatter metadata', async () => {
22
+ const content = `---
23
+ title: Test Page
24
+ description: A test page
25
+ author: John Doe
26
+ ---
27
+
28
+ # Content`;
29
+
30
+ const result = await renderMarkdown(content);
31
+
32
+ expect(result.metadata.title).toBe('Test Page');
33
+ expect(result.metadata.description).toBe('A test page');
34
+ expect(result.metadata.author).toBe('John Doe');
35
+ });
36
+
37
+ it('should handle content without frontmatter', async () => {
38
+ const content = '## Section\n\nSome content here';
39
+ const result = await renderMarkdown(content);
40
+
41
+ expect(result.html).toBeTruthy();
42
+ expect(result.metadata).toEqual({});
43
+ });
44
+
45
+ it('should convert markdown formatting to HTML', async () => {
46
+ const content = `
47
+ # Heading 1
48
+ ## Heading 2
49
+ **Bold text**
50
+ *Italic text*
51
+ \`code\`
52
+ `;
53
+
54
+ const result = await renderMarkdown(content);
55
+
56
+ expect(result.html).toContain('<h1>');
57
+ expect(result.html).toContain('<h2>');
58
+ expect(result.html).toContain('<strong>');
59
+ expect(result.html).toContain('<em>');
60
+ expect(result.html).toContain('<code>');
61
+ });
62
+
63
+ it('should handle code blocks', async () => {
64
+ const content = `\`\`\`javascript
65
+ const x = 42;
66
+ console.log(x);
67
+ \`\`\``;
68
+
69
+ const result = await renderMarkdown(content);
70
+
71
+ expect(result.html).toContain('<pre>');
72
+ expect(result.html).toContain('<code');
73
+ expect(result.html).toContain('const x = 42');
74
+ });
75
+
76
+ it('should handle lists', async () => {
77
+ const content = `
78
+ - Item 1
79
+ - Item 2
80
+ - Item 3
81
+
82
+ 1. First
83
+ 2. Second
84
+ 3. Third
85
+ `;
86
+
87
+ const result = await renderMarkdown(content);
88
+
89
+ expect(result.html).toContain('<li>');
90
+ expect(result.html).toContain('Item 1');
91
+ });
92
+
93
+ it('should handle links', async () => {
94
+ const content = '[Visit Example](https://example.com)';
95
+ const result = await renderMarkdown(content);
96
+
97
+ expect(result.html).toContain('<a');
98
+ expect(result.html).toContain('https://example.com');
99
+ expect(result.html).toContain('Visit Example');
100
+ });
101
+
102
+ it('should handle images', async () => {
103
+ const content = '![Alt text](./image.png)';
104
+ const result = await renderMarkdown(content);
105
+
106
+ expect(result.html).toContain('<img');
107
+ expect(result.html).toContain('./image.png');
108
+ expect(result.html).toContain('Alt text');
109
+ });
110
+
111
+ it('should handle blockquotes', async () => {
112
+ const content = '> This is a quote';
113
+ const result = await renderMarkdown(content);
114
+
115
+ expect(result.html).toContain('<blockquote');
116
+ expect(result.html).toContain('This is a quote');
117
+ });
118
+
119
+ it('should handle tables', async () => {
120
+ const content = `
121
+ | Header 1 | Header 2 |
122
+ |----------|----------|
123
+ | Cell 1 | Cell 2 |
124
+ | Cell 3 | Cell 4 |
125
+ `;
126
+
127
+ const result = await renderMarkdown(content);
128
+
129
+ expect(result.html).toContain('<table');
130
+ expect(result.html).toContain('Header 1');
131
+ });
132
+
133
+ it('should return result type with html and metadata', async () => {
134
+ const content = `---
135
+ title: Test
136
+ ---
137
+ Content`;
138
+
139
+ const result = await renderMarkdown(content);
140
+
141
+ expect(result).toHaveProperty('html');
142
+ expect(result).toHaveProperty('metadata');
143
+ expect(typeof result.html).toBe('string');
144
+ expect(typeof result.metadata).toBe('object');
145
+ });
146
+
147
+ it('should handle empty content', async () => {
148
+ const result = await renderMarkdown('');
149
+ expect(result.html).toBeDefined();
150
+ expect(result.metadata).toBeDefined();
151
+ });
152
+
153
+ it('should handle very long content', async () => {
154
+ const longContent = Array(100).fill('# Heading\n\nContent').join('\n');
155
+ const result = await renderMarkdown(longContent);
156
+
157
+ expect(result.html).toBeTruthy();
158
+ expect(result.html.length).toBeGreaterThan(0);
159
+ });
160
+
161
+ it('should preserve custom metadata fields', async () => {
162
+ const content = `---
163
+ title: Test
164
+ custom_field: custom_value
165
+ tags: [tag1, tag2]
166
+ ---
167
+ Content`;
168
+
169
+ const result = await renderMarkdown(content);
170
+
171
+ expect(result.metadata.custom_field).toBe('custom_value');
172
+ expect(Array.isArray(result.metadata.tags)).toBe(true);
173
+ });
174
+ });
175
+
176
+ describe('extractMetadata', () => {
177
+ it('should extract metadata from content', () => {
178
+ const content = `---
179
+ title: Page Title
180
+ description: Page description
181
+ author: John Doe
182
+ date: 2024-01-01
183
+ ---
184
+ Content`;
185
+
186
+ const metadata = extractMetadata(content);
187
+
188
+ expect(metadata.title).toBe('Page Title');
189
+ expect(metadata.description).toBe('Page description');
190
+ expect(metadata.author).toBe('John Doe');
191
+ // YAML parses dates as Date objects, convert to ISO string
192
+ expect(metadata.date instanceof Date || typeof metadata.date === 'string').toBe(true);
193
+ });
194
+
195
+ it('should return empty object if no frontmatter', () => {
196
+ const content = '# Just content\n\nNo metadata here';
197
+ const metadata = extractMetadata(content);
198
+
199
+ expect(Object.keys(metadata).length).toBe(0);
200
+ });
201
+
202
+ it('should handle empty frontmatter', () => {
203
+ const content = `---
204
+ ---
205
+ Content`;
206
+
207
+ const metadata = extractMetadata(content);
208
+ expect(metadata).toBeDefined();
209
+ });
210
+
211
+ it('should support various data types in metadata', () => {
212
+ const content = `---
213
+ string_field: value
214
+ number_field: 42
215
+ boolean_field: true
216
+ array_field: [1, 2, 3]
217
+ object_field:
218
+ nested: value
219
+ ---
220
+ Content`;
221
+
222
+ const metadata = extractMetadata(content);
223
+
224
+ expect(metadata.string_field).toBe('value');
225
+ expect(metadata.number_field).toBe(42);
226
+ expect(metadata.boolean_field).toBe(true);
227
+ expect(Array.isArray(metadata.array_field)).toBe(true);
228
+ expect(metadata.object_field.nested).toBe('value');
229
+ });
230
+ });
231
+
232
+ describe('getExcerpt', () => {
233
+ it('should extract excerpt from markdown content', () => {
234
+ const content = `---
235
+ title: Test
236
+ ---
237
+
238
+ This is the first paragraph with some content.
239
+
240
+ This is the second paragraph.`;
241
+
242
+ const excerpt = getExcerpt(content);
243
+
244
+ expect(excerpt).toContain('This is the first paragraph');
245
+ });
246
+
247
+ it('should remove markdown formatting from excerpt', () => {
248
+ const content = `**Bold** and *italic* with [links](https://example.com)`;
249
+ const excerpt = getExcerpt(content);
250
+
251
+ expect(excerpt).not.toContain('**');
252
+ expect(excerpt).not.toContain('*');
253
+ expect(excerpt).not.toContain('[');
254
+ expect(excerpt).toContain('Bold');
255
+ expect(excerpt).toContain('links');
256
+ });
257
+
258
+ it('should remove header formatting from excerpt', () => {
259
+ const content = `This is the actual content without headers.`;
260
+
261
+ const excerpt = getExcerpt(content);
262
+
263
+ expect(excerpt).toContain('This is the actual content');
264
+ });
265
+
266
+ it('should remove inline code from excerpt', () => {
267
+ const content = 'This is `code` in the text';
268
+ const excerpt = getExcerpt(content);
269
+
270
+ expect(excerpt).not.toContain('`');
271
+ expect(excerpt).toContain('This is code in the text');
272
+ });
273
+
274
+ it('should respect word limit', () => {
275
+ const longContent = Array(50).fill('word').join(' ');
276
+ const excerpt = getExcerpt(longContent, 10);
277
+
278
+ const wordCount = excerpt.split(/\s+/).length;
279
+ expect(wordCount).toBeLessThanOrEqual(11); // 10 words + ellipsis might add spaces
280
+ });
281
+
282
+ it('should add ellipsis if content is truncated', () => {
283
+ const longContent = Array(200).fill('word').join(' ');
284
+ const excerpt = getExcerpt(longContent, 10);
285
+
286
+ expect(excerpt).toContain('...');
287
+ });
288
+
289
+ it('should not add ellipsis if content fits', () => {
290
+ const shortContent = 'Short content';
291
+ const excerpt = getExcerpt(shortContent, 100);
292
+
293
+ expect(excerpt).not.toContain('...');
294
+ });
295
+
296
+ it('should handle content with frontmatter', () => {
297
+ const content = `---
298
+ title: Test
299
+ ---
300
+
301
+ This is the excerpt content.`;
302
+
303
+ const excerpt = getExcerpt(content);
304
+
305
+ expect(excerpt).toContain('This is the excerpt content');
306
+ expect(excerpt).not.toContain('title');
307
+ });
308
+
309
+ it('should handle empty content', () => {
310
+ const excerpt = getExcerpt('');
311
+ expect(excerpt).toBe('');
312
+ });
313
+
314
+ it('should handle content with only whitespace', () => {
315
+ const excerpt = getExcerpt(' \n\n ');
316
+ expect(excerpt.trim()).toBe('');
317
+ });
318
+
319
+ it('should handle default word limit', () => {
320
+ const content = Array(200).fill('word').join(' ');
321
+ const excerpt = getExcerpt(content); // Uses default 150
322
+
323
+ expect(excerpt).toBeTruthy();
324
+ });
325
+ });
326
+
327
+ describe('Edge Cases', () => {
328
+ it('should handle malformed markdown gracefully', async () => {
329
+ const content = '[unclosed link]](extra';
330
+ const result = await renderMarkdown(content);
331
+
332
+ expect(result.html).toBeDefined();
333
+ expect(result.metadata).toBeDefined();
334
+ });
335
+
336
+ it('should handle special characters in content', async () => {
337
+ const content = '< > & " \' `special characters`';
338
+ const result = await renderMarkdown(content);
339
+
340
+ expect(result.html).toBeTruthy();
341
+ });
342
+
343
+ it('should handle unicode content', async () => {
344
+ const content = '# 你好世界\n\n日本語テスト';
345
+ const result = await renderMarkdown(content);
346
+
347
+ expect(result.html).toContain('你好世界');
348
+ });
349
+
350
+ it('should handle nested formatting', async () => {
351
+ const content = '***Bold and italic***';
352
+ const result = await renderMarkdown(content);
353
+
354
+ expect(result.html).toBeTruthy();
355
+ });
356
+ });
357
+ });
@@ -0,0 +1,77 @@
1
+ import { marked } from 'marked';
2
+ import matter from 'gray-matter';
3
+
4
+ export interface MarkdownMetadata {
5
+ title?: string;
6
+ description?: string;
7
+ author?: string;
8
+ date?: string;
9
+ [key: string]: any;
10
+ }
11
+
12
+ export interface MarkdownResult {
13
+ html: string;
14
+ metadata: MarkdownMetadata;
15
+ slug?: string;
16
+ }
17
+
18
+ /**
19
+ * Parse markdown content with frontmatter
20
+ * Returns HTML content and metadata
21
+ */
22
+ export async function renderMarkdown(content: string): Promise<MarkdownResult> {
23
+ try {
24
+ // Parse frontmatter and content
25
+ const { data, content: markdownContent } = matter(content);
26
+
27
+ // Convert markdown to HTML (marked is async in v17+)
28
+ const html = await marked(markdownContent);
29
+
30
+ return {
31
+ html,
32
+ metadata: data as MarkdownMetadata
33
+ };
34
+ } catch (error) {
35
+ console.error('Error rendering markdown:', error);
36
+ return {
37
+ html: '<p>Error rendering markdown</p>',
38
+ metadata: {}
39
+ };
40
+ }
41
+ }
42
+
43
+ /**
44
+ * Extract metadata from markdown content
45
+ */
46
+ export function extractMetadata(content: string): MarkdownMetadata {
47
+ try {
48
+ const { data } = matter(content);
49
+ return data as MarkdownMetadata;
50
+ } catch (error) {
51
+ return {};
52
+ }
53
+ }
54
+
55
+ /**
56
+ * Get excerpt from markdown content (first paragraph)
57
+ */
58
+ export function getExcerpt(content: string, wordLimit = 150): string {
59
+ try {
60
+ const { content: markdownContent } = matter(content);
61
+
62
+ // Remove markdown syntax and get first paragraph
63
+ const text = markdownContent
64
+ .replace(/#{1,6}\s/g, '') // Remove headers
65
+ .replace(/[*_]{1,2}(.*?)[*_]{1,2}/g, '$1') // Remove bold/italic
66
+ .replace(/\[([^\]]+)\]\([^)]+\)/g, '$1') // Remove links
67
+ .replace(/`([^`]+)`/g, '$1') // Remove inline code
68
+ .trim();
69
+
70
+ const words = text.split(/\s+/);
71
+ const excerpt = words.slice(0, wordLimit).join(' ');
72
+
73
+ return excerpt.length < text.length ? excerpt + '...' : excerpt;
74
+ } catch (error) {
75
+ return '';
76
+ }
77
+ }
@@ -0,0 +1 @@
1
+ export const prerender = true;
@@ -0,0 +1,28 @@
1
+ <script lang="ts">
2
+ import '../app.css';
3
+
4
+ let { children } = $props();
5
+ </script>
6
+
7
+ <svelte:window
8
+ on:resize={() => {
9
+ // Handle window resize if needed
10
+ }}
11
+ />
12
+
13
+ <div class="app">
14
+ {@render children?.()}
15
+ </div>
16
+
17
+ <style>
18
+ :global(body) {
19
+ margin: 0;
20
+ padding: 0;
21
+ }
22
+
23
+ .app {
24
+ display: flex;
25
+ flex-direction: column;
26
+ min-height: 100vh;
27
+ }
28
+ </style>