@xyd-js/content 0.0.0-build
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/CHANGELOG.md +14 -0
- package/ISSUES.md +1 -0
- package/LICENSE +21 -0
- package/README.md +3 -0
- package/TODO.md +2 -0
- package/dist/index.d.ts +28 -0
- package/dist/index.js +1625 -0
- package/dist/index.js.map +1 -0
- package/dist/md.d.ts +72 -0
- package/dist/md.js +23508 -0
- package/dist/md.js.map +1 -0
- package/dist/mdToc-NBBxMJ4l.d.ts +12 -0
- package/dist/vite.d.ts +1066 -0
- package/dist/vite.js +20156 -0
- package/dist/vite.js.map +1 -0
- package/package.json +67 -0
- package/packages/md/index.ts +25 -0
- package/packages/md/plugins/component-directives/index.ts +3 -0
- package/packages/md/plugins/component-directives/mdComponentDirective.ts +577 -0
- package/packages/md/plugins/component-directives/types.ts +1 -0
- package/packages/md/plugins/component-directives/utils.ts +27 -0
- package/packages/md/plugins/composer/__fixtures__/1.single-example/input.md +7 -0
- package/packages/md/plugins/composer/__fixtures__/1.single-example/output.json +63 -0
- package/packages/md/plugins/composer/__fixtures__/2.single-example-with-name/input.md +7 -0
- package/packages/md/plugins/composer/__fixtures__/2.single-example-with-name/output.json +63 -0
- package/packages/md/plugins/composer/__fixtures__/3.multiple-examples/input.md +15 -0
- package/packages/md/plugins/composer/__fixtures__/3.multiple-examples/output.json +122 -0
- package/packages/md/plugins/composer/__fixtures__/4.example-groups/input.md +23 -0
- package/packages/md/plugins/composer/__fixtures__/4.example-groups/output.json +184 -0
- package/packages/md/plugins/composer/__tests__/mdComposer.test.ts +41 -0
- package/packages/md/plugins/composer/__tests__/testHelpers.ts +48 -0
- package/packages/md/plugins/composer/index.ts +1 -0
- package/packages/md/plugins/composer/mdComposer.ts +146 -0
- package/packages/md/plugins/developer-writing/index.ts +3 -0
- package/packages/md/plugins/developer-writing/mdCodeRehype.ts +81 -0
- package/packages/md/plugins/functions/__fixtures__/external.ts +4 -0
- package/packages/md/plugins/functions/__fixtures__/test-include.md +31 -0
- package/packages/md/plugins/functions/__fixtures__/test.js +11 -0
- package/packages/md/plugins/functions/__fixtures__/test.py +9 -0
- package/packages/md/plugins/functions/__fixtures__/test.ts +18 -0
- package/packages/md/plugins/functions/__tests__/mdFunctionImportCode.test.ts +314 -0
- package/packages/md/plugins/functions/__tests__/mdFunctionInclude.test.ts +44 -0
- package/packages/md/plugins/functions/__tests__/parseFunctionCall.test.ts +70 -0
- package/packages/md/plugins/functions/__tests__/testHelpers.ts +95 -0
- package/packages/md/plugins/functions/index.ts +15 -0
- package/packages/md/plugins/functions/mdFunctionChangelog.ts +135 -0
- package/packages/md/plugins/functions/mdFunctionImportCode.ts +92 -0
- package/packages/md/plugins/functions/mdFunctionInclude.ts +119 -0
- package/packages/md/plugins/functions/mdFunctionUniform.ts +79 -0
- package/packages/md/plugins/functions/types.ts +9 -0
- package/packages/md/plugins/functions/uniformProcessor.ts +349 -0
- package/packages/md/plugins/functions/utils.ts +457 -0
- package/packages/md/plugins/index.ts +125 -0
- package/packages/md/plugins/mdCode.ts +16 -0
- package/packages/md/plugins/mdHeadingId.ts +47 -0
- package/packages/md/plugins/mdImage.test.ts +59 -0
- package/packages/md/plugins/mdImage.ts +55 -0
- package/packages/md/plugins/mdImageRehype.ts +13 -0
- package/packages/md/plugins/mdPage.ts +35 -0
- package/packages/md/plugins/mdThemeSettings.ts +34 -0
- package/packages/md/plugins/mdToc.ts +229 -0
- package/packages/md/plugins/meta/index.ts +1 -0
- package/packages/md/plugins/meta/mdMeta.ts +198 -0
- package/packages/md/plugins/output-variables/__fixtures__/1.simple/input.md +22 -0
- package/packages/md/plugins/output-variables/__fixtures__/1.simple/output.json +191 -0
- package/packages/md/plugins/output-variables/__fixtures__/2.multiple-vars/input.md +21 -0
- package/packages/md/plugins/output-variables/__fixtures__/2.multiple-vars/output.json +127 -0
- package/packages/md/plugins/output-variables/__tests__/index.test.ts +28 -0
- package/packages/md/plugins/output-variables/__tests__/testHelpers.ts +36 -0
- package/packages/md/plugins/output-variables/index.ts +1 -0
- package/packages/md/plugins/output-variables/lib/const.ts +4 -0
- package/packages/md/plugins/output-variables/lib/factoryAttributes.ts +350 -0
- package/packages/md/plugins/output-variables/lib/factoryLabel.ts +135 -0
- package/packages/md/plugins/output-variables/lib/factoryName.ts +59 -0
- package/packages/md/plugins/output-variables/lib/index.ts +21 -0
- package/packages/md/plugins/output-variables/lib/outputVarsContainer.ts +328 -0
- package/packages/md/plugins/output-variables/lib/util.ts +494 -0
- package/packages/md/plugins/output-variables/remarkOutputVars.ts +22 -0
- package/packages/md/plugins/recmaOverrideComponents.ts +74 -0
- package/packages/md/plugins/rehypeHeading.ts +58 -0
- package/packages/md/plugins/types.ts +15 -0
- package/packages/md/plugins/utils/componentLike.ts +76 -0
- package/packages/md/plugins/utils/index.ts +2 -0
- package/packages/md/plugins/utils/injectCodeMeta.ts +59 -0
- package/packages/md/plugins/utils/mdParameters.test.ts +114 -0
- package/packages/md/plugins/utils/mdParameters.ts +249 -0
- package/packages/md/plugins/utils/mdastTypes.ts +42 -0
- package/packages/md/search/index.ts +257 -0
- package/packages/md/search/types.ts +36 -0
- package/packages/vite/index.ts +20 -0
- package/src/fs.ts +81 -0
- package/src/index.ts +7 -0
- package/src/navigation.ts +147 -0
- package/src/types.ts +8 -0
- package/tsconfig.json +49 -0
- package/tsup.config.ts +32 -0
- 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,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
|
+
}
|