@xyd-js/content 0.1.0-build.171

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 (97) hide show
  1. package/CHANGELOG.md +2174 -0
  2. package/ISSUES.md +1 -0
  3. package/LICENSE +21 -0
  4. package/README.md +3 -0
  5. package/TODO.md +2 -0
  6. package/dist/index.d.ts +28 -0
  7. package/dist/index.js +1625 -0
  8. package/dist/index.js.map +1 -0
  9. package/dist/md.d.ts +72 -0
  10. package/dist/md.js +23508 -0
  11. package/dist/md.js.map +1 -0
  12. package/dist/mdToc-NBBxMJ4l.d.ts +12 -0
  13. package/dist/vite.d.ts +1066 -0
  14. package/dist/vite.js +20156 -0
  15. package/dist/vite.js.map +1 -0
  16. package/package.json +67 -0
  17. package/packages/md/index.ts +25 -0
  18. package/packages/md/plugins/component-directives/index.ts +3 -0
  19. package/packages/md/plugins/component-directives/mdComponentDirective.ts +577 -0
  20. package/packages/md/plugins/component-directives/types.ts +1 -0
  21. package/packages/md/plugins/component-directives/utils.ts +27 -0
  22. package/packages/md/plugins/composer/__fixtures__/1.single-example/input.md +7 -0
  23. package/packages/md/plugins/composer/__fixtures__/1.single-example/output.json +63 -0
  24. package/packages/md/plugins/composer/__fixtures__/2.single-example-with-name/input.md +7 -0
  25. package/packages/md/plugins/composer/__fixtures__/2.single-example-with-name/output.json +63 -0
  26. package/packages/md/plugins/composer/__fixtures__/3.multiple-examples/input.md +15 -0
  27. package/packages/md/plugins/composer/__fixtures__/3.multiple-examples/output.json +122 -0
  28. package/packages/md/plugins/composer/__fixtures__/4.example-groups/input.md +23 -0
  29. package/packages/md/plugins/composer/__fixtures__/4.example-groups/output.json +184 -0
  30. package/packages/md/plugins/composer/__tests__/mdComposer.test.ts +41 -0
  31. package/packages/md/plugins/composer/__tests__/testHelpers.ts +48 -0
  32. package/packages/md/plugins/composer/index.ts +1 -0
  33. package/packages/md/plugins/composer/mdComposer.ts +146 -0
  34. package/packages/md/plugins/developer-writing/index.ts +3 -0
  35. package/packages/md/plugins/developer-writing/mdCodeRehype.ts +81 -0
  36. package/packages/md/plugins/functions/__fixtures__/external.ts +4 -0
  37. package/packages/md/plugins/functions/__fixtures__/test-include.md +31 -0
  38. package/packages/md/plugins/functions/__fixtures__/test.js +11 -0
  39. package/packages/md/plugins/functions/__fixtures__/test.py +9 -0
  40. package/packages/md/plugins/functions/__fixtures__/test.ts +18 -0
  41. package/packages/md/plugins/functions/__tests__/mdFunctionImportCode.test.ts +314 -0
  42. package/packages/md/plugins/functions/__tests__/mdFunctionInclude.test.ts +44 -0
  43. package/packages/md/plugins/functions/__tests__/parseFunctionCall.test.ts +70 -0
  44. package/packages/md/plugins/functions/__tests__/testHelpers.ts +95 -0
  45. package/packages/md/plugins/functions/index.ts +15 -0
  46. package/packages/md/plugins/functions/mdFunctionChangelog.ts +135 -0
  47. package/packages/md/plugins/functions/mdFunctionImportCode.ts +92 -0
  48. package/packages/md/plugins/functions/mdFunctionInclude.ts +119 -0
  49. package/packages/md/plugins/functions/mdFunctionUniform.ts +79 -0
  50. package/packages/md/plugins/functions/types.ts +9 -0
  51. package/packages/md/plugins/functions/uniformProcessor.ts +349 -0
  52. package/packages/md/plugins/functions/utils.ts +457 -0
  53. package/packages/md/plugins/index.ts +125 -0
  54. package/packages/md/plugins/mdCode.ts +16 -0
  55. package/packages/md/plugins/mdHeadingId.ts +47 -0
  56. package/packages/md/plugins/mdImage.test.ts +59 -0
  57. package/packages/md/plugins/mdImage.ts +55 -0
  58. package/packages/md/plugins/mdImageRehype.ts +13 -0
  59. package/packages/md/plugins/mdPage.ts +35 -0
  60. package/packages/md/plugins/mdThemeSettings.ts +34 -0
  61. package/packages/md/plugins/mdToc.ts +229 -0
  62. package/packages/md/plugins/meta/index.ts +1 -0
  63. package/packages/md/plugins/meta/mdMeta.ts +198 -0
  64. package/packages/md/plugins/output-variables/__fixtures__/1.simple/input.md +22 -0
  65. package/packages/md/plugins/output-variables/__fixtures__/1.simple/output.json +191 -0
  66. package/packages/md/plugins/output-variables/__fixtures__/2.multiple-vars/input.md +21 -0
  67. package/packages/md/plugins/output-variables/__fixtures__/2.multiple-vars/output.json +127 -0
  68. package/packages/md/plugins/output-variables/__tests__/index.test.ts +28 -0
  69. package/packages/md/plugins/output-variables/__tests__/testHelpers.ts +36 -0
  70. package/packages/md/plugins/output-variables/index.ts +1 -0
  71. package/packages/md/plugins/output-variables/lib/const.ts +4 -0
  72. package/packages/md/plugins/output-variables/lib/factoryAttributes.ts +350 -0
  73. package/packages/md/plugins/output-variables/lib/factoryLabel.ts +135 -0
  74. package/packages/md/plugins/output-variables/lib/factoryName.ts +59 -0
  75. package/packages/md/plugins/output-variables/lib/index.ts +21 -0
  76. package/packages/md/plugins/output-variables/lib/outputVarsContainer.ts +328 -0
  77. package/packages/md/plugins/output-variables/lib/util.ts +494 -0
  78. package/packages/md/plugins/output-variables/remarkOutputVars.ts +22 -0
  79. package/packages/md/plugins/recmaOverrideComponents.ts +74 -0
  80. package/packages/md/plugins/rehypeHeading.ts +58 -0
  81. package/packages/md/plugins/types.ts +15 -0
  82. package/packages/md/plugins/utils/componentLike.ts +76 -0
  83. package/packages/md/plugins/utils/index.ts +2 -0
  84. package/packages/md/plugins/utils/injectCodeMeta.ts +59 -0
  85. package/packages/md/plugins/utils/mdParameters.test.ts +114 -0
  86. package/packages/md/plugins/utils/mdParameters.ts +249 -0
  87. package/packages/md/plugins/utils/mdastTypes.ts +42 -0
  88. package/packages/md/search/index.ts +257 -0
  89. package/packages/md/search/types.ts +36 -0
  90. package/packages/vite/index.ts +20 -0
  91. package/src/fs.ts +81 -0
  92. package/src/index.ts +7 -0
  93. package/src/navigation.ts +147 -0
  94. package/src/types.ts +8 -0
  95. package/tsconfig.json +49 -0
  96. package/tsup.config.ts +32 -0
  97. package/vitest.config.ts +17 -0
@@ -0,0 +1,76 @@
1
+ import * as fs from 'fs';
2
+ import { mdxJsxFromMarkdown } from 'mdast-util-mdx-jsx';
3
+ import * as React from 'react';
4
+ import acornJsx from 'acorn-jsx';
5
+ import { fromMarkdown } from 'mdast-util-from-markdown'
6
+ import { mdxJsx } from 'micromark-extension-mdx-jsx'
7
+ import * as acorn from 'acorn';
8
+ import reactElementToJSXString from 'react-element-to-jsx-string';
9
+
10
+ const acornWithJsx = acorn.Parser.extend(acornJsx());
11
+
12
+ export function componentLike(
13
+ componentName: string,
14
+ props: Record<string, any>,
15
+ children: any[]
16
+ ) {
17
+ console.time('componentLike:total');
18
+
19
+ console.time('componentLike:createElement');
20
+ // Ensure proper escaping in props and children before creating the React element
21
+ const escapedProps = ensureProperEscaping(props);
22
+ const escapedChildren = ensureProperEscaping(children);
23
+ const reactElement = React.createElement(componentName, escapedProps, ...escapedChildren);
24
+ console.timeEnd('componentLike:createElement');
25
+
26
+ // Convert the React element to a JSX string
27
+ console.time('componentLike:toJSXString');
28
+ // @ts-ignore - The default property exists at runtime
29
+ const mdxString = reactElementToJSXString.default(reactElement);
30
+ console.timeEnd('componentLike:toJSXString');
31
+
32
+ // Parse the JSX string to get proper MDX attributes
33
+ console.time('componentLike:fromMarkdown');
34
+
35
+ const ast = fromMarkdown(mdxString, {
36
+ extensions: [mdxJsx({
37
+ acorn: acornWithJsx,
38
+ addResult: true
39
+ })],
40
+ mdastExtensions: [mdxJsxFromMarkdown()]
41
+ });
42
+ console.timeEnd('componentLike:fromMarkdown');
43
+
44
+ console.timeEnd('componentLike:total');
45
+ return ast
46
+ }
47
+
48
+
49
+ /**
50
+ * Recursively ensures proper backslash escaping in string values.
51
+ * Fixes backslashes like `curl --request POST \` into double `\\` to fix JSON format
52
+ */
53
+ function ensureProperEscaping(obj: any): any {
54
+ if (React.isValidElement(obj)) {
55
+ return obj
56
+ }
57
+ // return obj
58
+ if (typeof obj === 'string') {
59
+ // Replace any single backslashes that aren't already part of an escape sequence
60
+ return obj.replace(/(?<!\\)\\(?!\\)/g, '\\\\');
61
+ }
62
+
63
+ if (Array.isArray(obj)) {
64
+ return obj.map(item => ensureProperEscaping(item));
65
+ }
66
+
67
+ if (obj && typeof obj === 'object') {
68
+ const result = { ...obj };
69
+ for (const key in result) {
70
+ result[key] = ensureProperEscaping(result[key]);
71
+ }
72
+ return result;
73
+ }
74
+
75
+ return obj;
76
+ }
@@ -0,0 +1,2 @@
1
+ export * from './componentLike';
2
+ export * from './mdastTypes';
@@ -0,0 +1,59 @@
1
+ import { parseImportPath } from "../functions/utils";
2
+ import { mdParameters } from "./mdParameters";
3
+
4
+ /**
5
+ * Injects code meta and props into a code node
6
+ */
7
+ export function injectCodeMeta(node: any, metaString?: string) {
8
+ const { filePath, regions, lineRanges } = parseImportPath(node.lang || "") || {};
9
+ node.lang = filePath;
10
+ node.data = node.data || {};
11
+ node.data.regions = regions;
12
+ node.data.lineRanges = lineRanges;
13
+
14
+ const { attributes, sanitizedText: defaultMetaTitle } = mdParameters(metaString || node.meta || "", {
15
+ htmlMd: true
16
+ });
17
+ node.meta = defaultMetaTitle;
18
+
19
+ const props: { [prop: string]: any } = {
20
+ regions: JSON.stringify(node.data.regions),
21
+ lineRanges: JSON.stringify(node.data.lineRanges)
22
+ };
23
+ props.meta = node.lang || "";
24
+ props.title = defaultMetaTitle;
25
+
26
+ if (attributes && attributes.lines === "true") {
27
+ props.lineNumbers = "true";
28
+ }
29
+ if (attributes && attributes.scroll === "false") {
30
+ props.size = "full";
31
+ }
32
+ if (attributes && attributes.scroll === "true") {
33
+ props.size = "";
34
+ }
35
+ if (attributes && attributes.descHead && attributes.descHead !== "false") {
36
+ props.descriptionHead = attributes.descHead;
37
+ }
38
+ if (attributes && attributes.desc && attributes.desc !== "false") {
39
+ props.descriptionContent = attributes.desc;
40
+ }
41
+ if (attributes && attributes.descIcon && attributes.descIcon !== "false") {
42
+ props.descriptionIcon = attributes.descIcon;
43
+ }
44
+ if (attributes && attributes.meta && attributes.meta !== "false") {
45
+ props.meta = attributes.meta;
46
+ }
47
+ if (attributes && attributes.title && attributes.title !== "false") {
48
+ props.title = attributes.title;
49
+ }
50
+ if (attributes?.title === "false") {
51
+ props.title = "";
52
+ }
53
+
54
+ node.data.hProperties = {
55
+ ...(node.data.hProperties || {}),
56
+ ...props
57
+ };
58
+ }
59
+
@@ -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
+ }