@mintlify/common 1.0.1115 → 1.0.1117
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/dist/mdx/getMDXOptions.js +3 -0
- package/dist/mdx/lib/remark-utils.d.ts +4 -0
- package/dist/mdx/lib/remark-utils.js +7 -0
- package/dist/mdx/plugins/remark/remarkComponentIds.js +5 -5
- package/dist/mdx/plugins/remark/remarkExtractTableOfContents.js +11 -14
- package/dist/mdx/plugins/remark/remarkTitleMarkdown.d.ts +3 -0
- package/dist/mdx/plugins/remark/remarkTitleMarkdown.js +83 -0
- package/dist/mdx/snippets/resolveImport/resolveComponentWithContent.js +70 -10
- package/dist/mdx/titleMarkdown.d.ts +3 -0
- package/dist/mdx/titleMarkdown.js +90 -0
- package/dist/tsconfig.build.tsbuildinfo +1 -1
- package/package.json +6 -2
|
@@ -3,6 +3,7 @@ import { remarkMdxExpandExpressions, } from './plugins/remark/remarkMdxExpandExp
|
|
|
3
3
|
import { remarkMdxRemoveUnknownJsx } from './plugins/remark/remarkMdxRemoveUnknownJsx/index.js';
|
|
4
4
|
import { remarkMermaid } from './plugins/remark/remarkMermaid.js';
|
|
5
5
|
import { remarkNoCopyLanguage } from './plugins/remark/remarkNoCopyLanguage.js';
|
|
6
|
+
import { remarkParseTitleMarkdown, remarkRenderTitleMarkdown, } from './plugins/remark/remarkTitleMarkdown.js';
|
|
6
7
|
// avoid running extractors unnecessarily
|
|
7
8
|
const rehypeExtractors = (mdxExtracts, data) => {
|
|
8
9
|
if (!mdxExtracts)
|
|
@@ -30,6 +31,7 @@ export const getMDXOptions = ({ data, remarkPlugins = [], rehypePlugins = [], md
|
|
|
30
31
|
],
|
|
31
32
|
remarkMdxStyleStringToObject,
|
|
32
33
|
[remarkResolveRelativeLinks, { path: data.path }],
|
|
34
|
+
remarkParseTitleMarkdown,
|
|
33
35
|
remarkPrompt,
|
|
34
36
|
[remarkComponentIds, data.pageMetadata],
|
|
35
37
|
remarkUnwrapJsxHeadings,
|
|
@@ -46,6 +48,7 @@ export const getMDXOptions = ({ data, remarkPlugins = [], rehypePlugins = [], md
|
|
|
46
48
|
remarkNoCopyLanguage,
|
|
47
49
|
remarkVideo,
|
|
48
50
|
...remarkPlugins,
|
|
51
|
+
remarkRenderTitleMarkdown,
|
|
49
52
|
remarkMapExplicitAnchors,
|
|
50
53
|
[remarkMdxRemoveUnknownJsx, { allowlist: [...allowedComponents, ...knownComponents] }],
|
|
51
54
|
[remarkMdxClientComponentBoundaries, { allowlist: knownComponents }],
|
|
@@ -1,8 +1,12 @@
|
|
|
1
1
|
import type { RootContent as HastRootContent } from 'hast';
|
|
2
2
|
import type { RootContent as MdastRootContent } from 'mdast';
|
|
3
3
|
import type { MdxJsxAttribute, MdxJsxAttributeValueExpression } from 'mdast-util-mdx-jsx';
|
|
4
|
+
import type { Data } from 'unist';
|
|
4
5
|
type RootContent = MdastRootContent | HastRootContent;
|
|
5
6
|
export declare const createMdxJsxAttribute: (key: string, value: MdxJsxAttributeValueExpression | string | null | undefined | number | boolean) => MdxJsxAttribute;
|
|
7
|
+
export declare function getTitleMarkdownText(node: {
|
|
8
|
+
data?: Data;
|
|
9
|
+
}, fallback: string): string;
|
|
6
10
|
export declare function getUnicodeId(title: string): string;
|
|
7
11
|
export declare const getTableOfContentsTitle: (node: RootContent, index?: number, children?: RootContent[]) => string;
|
|
8
12
|
export {};
|
|
@@ -7,6 +7,13 @@ export const createMdxJsxAttribute = (key, value) => {
|
|
|
7
7
|
value: value,
|
|
8
8
|
};
|
|
9
9
|
};
|
|
10
|
+
export function getTitleMarkdownText(node, fallback) {
|
|
11
|
+
const nodeData = node.data;
|
|
12
|
+
const data = nodeData && 'mintlifyTitleMarkdown' in nodeData ? nodeData.mintlifyTitleMarkdown : undefined;
|
|
13
|
+
return data && typeof data === 'object' && 'text' in data && typeof data.text === 'string'
|
|
14
|
+
? data.text
|
|
15
|
+
: fallback;
|
|
16
|
+
}
|
|
10
17
|
export function getUnicodeId(title) {
|
|
11
18
|
return encodeURIComponent(title.toLowerCase().trim().replace(/\s+/g, '-'));
|
|
12
19
|
}
|
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
import defaultSlugify, { slugifyWithCounter } from '@sindresorhus/slugify';
|
|
2
2
|
import { visit } from 'unist-util-visit';
|
|
3
3
|
import { slugify } from '../../../slugify.js';
|
|
4
|
-
import { createMdxJsxAttribute } from '../../lib/remark-utils.js';
|
|
5
|
-
import { getTableOfContentsTitle } from '../../lib/remark-utils.js';
|
|
4
|
+
import { createMdxJsxAttribute, getTableOfContentsTitle, getTitleMarkdownText, } from '../../lib/remark-utils.js';
|
|
6
5
|
export function generateComponentId(title, count) {
|
|
7
6
|
const base = defaultSlugify(title.replace(':', '-'), { decamelize: false });
|
|
8
7
|
return count != null && count > 0 ? `${base}-${count}` : base;
|
|
@@ -65,14 +64,15 @@ export const remarkComponentIds = (pageMetadata) => (tree) => {
|
|
|
65
64
|
else {
|
|
66
65
|
const title = (_b = node.attributes.find((attr) => 'name' in attr && attr.name === 'title')) === null || _b === void 0 ? void 0 : _b.value;
|
|
67
66
|
if (title && typeof title === 'string') {
|
|
67
|
+
const visibleTitle = getTitleMarkdownText(node, title);
|
|
68
68
|
// Re-slug (bumping the counter) past any id an explicit prop claimed.
|
|
69
69
|
// usedTabIds only guards against explicit ids; uniqueness among
|
|
70
70
|
// title-derived slugs is owned by tabSlugifyFn, which monotonically
|
|
71
71
|
// advances and never repeats a value, so the slots burned by this
|
|
72
72
|
// loop don't need to be tracked here.
|
|
73
|
-
let slug = slugify(
|
|
73
|
+
let slug = slugify(visibleTitle, tabSlugifyFn);
|
|
74
74
|
while (usedTabIds.has(slug))
|
|
75
|
-
slug = slugify(
|
|
75
|
+
slug = slugify(visibleTitle, tabSlugifyFn);
|
|
76
76
|
usedTabIds.add(slug);
|
|
77
77
|
tabSlugs.set(node, slug);
|
|
78
78
|
}
|
|
@@ -164,7 +164,7 @@ export const remarkComponentIds = (pageMetadata) => (tree) => {
|
|
|
164
164
|
const hasId = node.attributes.some((attr) => 'name' in attr && attr.name === 'id');
|
|
165
165
|
if (hasId)
|
|
166
166
|
return;
|
|
167
|
-
const title = titleAttr.value;
|
|
167
|
+
const title = getTitleMarkdownText(node, titleAttr.value);
|
|
168
168
|
const baseSlug = generateComponentId(title);
|
|
169
169
|
const count = (_a = accordionCounts.get(baseSlug)) !== null && _a !== void 0 ? _a : 0;
|
|
170
170
|
accordionCounts.set(baseSlug, count + 1);
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { visit } from 'unist-util-visit';
|
|
2
2
|
import { safeCleanHeadingId, slugify } from '../../../slugify.js';
|
|
3
|
-
import { createMdxJsxAttribute, getTableOfContentsTitle } from '../../lib/remark-utils.js';
|
|
3
|
+
import { createMdxJsxAttribute, getTableOfContentsTitle, getTitleMarkdownText, } from '../../lib/remark-utils.js';
|
|
4
4
|
import { isMdxJsxFragment } from '../../utils.js';
|
|
5
5
|
import { AVOIDED_PAGE_MODES, HEADING_LEVELS } from './remarkComponentIds.js';
|
|
6
6
|
import { isHiddenFromHumans, isVisibilityElement, resolveVisibilityFor, } from './remarkVisibilityForMarkdown.js';
|
|
@@ -95,7 +95,7 @@ export const remarkExtractTableOfContents = (mdxExtracts, pageMetadata) => {
|
|
|
95
95
|
if (node.type === 'mdxJsxFlowElement' && node.name === 'Tab') {
|
|
96
96
|
const idValue = getStringAttribute(node.attributes, 'id');
|
|
97
97
|
const titleValue = getStringAttribute(node.attributes, 'title');
|
|
98
|
-
const tabId = idValue !== null && idValue !== void 0 ? idValue : (titleValue ? slugify(titleValue) : undefined);
|
|
98
|
+
const tabId = idValue !== null && idValue !== void 0 ? idValue : (titleValue ? slugify(getTitleMarkdownText(node, titleValue)) : undefined);
|
|
99
99
|
if (tabId) {
|
|
100
100
|
visit(node, (childNode) => {
|
|
101
101
|
tabContentMap.set(childNode, tabId);
|
|
@@ -123,7 +123,7 @@ export const remarkExtractTableOfContents = (mdxExtracts, pageMetadata) => {
|
|
|
123
123
|
}
|
|
124
124
|
});
|
|
125
125
|
visit(tree, (node) => {
|
|
126
|
-
var _a, _b, _c, _d, _e, _f
|
|
126
|
+
var _a, _b, _c, _d, _e, _f;
|
|
127
127
|
if (excludedNodes.has(node))
|
|
128
128
|
return;
|
|
129
129
|
const currentTabId = tabContentMap.get(node);
|
|
@@ -144,14 +144,17 @@ export const remarkExtractTableOfContents = (mdxExtracts, pageMetadata) => {
|
|
|
144
144
|
(attr.value == null ||
|
|
145
145
|
attr.value === 'true' ||
|
|
146
146
|
(typeof attr.value === 'object' && attr.value.value === 'true')));
|
|
147
|
+
const stepTitle = node.type === 'mdxJsxFlowElement' && node.name === 'Step'
|
|
148
|
+
? getTitleMarkdownText(node, (_c = getStringAttribute(node.attributes, 'title')) !== null && _c !== void 0 ? _c : '')
|
|
149
|
+
: undefined;
|
|
147
150
|
const isValidStep = node.type === 'mdxJsxFlowElement' &&
|
|
148
151
|
node.name === 'Step' &&
|
|
149
152
|
!hasNoAnchor &&
|
|
150
153
|
['h2', 'h3'].includes(stepTitleSize !== null && stepTitleSize !== void 0 ? stepTitleSize : '') &&
|
|
151
|
-
(
|
|
154
|
+
(stepTitle === null || stepTitle === void 0 ? void 0 : stepTitle.trim()) !== '';
|
|
152
155
|
const hasIdAttribute = node.type === 'mdxJsxFlowElement' &&
|
|
153
156
|
node.attributes.some((attr) => 'name' in attr && attr.name === 'id');
|
|
154
|
-
const isAvoidedPageMode = AVOIDED_PAGE_MODES.includes((
|
|
157
|
+
const isAvoidedPageMode = AVOIDED_PAGE_MODES.includes((_d = pageMetadata === null || pageMetadata === void 0 ? void 0 : pageMetadata.mode) !== null && _d !== void 0 ? _d : '');
|
|
155
158
|
if (!isValidHeading &&
|
|
156
159
|
!isValidMdxHeading &&
|
|
157
160
|
!isTransformedHeading &&
|
|
@@ -169,7 +172,7 @@ export const remarkExtractTableOfContents = (mdxExtracts, pageMetadata) => {
|
|
|
169
172
|
}
|
|
170
173
|
else if ('name' in node && node.name === 'Step') {
|
|
171
174
|
const titleSizeToLevel = { h2: 2, h3: 3, h4: 4 };
|
|
172
|
-
level = (
|
|
175
|
+
level = (_e = titleSizeToLevel[stepTitleSize !== null && stepTitleSize !== void 0 ? stepTitleSize : '']) !== null && _e !== void 0 ? _e : 3;
|
|
173
176
|
// @ts-expect-error we're assigning to depth despite the node not containing depth in the type
|
|
174
177
|
node.depth = level;
|
|
175
178
|
}
|
|
@@ -182,7 +185,7 @@ export const remarkExtractTableOfContents = (mdxExtracts, pageMetadata) => {
|
|
|
182
185
|
level = levelAttr.value;
|
|
183
186
|
}
|
|
184
187
|
}
|
|
185
|
-
else if ('name' in node && ((
|
|
188
|
+
else if ('name' in node && ((_f = node.name) === null || _f === void 0 ? void 0 : _f[1])) {
|
|
186
189
|
const num = Number(node.name[1]);
|
|
187
190
|
level = !isNaN(num) ? num : undefined;
|
|
188
191
|
if (level !== undefined) {
|
|
@@ -190,13 +193,7 @@ export const remarkExtractTableOfContents = (mdxExtracts, pageMetadata) => {
|
|
|
190
193
|
node.depth = level;
|
|
191
194
|
}
|
|
192
195
|
}
|
|
193
|
-
|
|
194
|
-
if ('name' in node && node.name === 'Step') {
|
|
195
|
-
title = (_h = getStringAttribute(node.attributes, 'title')) !== null && _h !== void 0 ? _h : getTableOfContentsTitle(node);
|
|
196
|
-
}
|
|
197
|
-
else {
|
|
198
|
-
title = getTableOfContentsTitle(node);
|
|
199
|
-
}
|
|
196
|
+
const title = stepTitle !== null && stepTitle !== void 0 ? stepTitle : getTableOfContentsTitle(node);
|
|
200
197
|
let slug;
|
|
201
198
|
const explicitId = ('name' in node &&
|
|
202
199
|
(node.name === 'Heading' || node.name === 'Step' || node.name === 'Update')) ||
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import { Parser } from 'acorn';
|
|
2
|
+
import jsx from 'acorn-jsx';
|
|
3
|
+
import { visit } from 'unist-util-visit';
|
|
4
|
+
import { parseTitleMarkdown, titleMarkdownToText } from '../../titleMarkdown.js';
|
|
5
|
+
const JSXParser = Parser.extend(jsx());
|
|
6
|
+
const TITLE_COMPONENTS = new Set(['Accordion', 'Card', 'Color.Row', 'Step', 'Tab', 'Tile']);
|
|
7
|
+
const TITLE_DATA_KEY = 'mintlifyTitleMarkdown';
|
|
8
|
+
function nodeJsx(node) {
|
|
9
|
+
if (node.type === 'text')
|
|
10
|
+
return `{${JSON.stringify(node.value)}}`;
|
|
11
|
+
if (node.type === 'inlineCode')
|
|
12
|
+
return `<code>{${JSON.stringify(node.value)}}</code>`;
|
|
13
|
+
if (!('children' in node))
|
|
14
|
+
return '';
|
|
15
|
+
const children = node.children.map(nodeJsx).join('');
|
|
16
|
+
if (node.type === 'link')
|
|
17
|
+
return children;
|
|
18
|
+
const tag = node.type === 'emphasis' ? 'em' : node.type === 'delete' ? 'del' : 'strong';
|
|
19
|
+
return `<${tag}${tag === 'strong' ? ' className="font-extrabold"' : ''}>${children}</${tag}>`;
|
|
20
|
+
}
|
|
21
|
+
function titleExpression(value) {
|
|
22
|
+
var _a;
|
|
23
|
+
const nodes = parseTitleMarkdown(value);
|
|
24
|
+
if (!nodes.length || (((_a = nodes[0]) === null || _a === void 0 ? void 0 : _a.type) === 'text' && nodes[0].value === value))
|
|
25
|
+
return;
|
|
26
|
+
const jsxValue = `<>${nodes.map(nodeJsx).join('')}</>`;
|
|
27
|
+
const estree = JSXParser.parse(jsxValue, {
|
|
28
|
+
ecmaVersion: 'latest',
|
|
29
|
+
sourceType: 'module',
|
|
30
|
+
});
|
|
31
|
+
return {
|
|
32
|
+
type: 'mdxJsxAttributeValueExpression',
|
|
33
|
+
value: jsxValue,
|
|
34
|
+
data: { estree },
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
function getTitleAttribute(node) {
|
|
38
|
+
return node.attributes.find((attribute) => attribute.type === 'mdxJsxAttribute' && attribute.name === 'title');
|
|
39
|
+
}
|
|
40
|
+
function getTitleValue(attribute) {
|
|
41
|
+
var _a, _b, _c;
|
|
42
|
+
if (typeof attribute.value === 'string')
|
|
43
|
+
return attribute.value;
|
|
44
|
+
const statement = (_c = (_b = (_a = attribute.value) === null || _a === void 0 ? void 0 : _a.data) === null || _b === void 0 ? void 0 : _b.estree) === null || _c === void 0 ? void 0 : _c.body[0];
|
|
45
|
+
return (statement === null || statement === void 0 ? void 0 : statement.type) === 'ExpressionStatement' &&
|
|
46
|
+
statement.expression.type === 'Literal' &&
|
|
47
|
+
typeof statement.expression.value === 'string'
|
|
48
|
+
? statement.expression.value
|
|
49
|
+
: undefined;
|
|
50
|
+
}
|
|
51
|
+
export const remarkParseTitleMarkdown = () => (tree) => {
|
|
52
|
+
visit(tree, (node) => {
|
|
53
|
+
if (node.type !== 'mdxJsxFlowElement' && node.type !== 'mdxJsxTextElement')
|
|
54
|
+
return;
|
|
55
|
+
if (!node.name || !TITLE_COMPONENTS.has(node.name))
|
|
56
|
+
return;
|
|
57
|
+
const title = getTitleAttribute(node);
|
|
58
|
+
if (!title)
|
|
59
|
+
return;
|
|
60
|
+
const titleValue = getTitleValue(title);
|
|
61
|
+
if (titleValue === undefined)
|
|
62
|
+
return;
|
|
63
|
+
node.data = Object.assign(Object.assign({}, node.data), { [TITLE_DATA_KEY]: {
|
|
64
|
+
text: titleMarkdownToText(titleValue),
|
|
65
|
+
} });
|
|
66
|
+
});
|
|
67
|
+
};
|
|
68
|
+
export const remarkRenderTitleMarkdown = () => (tree) => {
|
|
69
|
+
visit(tree, (node) => {
|
|
70
|
+
var _a;
|
|
71
|
+
if (node.type !== 'mdxJsxFlowElement' && node.type !== 'mdxJsxTextElement')
|
|
72
|
+
return;
|
|
73
|
+
if (!node.name || !TITLE_COMPONENTS.has(node.name))
|
|
74
|
+
return;
|
|
75
|
+
const title = getTitleAttribute(node);
|
|
76
|
+
if (!title)
|
|
77
|
+
return;
|
|
78
|
+
const titleValue = getTitleValue(title);
|
|
79
|
+
if (titleValue === undefined)
|
|
80
|
+
return;
|
|
81
|
+
title.value = (_a = titleExpression(titleValue)) !== null && _a !== void 0 ? _a : title.value;
|
|
82
|
+
});
|
|
83
|
+
};
|
|
@@ -7,6 +7,8 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
|
|
|
7
7
|
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
8
8
|
});
|
|
9
9
|
};
|
|
10
|
+
import { Parser } from 'acorn';
|
|
11
|
+
import acornJsx from 'acorn-jsx';
|
|
10
12
|
import { jsx, toJs } from 'estree-util-to-js';
|
|
11
13
|
import { walk } from 'estree-walker';
|
|
12
14
|
import { visit } from 'unist-util-visit';
|
|
@@ -81,13 +83,23 @@ const replaceVariablesWithProps = (node, treeToInject, exportMap, snippetExportM
|
|
|
81
83
|
return uniqueName;
|
|
82
84
|
};
|
|
83
85
|
/**
|
|
84
|
-
* Rename every free identifier in an expression
|
|
85
|
-
*
|
|
86
|
-
*
|
|
87
|
-
*
|
|
86
|
+
* Rename every free identifier in an expression to its unique name.
|
|
87
|
+
*
|
|
88
|
+
* The renames are spliced into the expression's own source text by position, so everything
|
|
89
|
+
* else — parentheses, indentation, an inline `style={{ … }}` object, the markdown inside an
|
|
90
|
+
* `<MDX>` fragment — stays byte-for-byte as the author wrote it. Regenerating the text from the
|
|
91
|
+
* estree instead pretty-prints nested objects across several lines at column 0, which turns into
|
|
92
|
+
* lazy lines inside a list once an `<MDX>` fragment is re-parsed as markdown. The source is
|
|
93
|
+
* re-parsed with acorn because the mdast estree carries document offsets while `value` has its
|
|
94
|
+
* per-line indent stripped, so the two no longer line up.
|
|
95
|
+
*
|
|
96
|
+
* The estree is renamed in place as well so it never disagrees with the text. Only when the text
|
|
97
|
+
* cannot be parsed on its own is it regenerated from the estree (the previous behaviour).
|
|
98
|
+
* Replacing the first textual occurrence only — the behaviour before that — corrupted any
|
|
99
|
+
* expression that referenced the same prop twice, e.g. `a === "x" ? 1 : a === "y" ? 2 : 3`.
|
|
88
100
|
*/
|
|
89
101
|
const renamed = new WeakSet();
|
|
90
|
-
const
|
|
102
|
+
const renameIdentifiers = (estree, edits) => {
|
|
91
103
|
walk(estree, {
|
|
92
104
|
enter(jsNode, parent) {
|
|
93
105
|
if (jsNode.type !== 'Identifier')
|
|
@@ -102,20 +114,29 @@ const replaceVariablesWithProps = (node, treeToInject, exportMap, snippetExportM
|
|
|
102
114
|
// `obj.prop` and `{ key: value }` — the property name is not a variable reference
|
|
103
115
|
if ((parent === null || parent === void 0 ? void 0 : parent.type) === 'MemberExpression' && parent.property === jsNode && !parent.computed)
|
|
104
116
|
return;
|
|
117
|
+
if ((parent === null || parent === void 0 ? void 0 : parent.type) === 'Property' && parent.shorthand && parent.value === jsNode)
|
|
118
|
+
return;
|
|
105
119
|
if ((parent === null || parent === void 0 ? void 0 : parent.type) === 'Property' && parent.key === jsNode && !parent.computed) {
|
|
106
120
|
if (!parent.shorthand)
|
|
107
121
|
return;
|
|
108
122
|
// `{ prop }` → `{ prop: prop_0 }`
|
|
123
|
+
const uniqueName = getUniqueName(jsNode.name);
|
|
124
|
+
edits === null || edits === void 0 ? void 0 : edits.push(Object.assign(Object.assign({}, positionOf(jsNode)), { text: `${jsNode.name}: ${uniqueName}` }));
|
|
109
125
|
parent.shorthand = false;
|
|
110
|
-
const value = Object.assign(Object.assign({}, jsNode), { name:
|
|
126
|
+
const value = Object.assign(Object.assign({}, jsNode), { name: uniqueName });
|
|
111
127
|
renamed.add(value);
|
|
112
128
|
parent.value = value;
|
|
113
129
|
return;
|
|
114
130
|
}
|
|
115
|
-
|
|
131
|
+
const uniqueName = getUniqueName(jsNode.name);
|
|
132
|
+
edits === null || edits === void 0 ? void 0 : edits.push(Object.assign(Object.assign({}, positionOf(jsNode)), { text: uniqueName }));
|
|
133
|
+
jsNode.name = uniqueName;
|
|
116
134
|
renamed.add(jsNode);
|
|
117
135
|
},
|
|
118
136
|
});
|
|
137
|
+
};
|
|
138
|
+
const scopeIdentifiers = (estree) => {
|
|
139
|
+
renameIdentifiers(estree);
|
|
119
140
|
return generate(estree);
|
|
120
141
|
};
|
|
121
142
|
const scopeSpreadAttribute = (estree) => {
|
|
@@ -123,24 +144,39 @@ const replaceVariablesWithProps = (node, treeToInject, exportMap, snippetExportM
|
|
|
123
144
|
const spread = getSpreadElement(estree);
|
|
124
145
|
return spread ? `...${generate(toProgram(spread.argument))}` : generate(estree);
|
|
125
146
|
};
|
|
147
|
+
/** `{value}` of an expression or attribute; `...rest` of a spread attribute when `spread`. */
|
|
148
|
+
const scopeSource = (source, estree, spread = false) => {
|
|
149
|
+
const text = spread ? `{${source}}` : source;
|
|
150
|
+
const parsed = parseExpression(text);
|
|
151
|
+
if (!parsed)
|
|
152
|
+
return spread ? scopeSpreadAttribute(estree) : scopeIdentifiers(estree);
|
|
153
|
+
const edits = [];
|
|
154
|
+
renameIdentifiers(parsed, edits);
|
|
155
|
+
renameIdentifiers(estree);
|
|
156
|
+
let result = text;
|
|
157
|
+
for (const edit of edits.sort((a, b) => b.start - a.start)) {
|
|
158
|
+
result = result.slice(0, edit.start) + edit.text + result.slice(edit.end);
|
|
159
|
+
}
|
|
160
|
+
return spread ? result.slice(1, -1) : result;
|
|
161
|
+
};
|
|
126
162
|
visit(treeToInject, (node) => {
|
|
127
163
|
var _a, _b, _c;
|
|
128
164
|
if ((node.type === 'mdxTextExpression' || node.type === 'mdxFlowExpression') &&
|
|
129
165
|
((_a = node.data) === null || _a === void 0 ? void 0 : _a.estree)) {
|
|
130
|
-
node.value =
|
|
166
|
+
node.value = scopeSource(node.value, node.data.estree);
|
|
131
167
|
return;
|
|
132
168
|
}
|
|
133
169
|
// props referenced inside JSX attributes of the snippet, e.g. `<Card title={variant === "x" ? "a" : "b"}>`
|
|
134
170
|
if (node.type === 'mdxJsxFlowElement' || node.type === 'mdxJsxTextElement') {
|
|
135
171
|
for (const attribute of node.attributes) {
|
|
136
172
|
if (attribute.type === 'mdxJsxExpressionAttribute' && ((_b = attribute.data) === null || _b === void 0 ? void 0 : _b.estree)) {
|
|
137
|
-
attribute.value =
|
|
173
|
+
attribute.value = scopeSource(attribute.value, attribute.data.estree, true);
|
|
138
174
|
}
|
|
139
175
|
else if (attribute.type === 'mdxJsxAttribute' &&
|
|
140
176
|
attribute.value &&
|
|
141
177
|
typeof attribute.value === 'object' &&
|
|
142
178
|
((_c = attribute.value.data) === null || _c === void 0 ? void 0 : _c.estree)) {
|
|
143
|
-
attribute.value.value =
|
|
179
|
+
attribute.value.value = scopeSource(attribute.value.value, attribute.value.data.estree);
|
|
144
180
|
}
|
|
145
181
|
}
|
|
146
182
|
}
|
|
@@ -170,6 +206,30 @@ const replaceVariablesWithProps = (node, treeToInject, exportMap, snippetExportM
|
|
|
170
206
|
};
|
|
171
207
|
};
|
|
172
208
|
const generate = (estree) => toJs(estree, { handlers: jsx }).value.replace(/;\s*$/, '');
|
|
209
|
+
const JSXParser = Parser.extend(acornJsx());
|
|
210
|
+
/**
|
|
211
|
+
* Parse `text` as a single expression; undefined when it is not one on its own. `preserveParens`
|
|
212
|
+
* so `( … )` around the whole expression — common when a snippet wraps a ternary in `{( … )}` —
|
|
213
|
+
* yields a node spanning the entire text; without it acorn returns the inner expression, whose
|
|
214
|
+
* `end` stops before the closing `)`, and the text would wrongly be treated as unparseable.
|
|
215
|
+
*/
|
|
216
|
+
const parseExpression = (text) => {
|
|
217
|
+
try {
|
|
218
|
+
const expression = JSXParser.parseExpressionAt(text, 0, {
|
|
219
|
+
ecmaVersion: 'latest',
|
|
220
|
+
sourceType: 'module',
|
|
221
|
+
preserveParens: true,
|
|
222
|
+
});
|
|
223
|
+
return text.slice(expression.end).trim() === '' ? expression : undefined;
|
|
224
|
+
}
|
|
225
|
+
catch (_a) {
|
|
226
|
+
return undefined;
|
|
227
|
+
}
|
|
228
|
+
};
|
|
229
|
+
const positionOf = (node) => {
|
|
230
|
+
const { start, end } = node;
|
|
231
|
+
return { start, end };
|
|
232
|
+
};
|
|
173
233
|
const toProgram = (expression) => ({
|
|
174
234
|
type: 'Program',
|
|
175
235
|
sourceType: 'module',
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import { fromMarkdown } from 'mdast-util-from-markdown';
|
|
2
|
+
import { gfmFromMarkdown } from 'mdast-util-gfm';
|
|
3
|
+
import { gfm } from 'micromark-extension-gfm';
|
|
4
|
+
import { getTableOfContentsTitle } from './lib/remark-utils.js';
|
|
5
|
+
function sourceText(node, source) {
|
|
6
|
+
var _a, _b;
|
|
7
|
+
const start = (_a = node.position) === null || _a === void 0 ? void 0 : _a.start.offset;
|
|
8
|
+
const end = (_b = node.position) === null || _b === void 0 ? void 0 : _b.end.offset;
|
|
9
|
+
return typeof start === 'number' && typeof end === 'number' ? source.slice(start, end) : '';
|
|
10
|
+
}
|
|
11
|
+
function appendText(nodes, value) {
|
|
12
|
+
if (!value)
|
|
13
|
+
return;
|
|
14
|
+
const previous = nodes.at(-1);
|
|
15
|
+
if ((previous === null || previous === void 0 ? void 0 : previous.type) === 'text')
|
|
16
|
+
previous.value += value;
|
|
17
|
+
else
|
|
18
|
+
nodes.push({ type: 'text', value });
|
|
19
|
+
}
|
|
20
|
+
function isSupportedLink(node, source) {
|
|
21
|
+
const raw = sourceText(node, source);
|
|
22
|
+
const destination = node.url.trim();
|
|
23
|
+
return (raw.startsWith('[') &&
|
|
24
|
+
destination.length > 0 &&
|
|
25
|
+
(/^(?:https?:\/\/|mailto:|tel:|\/|#|\.\.?\/)/i.test(destination) ||
|
|
26
|
+
destination.includes('/') ||
|
|
27
|
+
/^[^\s@]+\.[^\s]+$/u.test(destination)));
|
|
28
|
+
}
|
|
29
|
+
function sanitizeNodes(nodes, source) {
|
|
30
|
+
const sanitized = [];
|
|
31
|
+
for (const node of nodes) {
|
|
32
|
+
switch (node.type) {
|
|
33
|
+
case 'text':
|
|
34
|
+
appendText(sanitized, node.value.replace(/\n/g, ' '));
|
|
35
|
+
break;
|
|
36
|
+
case 'inlineCode':
|
|
37
|
+
sanitized.push({ type: 'inlineCode', value: node.value });
|
|
38
|
+
break;
|
|
39
|
+
case 'strong':
|
|
40
|
+
case 'emphasis':
|
|
41
|
+
case 'delete':
|
|
42
|
+
sanitized.push(Object.assign(Object.assign({}, node), { children: sanitizeNodes(node.children, source) }));
|
|
43
|
+
break;
|
|
44
|
+
case 'link':
|
|
45
|
+
if (isSupportedLink(node, source)) {
|
|
46
|
+
sanitized.push(Object.assign(Object.assign({}, node), { children: sanitizeNodes(node.children, source) }));
|
|
47
|
+
}
|
|
48
|
+
else {
|
|
49
|
+
appendText(sanitized, sourceText(node, source));
|
|
50
|
+
}
|
|
51
|
+
break;
|
|
52
|
+
default:
|
|
53
|
+
appendText(sanitized, sourceText(node, source));
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
return sanitized;
|
|
57
|
+
}
|
|
58
|
+
export function parseTitleMarkdown(value) {
|
|
59
|
+
if (!value)
|
|
60
|
+
return [];
|
|
61
|
+
try {
|
|
62
|
+
const prefix = 'title: ';
|
|
63
|
+
const source = `${prefix}${value.replace(/\n/g, ' ')}`;
|
|
64
|
+
const root = fromMarkdown(source, {
|
|
65
|
+
extensions: [gfm()],
|
|
66
|
+
mdastExtensions: [gfmFromMarkdown()],
|
|
67
|
+
});
|
|
68
|
+
const paragraph = root.children.length === 1 ? root.children[0] : undefined;
|
|
69
|
+
if ((paragraph === null || paragraph === void 0 ? void 0 : paragraph.type) !== 'paragraph')
|
|
70
|
+
return [{ type: 'text', value }];
|
|
71
|
+
const nodes = sanitizeNodes(paragraph.children, source);
|
|
72
|
+
const first = nodes[0];
|
|
73
|
+
if ((first === null || first === void 0 ? void 0 : first.type) !== 'text' || !first.value.startsWith(prefix)) {
|
|
74
|
+
return [{ type: 'text', value }];
|
|
75
|
+
}
|
|
76
|
+
first.value = first.value.slice(prefix.length);
|
|
77
|
+
if (!first.value)
|
|
78
|
+
nodes.shift();
|
|
79
|
+
return nodes;
|
|
80
|
+
}
|
|
81
|
+
catch (_a) {
|
|
82
|
+
return [{ type: 'text', value }];
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
export function titleMarkdownToText(value) {
|
|
86
|
+
return getTableOfContentsTitle({
|
|
87
|
+
type: 'paragraph',
|
|
88
|
+
children: parseTitleMarkdown(value),
|
|
89
|
+
});
|
|
90
|
+
}
|