@xyd-js/content 0.1.0-xyd.13 → 0.1.0-xyd.16

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 (89) hide show
  1. package/CHANGELOG.md +34 -0
  2. package/ISSUES.md +1 -0
  3. package/TODO.md +2 -0
  4. package/dist/index.d.ts +24 -5
  5. package/dist/index.js +1411 -21776
  6. package/dist/index.js.map +1 -1
  7. package/dist/md.d.ts +62 -7
  8. package/dist/md.js +18071 -15085
  9. package/dist/md.js.map +1 -1
  10. package/dist/{mdToc-CYxzibVZ.d.ts → mdToc-NBBxMJ4l.d.ts} +1 -0
  11. package/dist/vite.d.ts +81 -2
  12. package/dist/vite.js +9716 -10090
  13. package/dist/vite.js.map +1 -1
  14. package/example.txt +0 -0
  15. package/package.json +24 -6
  16. package/packages/md/index.ts +17 -8
  17. package/packages/md/plugins/component-directives/index.ts +3 -0
  18. package/packages/md/plugins/component-directives/mdComponentDirective.ts +524 -0
  19. package/packages/md/plugins/component-directives/types.ts +1 -0
  20. package/packages/md/plugins/component-directives/utils.ts +27 -0
  21. package/packages/md/plugins/composer/__fixtures__/1.single-example/input.md +7 -0
  22. package/packages/md/plugins/composer/__fixtures__/1.single-example/output.json +63 -0
  23. package/packages/md/plugins/composer/__fixtures__/2.single-example-with-name/input.md +7 -0
  24. package/packages/md/plugins/composer/__fixtures__/2.single-example-with-name/output.json +63 -0
  25. package/packages/md/plugins/composer/__fixtures__/3.multiple-examples/input.md +15 -0
  26. package/packages/md/plugins/composer/__fixtures__/3.multiple-examples/output.json +122 -0
  27. package/packages/md/plugins/composer/__fixtures__/4.example-groups/input.md +23 -0
  28. package/packages/md/plugins/composer/__fixtures__/4.example-groups/output.json +184 -0
  29. package/packages/md/plugins/composer/__tests__/mdComposer.test.ts +41 -0
  30. package/packages/md/plugins/composer/__tests__/testHelpers.ts +48 -0
  31. package/packages/md/plugins/composer/index.ts +1 -0
  32. package/packages/md/plugins/composer/mdComposer.ts +146 -0
  33. package/packages/md/plugins/developer-writing/index.ts +3 -0
  34. package/packages/md/plugins/developer-writing/mdCodeRehype.ts +78 -0
  35. package/packages/md/plugins/functions/__fixtures__/external.ts +4 -0
  36. package/packages/md/plugins/functions/__fixtures__/test.js +11 -0
  37. package/packages/md/plugins/functions/__fixtures__/test.py +9 -0
  38. package/packages/md/plugins/functions/__fixtures__/test.ts +18 -0
  39. package/packages/md/plugins/functions/__tests__/mdFunctionImportCode.test.ts +295 -0
  40. package/packages/md/plugins/functions/__tests__/parseFunctionCall.test.ts +47 -0
  41. package/packages/md/plugins/functions/__tests__/testHelpers.ts +71 -0
  42. package/packages/md/plugins/functions/index.ts +11 -0
  43. package/packages/md/plugins/functions/mdFunctionChangelog.ts +124 -0
  44. package/packages/md/plugins/functions/mdFunctionImportCode.ts +83 -0
  45. package/packages/md/plugins/functions/mdFunctionUniform.ts +79 -0
  46. package/packages/md/plugins/functions/types.ts +6 -0
  47. package/packages/md/plugins/functions/uniformProcessor.ts +349 -0
  48. package/packages/md/plugins/functions/utils.ts +423 -0
  49. package/packages/md/plugins/index.ts +56 -11
  50. package/packages/md/plugins/mdCode.ts +52 -4
  51. package/packages/md/plugins/mdHeadingId.ts +47 -0
  52. package/packages/md/plugins/mdPage.ts +3 -0
  53. package/packages/md/plugins/mdThemeSettings.ts +4 -0
  54. package/packages/md/plugins/mdToc.ts +108 -17
  55. package/packages/md/plugins/meta/index.ts +1 -0
  56. package/packages/md/plugins/meta/mdMeta.ts +189 -0
  57. package/packages/md/plugins/output-variables/__fixtures__/1.simple/input.md +22 -0
  58. package/packages/md/plugins/output-variables/__fixtures__/1.simple/output.json +191 -0
  59. package/packages/md/plugins/output-variables/__fixtures__/2.multiple-vars/input.md +21 -0
  60. package/packages/md/plugins/output-variables/__fixtures__/2.multiple-vars/output.json +127 -0
  61. package/packages/md/plugins/output-variables/__tests__/index.test.ts +28 -0
  62. package/packages/md/plugins/output-variables/__tests__/testHelpers.ts +36 -0
  63. package/packages/md/plugins/output-variables/index.ts +1 -0
  64. package/packages/md/plugins/output-variables/lib/const.ts +4 -0
  65. package/packages/md/plugins/output-variables/lib/factoryAttributes.ts +350 -0
  66. package/packages/md/plugins/output-variables/lib/factoryLabel.ts +135 -0
  67. package/packages/md/plugins/output-variables/lib/factoryName.ts +59 -0
  68. package/packages/md/plugins/output-variables/lib/index.ts +21 -0
  69. package/packages/md/plugins/output-variables/lib/outputVarsContainer.ts +328 -0
  70. package/packages/md/plugins/output-variables/lib/util.ts +494 -0
  71. package/packages/md/plugins/output-variables/remarkOutputVars.ts +22 -0
  72. package/packages/md/plugins/rehypeHeading.ts +50 -0
  73. package/packages/md/plugins/types.ts +15 -0
  74. package/packages/md/plugins/utils/componentLike.ts +72 -0
  75. package/packages/md/plugins/utils/index.ts +2 -0
  76. package/packages/md/plugins/utils/mdParameters.test.ts +114 -0
  77. package/packages/md/plugins/utils/mdParameters.ts +249 -0
  78. package/packages/md/plugins/utils/mdastTypes.ts +42 -0
  79. package/packages/md/search/index.ts +251 -0
  80. package/packages/md/search/types.ts +36 -0
  81. package/packages/vite/index.ts +8 -2
  82. package/src/fs.ts +51 -36
  83. package/src/index.ts +4 -4
  84. package/src/navigation.ts +50 -38
  85. package/src/types.ts +8 -0
  86. package/tsconfig.json +31 -8
  87. package/vitest.config.ts +17 -0
  88. package/packages/md/plugins/mdCodeGroup.ts +0 -40
  89. package/packages/md/plugins/mdComponentDirective.ts +0 -141
@@ -0,0 +1,114 @@
1
+ import { describe, it, expect } from 'vitest';
2
+ import { mdParameters } from './mdParameters';
3
+
4
+ describe('mdParameters', () => {
5
+ describe("attributes", () => {
6
+ it('1.basic', () => {
7
+ const result = mdParameters('Hello [class="test" id="main"] world');
8
+
9
+ expect(result.attributes).toEqual({
10
+ class: 'test',
11
+ id: 'main'
12
+ });
13
+ expect(result.props).toEqual({});
14
+ expect(result.sanitizedText).toBe('Hello world');
15
+ });
16
+
17
+ it('2.markdown+links', () => {
18
+ const result = mdParameters('[desc="This is [xyd](https://github.com/xyd-js) - a docs framework"]');
19
+
20
+ expect(result.attributes).toEqual({
21
+ desc: 'This is [xyd](https://github.com/xyd-js) - a docs framework',
22
+ });
23
+ expect(result.props).toEqual({});
24
+ expect(result.sanitizedText).toBe('');
25
+ });
26
+ })
27
+
28
+ describe("props", () => {
29
+ it('1.basic', () => {
30
+ const result = mdParameters('Hello {title="tooltip" disabled} world');
31
+
32
+ expect(result.attributes).toEqual({});
33
+ expect(result.props).toEqual({
34
+ title: 'tooltip',
35
+ disabled: 'true'
36
+ });
37
+ expect(result.sanitizedText).toBe('Hello world');
38
+ });
39
+ })
40
+
41
+ describe("attributes + props", () => {
42
+ it('1.basic', () => {
43
+ const result = mdParameters('Hello [class="test"] {title="tooltip"} world');
44
+
45
+ expect(result.attributes).toEqual({
46
+ class: 'test'
47
+ });
48
+ expect(result.props).toEqual({
49
+ title: 'tooltip'
50
+ });
51
+ expect(result.sanitizedText).toBe('Hello world');
52
+ });
53
+ })
54
+
55
+ describe("attributes + htmlMd option", () => {
56
+ it('1.basic', () => {
57
+ const result = mdParameters(
58
+ `[desc="Produces static files within <code>.xyd/build/client</code> which you an deploy easily."]`,
59
+ {
60
+ htmlMd: true
61
+ }
62
+ );
63
+ expect(result.attributes).toEqual({
64
+ desc: "Produces static files within `.xyd/build/client` which you an deploy easily."
65
+ });
66
+
67
+ expect(result.sanitizedText).toBe('');
68
+ });
69
+
70
+ it('2.multiple HTML tags in attribute', () => {
71
+ const result = mdParameters(
72
+ `[desc="This is <strong>bold</strong> and <em>italic</em> text with <code>inline code</code>."]`,
73
+ {
74
+ htmlMd: true
75
+ }
76
+ );
77
+ expect(result.attributes).toEqual({
78
+ desc: "This is **bold** and *italic* text with `inline code`."
79
+ });
80
+ });
81
+ })
82
+
83
+ describe("attributes + :::component", () => {
84
+ it('1.basic', () => {
85
+ const result = mdParameters(`[desc="Choose between light/dark mode. :::button \n **xyd 0.1.0-alpha is coming soon!** \n :::"]`);
86
+ const expectedText = `Choose between light/dark mode.\n :::button \n **xyd 0.1.0-alpha is coming soon!**\n :::`;
87
+
88
+ expect(result.attributes).toEqual({
89
+ desc: expectedText
90
+ });
91
+ expect(result.sanitizedText).toBe('');
92
+ })
93
+
94
+ it("2", () => {
95
+ const result = mdParameters(`[desc=":::button\\n **xyd 0.1.0-alpha is coming soon!**\\n"]`);
96
+ const expectedText = `:::button\n **xyd 0.1.0-alpha is coming soon!**\n:::`;
97
+
98
+ expect(result.attributes).toEqual({
99
+ desc: expectedText
100
+ });
101
+ expect(result.sanitizedText).toBe('');
102
+ })
103
+
104
+ it("3", () => {
105
+ const result = mdParameters(`[desc="Choose between :::color-scheme-button\\n::: mode"]`);
106
+ const expectedText = `Choose between\n :::color-scheme-button\n :::\n mode`;
107
+
108
+ expect(result.attributes).toEqual({
109
+ desc: expectedText
110
+ });
111
+ expect(result.sanitizedText).toBe('');
112
+ })
113
+ })
114
+ });
@@ -0,0 +1,249 @@
1
+ export function mdParameters(
2
+ text: string,
3
+ options = {
4
+ htmlMd: false,
5
+ }
6
+ ) {
7
+ const attributes = parseAttributes(text, options.htmlMd);
8
+ const props = parseProps(text, options.htmlMd);
9
+ let sanitizedText = removeParameters(text);
10
+
11
+ // Apply HTML to markdown transformation if requested
12
+ if (options?.htmlMd) {
13
+ sanitizedText = transformHtmlToMarkdown(sanitizedText);
14
+ }
15
+
16
+ return {
17
+ attributes,
18
+ props,
19
+ sanitizedText
20
+ }
21
+ }
22
+
23
+ function parseAttributes(text: string, htmlMd: boolean = false) {
24
+ return parseParameters(text, '[', ']', htmlMd);
25
+ }
26
+
27
+ function parseProps(text: string, htmlMd: boolean = false) {
28
+ return parseParameters(text, '{', '}', htmlMd);
29
+ }
30
+
31
+ function sanitizeParameters(value: string): string {
32
+ // Convert escaped newlines to actual newlines
33
+ value = value.replace(/\\n/g, '\n');
34
+
35
+ // Remove any HTML tags
36
+ value = value.replace(/<[^>]*>/g, '');
37
+
38
+ // Remove any script tags and their content
39
+ value = value.replace(/<script\b[^<]*(?:(?!<\/script>)<[^<]*)*<\/script>/gi, '');
40
+
41
+ // Remove any potentially dangerous characters, but preserve newlines, :::component syntax, and markdown
42
+ // Don't remove quotes, colons, or asterisks as they're needed for ::: components and markdown
43
+ value = value.replace(/[<>]/g, '');
44
+
45
+ // Remove any control characters except newlines (\n = \x0A)
46
+ value = value.replace(/[\x00-\x09\x0B-\x1F\x7F-\x9F]/g, '');
47
+
48
+ // Ensure proper spacing around ::: components
49
+ // Add newline before ::: if there isn't one already
50
+ value = value.replace(/([^\n])\s*:::/g, '$1\n :::');
51
+
52
+ // Auto-close ::: components if they're not already closed
53
+ // Check if there's a ::: component that's not properly closed
54
+ if (value.includes(':::')) {
55
+ // Count the number of ::: occurrences
56
+ const colonCount = (value.match(/:::/g) || []).length;
57
+
58
+ // If we have an odd number of ::: components, we need to add a closing one
59
+ if (colonCount % 2 === 1) {
60
+ // Only add closing ::: if the string doesn't already end with it
61
+ if (!value.trim().endsWith(':::')) {
62
+ // Add newline before closing ::: if the string doesn't end with newline
63
+ if (!value.endsWith('\n')) {
64
+ value = value + '\n';
65
+ }
66
+ value = value + ':::';
67
+ }
68
+ } else {
69
+ // Even number of ::: components, but we need to ensure proper formatting
70
+ // Look for patterns like "::: text" and convert to ":::\n text"
71
+ // Handle both cases: "::: text" and ":::\n text"
72
+ value = value.replace(/:::\s+([^\n]+)/g, ':::\n $1');
73
+ // Also handle the case where there's already a newline but we need to ensure proper spacing
74
+ value = value.replace(/:::\n\s*([^\n]+)/g, ':::\n $1');
75
+ // Specific case: if we have ":::\n text", ensure it becomes ":::\n text"
76
+ value = value.replace(/:::\n([^\n]+)/g, ':::\n $1');
77
+ }
78
+ }
79
+
80
+ // Trim whitespace
81
+ value = value.trim();
82
+
83
+ return value;
84
+ }
85
+
86
+ function removeParameters(text: string): string {
87
+ // Use a stack-based approach to handle nested brackets
88
+ let result = '';
89
+ let stack = 0;
90
+ let inQuotes = false;
91
+ let quoteChar = '';
92
+
93
+ for (let i = 0; i < text.length; i++) {
94
+ const char = text[i];
95
+
96
+ // Handle quotes
97
+ if ((char === '"' || char === "'") && !inQuotes) {
98
+ inQuotes = true;
99
+ quoteChar = char;
100
+ } else if (char === quoteChar && inQuotes) {
101
+ inQuotes = false;
102
+ quoteChar = '';
103
+ }
104
+
105
+ // Only process brackets when not in quotes
106
+ if (!inQuotes) {
107
+ if (char === '[' || char === '{') {
108
+ stack++;
109
+ } else if (char === ']' || char === '}') {
110
+ stack--;
111
+ } else if (stack === 0) {
112
+ result += char;
113
+ }
114
+ }
115
+ }
116
+
117
+ // Clean up multiple spaces and trim
118
+ return result.replace(/\s+/g, ' ').trim();
119
+ }
120
+
121
+ function parseParameters(
122
+ text: string,
123
+ delimiter: string,
124
+ closingDelimiter: string,
125
+ htmlMd: boolean = false
126
+ ) {
127
+ const attributes: Record<string, string> = {};
128
+
129
+ // Find all parameter blocks using stack-based approach
130
+ const blocks = findNestedBlocks(text, delimiter, closingDelimiter);
131
+
132
+ for (const blockContent of blocks) {
133
+ // Then parse individual parameters within the block
134
+ const paramRegex = /(!)?([^=\s]+)(?:=(?:"([^"]*)"|([^\s]*)))?/g;
135
+ let paramMatch;
136
+
137
+ while ((paramMatch = paramRegex.exec(blockContent)) !== null) {
138
+ const [_, isNegated, prop, quotedValue, unquotedValue] = paramMatch;
139
+ const value = quotedValue !== undefined ? quotedValue : unquotedValue;
140
+
141
+ // Sanitize the property name
142
+ let sanitizedParam = sanitizeParameters(prop);
143
+
144
+ // Handle the value - apply HTML to markdown transformation first if requested
145
+ let sanitizedValue: string;
146
+ if (value) {
147
+ if (htmlMd) {
148
+ // Apply HTML to markdown transformation first, then sanitize
149
+ sanitizedValue = sanitizeParameters(transformHtmlToMarkdown(value));
150
+ } else {
151
+ sanitizedValue = sanitizeParameters(value);
152
+ }
153
+ } else {
154
+ sanitizedValue = isNegated ? 'false' : 'true';
155
+ }
156
+
157
+ if (sanitizedParam.startsWith("#") && sanitizedValue === "true") {
158
+ sanitizedValue = sanitizedParam.replace("#", "").trim()
159
+ sanitizedParam = "id"
160
+ }
161
+
162
+ attributes[sanitizedParam] = sanitizedValue;
163
+ }
164
+ }
165
+
166
+ return attributes;
167
+ }
168
+
169
+ function findNestedBlocks(text: string, openDelimiter: string, closeDelimiter: string): string[] {
170
+ const blocks: string[] = [];
171
+ let stack = 0;
172
+ let startIndex = -1;
173
+ let inQuotes = false;
174
+ let quoteChar = '';
175
+
176
+ for (let i = 0; i < text.length; i++) {
177
+ const char = text[i];
178
+
179
+ // Handle quotes
180
+ if ((char === '"' || char === "'") && !inQuotes) {
181
+ inQuotes = true;
182
+ quoteChar = char;
183
+ } else if (char === quoteChar && inQuotes) {
184
+ inQuotes = false;
185
+ quoteChar = '';
186
+ }
187
+
188
+ // Only process delimiters when not in quotes
189
+ if (!inQuotes) {
190
+ if (char === openDelimiter) {
191
+ if (stack === 0) {
192
+ startIndex = i + 1; // +1 to skip the opening delimiter
193
+ }
194
+ stack++;
195
+ } else if (char === closeDelimiter) {
196
+ stack--;
197
+ if (stack === 0 && startIndex !== -1) {
198
+ blocks.push(text.substring(startIndex, i));
199
+ startIndex = -1;
200
+ }
201
+ }
202
+ }
203
+ }
204
+
205
+ return blocks;
206
+ }
207
+
208
+ function transformHtmlToMarkdown(text: string): string {
209
+ // Transform common HTML tags to markdown
210
+ return text
211
+ // <code> tags to backticks
212
+ .replace(/<code\b[^>]*>(.*?)<\/code>/gi, '`$1`')
213
+ // <strong> and <b> tags to bold
214
+ .replace(/<(strong|b)\b[^>]*>(.*?)<\/(strong|b)>/gi, '**$2**')
215
+ // <em> and <i> tags to italic
216
+ .replace(/<(em|i)\b[^>]*>(.*?)<\/(em|i)>/gi, '*$2*')
217
+ // <a> tags to markdown links
218
+ .replace(/<a\b[^>]*href=["']([^"']*)["'][^>]*>(.*?)<\/a>/gi, '[$2]($1)')
219
+ // <br> tags to line breaks
220
+ .replace(/<br\b[^>]*>/gi, '\n')
221
+ // <p> tags to paragraphs (with line breaks)
222
+ .replace(/<p\b[^>]*>(.*?)<\/p>/gi, '$1\n\n')
223
+ // <h1> to <h6> tags to markdown headers
224
+ .replace(/<h1\b[^>]*>(.*?)<\/h1>/gi, '# $1\n\n')
225
+ .replace(/<h2\b[^>]*>(.*?)<\/h2>/gi, '## $1\n\n')
226
+ .replace(/<h3\b[^>]*>(.*?)<\/h3>/gi, '### $1\n\n')
227
+ .replace(/<h4\b[^>]*>(.*?)<\/h4>/gi, '#### $1\n\n')
228
+ .replace(/<h5\b[^>]*>(.*?)<\/h5>/gi, '##### $1\n\n')
229
+ .replace(/<h6\b[^>]*>(.*?)<\/h6>/gi, '###### $1\n\n')
230
+ // <ul> and <ol> lists
231
+ .replace(/<ul\b[^>]*>(.*?)<\/ul>/gis, (match, content) => {
232
+ return content.replace(/<li\b[^>]*>(.*?)<\/li>/gi, '- $1\n') + '\n';
233
+ })
234
+ .replace(/<ol\b[^>]*>(.*?)<\/ol>/gis, (match, content) => {
235
+ let counter = 1;
236
+ return content.replace(/<li\b[^>]*>(.*?)<\/li>/gi, () => `${counter++}. $1\n`) + '\n';
237
+ })
238
+ // <blockquote> tags
239
+ .replace(/<blockquote\b[^>]*>(.*?)<\/blockquote>/gis, (match, content) => {
240
+ return content.split('\n').map(line => `> ${line}`).join('\n') + '\n\n';
241
+ })
242
+ // <pre> tags for code blocks
243
+ .replace(/<pre\b[^>]*>(.*?)<\/pre>/gis, '```\n$1\n```\n\n')
244
+ // Remove any remaining HTML tags
245
+ .replace(/<[^>]*>/g, '')
246
+ // Clean up multiple line breaks
247
+ .replace(/\n{3,}/g, '\n\n')
248
+ .trim();
249
+ }
@@ -0,0 +1,42 @@
1
+ /**
2
+ * List of standard MDAST node types
3
+ */
4
+ export const standardMdastTypes = [
5
+ 'root',
6
+ 'paragraph',
7
+ 'heading',
8
+ 'text',
9
+ 'emphasis',
10
+ 'strong',
11
+ 'delete',
12
+ 'blockquote',
13
+ 'code',
14
+ 'link',
15
+ 'image',
16
+ 'list',
17
+ 'listItem',
18
+ 'table',
19
+ 'tableRow',
20
+ 'tableCell',
21
+ 'html',
22
+ 'break',
23
+ 'thematicBreak',
24
+ 'yaml',
25
+ 'definition',
26
+ 'footnoteDefinition',
27
+ 'footnoteReference',
28
+ 'inlineCode',
29
+ 'linkReference',
30
+ 'imageReference',
31
+ 'footnote',
32
+ 'tableCaption'
33
+ ];
34
+
35
+ /**
36
+ * Checks if a given type is a standard MDAST type
37
+ * @param type The node type to check
38
+ * @returns True if the type is a standard MDAST type, false otherwise
39
+ */
40
+ export function isStandardMdastType(type: string): boolean {
41
+ return standardMdastTypes.includes(type);
42
+ }
@@ -0,0 +1,251 @@
1
+ import { readFileSync, statSync } from 'node:fs'
2
+ import path from 'node:path'
3
+
4
+ import { unified } from 'unified'
5
+ import remarkParse from 'remark-parse'
6
+ import remarkRehype from 'remark-rehype'
7
+ import rehypeStringify from 'rehype-stringify'
8
+ import { visit } from "unist-util-visit";
9
+
10
+ import GitHubSlugger from 'github-slugger';
11
+
12
+ import type { Settings, Sidebar, SidebarRoute } from '@xyd-js/core'
13
+
14
+ import { DocSectionSchema } from './types';
15
+ import { markdownPlugins } from "../"
16
+
17
+ export async function mapSettingsToDocSections(xydSettings: Settings) {
18
+ const pages = flatPages(xydSettings.navigation?.sidebar || [], {})
19
+ .map(page => ({
20
+ name: page,
21
+ path: path.join(process.cwd(), page)
22
+ }))
23
+
24
+ return await processSections(pages, xydSettings)
25
+ }
26
+
27
+ export async function mapContentToDocSections(
28
+ xydSettings: Settings,
29
+ route: string,
30
+ content: string,
31
+ ) {
32
+ const sections: DocSectionSchema[] = []
33
+ let currentSection: DocSectionSchema | null = null
34
+
35
+ const rootSection: DocSectionSchema = {
36
+ // id: '',
37
+ pageId: '',
38
+ pageUrl: '',
39
+ pageTitle: '',
40
+ headingLevel: 0,
41
+ headingTitle: '',
42
+ summary: '',
43
+ content: '',
44
+ }
45
+
46
+ const { remarkPlugins, rehypePlugins } = markdownPlugins({}, xydSettings)
47
+
48
+ const processor = unified()
49
+ .use(remarkParse)
50
+ .use(remarkPlugins)
51
+ .use(() => {
52
+ return (tree: any) => {
53
+ let currentContent = ''
54
+
55
+ visit(tree, (node) => {
56
+ // Skip yaml frontmatter
57
+ if (node.type === 'yaml') {
58
+ return
59
+ }
60
+
61
+ // Handle headings
62
+ if (node.type === 'heading') {
63
+ const heading = flattenNode(node)
64
+
65
+ // Save previous section's content
66
+ if (currentSection && currentContent) {
67
+ currentSection.content = currentContent.trim()
68
+ currentContent = ''
69
+ }
70
+
71
+ const slug = slugify(heading)
72
+
73
+ if (node.depth === 1) {
74
+ if (rootSection.pageId) {
75
+ console.error("Multiple h1 found")
76
+ }
77
+
78
+ rootSection.pageId = slug
79
+ rootSection.pageUrl = `/${route}`
80
+ rootSection.pageTitle = heading
81
+ rootSection.headingLevel = 1
82
+ rootSection.headingTitle = heading
83
+ sections.push(rootSection)
84
+ currentSection = rootSection
85
+ } else {
86
+ const section: DocSectionSchema = {
87
+ pageId: rootSection.pageId,
88
+ pageUrl: `${rootSection.pageUrl}#${slug}`, // TODO: QUERY PARAMS
89
+ pageTitle: rootSection.headingTitle,
90
+ headingLevel: node.depth,
91
+ headingTitle: heading,
92
+ content: '',
93
+ summary: '',
94
+ }
95
+ sections.push(section)
96
+ currentSection = section
97
+ }
98
+ return
99
+ }
100
+
101
+ // Skip if we're not in a section yet
102
+ if (!currentSection) {
103
+ return
104
+ }
105
+
106
+ // For list items, add proper formatting
107
+ if (node.type === 'listItem') {
108
+ const itemContent = flattenNode(node)
109
+ currentContent += `- ${itemContent}\n`
110
+ return
111
+ }
112
+
113
+ // For links, preserve the markdown format
114
+ if (node.type === 'link') {
115
+ const text = flattenNode(node)
116
+ currentContent += `[${text}](${node.url})\n`
117
+ return
118
+ }
119
+
120
+ // For other nodes, just add their content
121
+ const nodeContent = flattenNode(node)
122
+ if (nodeContent) {
123
+ currentContent += nodeContent + '\n'
124
+ }
125
+ })
126
+
127
+ // Save the last section's content
128
+ if (currentSection && currentContent) {
129
+ currentSection.content = currentContent.trim()
130
+ }
131
+ }
132
+ })
133
+ .use(remarkRehype)
134
+ .use(rehypePlugins)
135
+ .use(rehypeStringify)
136
+
137
+ await processor.process(content)
138
+
139
+ return sections
140
+ }
141
+
142
+ function flattenNode(node: any): string {
143
+ if (node.value) {
144
+ return node.value
145
+ }
146
+
147
+ if (node.children) {
148
+ return node.children.map((child: any) => flattenNode(child)).join('')
149
+ }
150
+
151
+ return ''
152
+ }
153
+
154
+
155
+ async function processSections(pages: { name: string, path: string }[], xydSettings: Settings) {
156
+ return (await Promise.all(
157
+ pages.flatMap(async (file, i) => {
158
+ let filePath = ""
159
+ let err
160
+
161
+ try {
162
+ const p = file.path + ".md"
163
+ statSync(p)
164
+ filePath = p
165
+ } catch (error) {
166
+ err = error
167
+ }
168
+
169
+ if (!filePath) {
170
+ try {
171
+ const p = file.path + ".mdx"
172
+ statSync(p)
173
+ filePath = p
174
+ err = null
175
+ } catch (error) {
176
+ err = error
177
+ }
178
+ }
179
+
180
+ if (err) {
181
+ console.error(err)
182
+ return []
183
+ }
184
+
185
+ const content = readFileSync(filePath, 'utf-8')
186
+ const sections = await mapContentToDocSections(
187
+ xydSettings,
188
+ file.name,
189
+ content,
190
+ )
191
+
192
+ return sections.flat()
193
+ })
194
+ )).flat()
195
+ }
196
+
197
+ // TODO: !!!! DRY !!!
198
+ function flatPages(
199
+ sidebar: (SidebarRoute | Sidebar | string)[],
200
+ groups: { [key: string]: string },
201
+ resp: string[] = [],
202
+ ) {
203
+ sidebar.map(async side => {
204
+ if (typeof side === "string") {
205
+ resp.push(side)
206
+ return
207
+ }
208
+
209
+ if ("route" in side) {
210
+ side.pages?.map(item => {
211
+ return flatPages([item], groups, resp)
212
+ })
213
+ return
214
+ }
215
+
216
+ // Type guard to check if it's a Sidebar
217
+ if ("group" in side) {
218
+ const groupKey = side.group || "";
219
+ if (groups[groupKey]) {
220
+ const link = groups[groupKey];
221
+ if (link) {
222
+ resp.push(link);
223
+ }
224
+ }
225
+
226
+ side.pages?.map(async page => {
227
+ if (typeof page === "string") {
228
+ resp.push(page);
229
+ return;
230
+ }
231
+
232
+ if ("virtual" in page && page.virtual) {
233
+ resp.push(page.virtual);
234
+ return;
235
+ }
236
+
237
+ if ("pages" in page) {
238
+ return flatPages([page as Sidebar], groups, resp);
239
+ }
240
+ });
241
+ }
242
+ });
243
+
244
+ return resp;
245
+ }
246
+
247
+ function slugify(text: string) {
248
+ const slugger = new GitHubSlugger()
249
+
250
+ return slugger.slug(text)
251
+ }
@@ -0,0 +1,36 @@
1
+ export interface DocSectionSchema { // TODO: move to @xyd-js/plugins/search ?
2
+ /**
3
+ * Id of the page e.g. "getting-started"
4
+ */
5
+ pageId: string
6
+
7
+ /**
8
+ * URL of the page e.g. "/getting-started"
9
+ */
10
+ pageUrl: string
11
+
12
+ /**
13
+ * Title of the page e.g. "Getting Started"
14
+ */
15
+ pageTitle: string
16
+
17
+ /**
18
+ * Level of the heading e.g. 1, 2
19
+ */
20
+ headingLevel: number
21
+
22
+ /**
23
+ * Title of the heading e.g. "Making a new project"
24
+ */
25
+ headingTitle: string
26
+
27
+ /**
28
+ * Summary of the page e.g. "This is the summary of the page"
29
+ */
30
+ summary: string
31
+
32
+ /**
33
+ * Content of the section
34
+ */
35
+ content: string
36
+ }