@tanstack/markdown 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +36 -0
- package/dist/extensions/callouts.d.ts +4 -0
- package/dist/extensions/callouts.d.ts.map +1 -0
- package/dist/extensions/callouts.js +39 -0
- package/dist/extensions/comment-components.d.ts +14 -0
- package/dist/extensions/comment-components.d.ts.map +1 -0
- package/dist/extensions/comment-components.js +68 -0
- package/dist/extensions/docs.d.ts +8 -0
- package/dist/extensions/docs.d.ts.map +1 -0
- package/dist/extensions/docs.js +25 -0
- package/dist/extensions/framework.d.ts +3 -0
- package/dist/extensions/framework.d.ts.map +1 -0
- package/dist/extensions/framework.js +40 -0
- package/dist/extensions/headings.d.ts +7 -0
- package/dist/extensions/headings.d.ts.map +1 -0
- package/dist/extensions/headings.js +51 -0
- package/dist/extensions/shared.d.ts +12 -0
- package/dist/extensions/shared.d.ts.map +1 -0
- package/dist/extensions/shared.js +92 -0
- package/dist/extensions/tabs.d.ts +7 -0
- package/dist/extensions/tabs.d.ts.map +1 -0
- package/dist/extensions/tabs.js +145 -0
- package/dist/html.d.ts +6 -0
- package/dist/html.d.ts.map +1 -0
- package/dist/html.js +157 -0
- package/dist/index.d.ts +5 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +3 -0
- package/dist/inline.d.ts +3 -0
- package/dist/inline.d.ts.map +1 -0
- package/dist/inline.js +227 -0
- package/dist/parser.d.ts +3 -0
- package/dist/parser.d.ts.map +1 -0
- package/dist/parser.js +349 -0
- package/dist/react.d.ts +15 -0
- package/dist/react.d.ts.map +1 -0
- package/dist/react.js +129 -0
- package/dist/types.d.ts +172 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +1 -0
- package/dist/utils.d.ts +11 -0
- package/dist/utils.d.ts.map +1 -0
- package/dist/utils.js +70 -0
- package/package.json +94 -0
package/README.md
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
# TanStack Markdown
|
|
2
|
+
|
|
3
|
+
A tiny deterministic TanStack-flavored Markdown renderer.
|
|
4
|
+
|
|
5
|
+
```bash
|
|
6
|
+
pnpm add @tanstack/markdown
|
|
7
|
+
```
|
|
8
|
+
|
|
9
|
+
## Commands
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
pnpm test
|
|
13
|
+
pnpm run typecheck
|
|
14
|
+
pnpm run build
|
|
15
|
+
pnpm run research
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
## Entry Points
|
|
19
|
+
|
|
20
|
+
- `src/parser.ts`: Markdown subset parser to a serializable AST
|
|
21
|
+
- `src/html.ts`: HTML string renderer
|
|
22
|
+
- `src/react.ts`: React adapter
|
|
23
|
+
- `src/extensions/*`: optional docs-site extension functions
|
|
24
|
+
- `scripts/measure-size.ts`: bundle size matrix
|
|
25
|
+
- `scripts/bench.ts`: CPU benchmark matrix
|
|
26
|
+
|
|
27
|
+
## Reports
|
|
28
|
+
|
|
29
|
+
- [Bundle sizes](./reports/sizes.md)
|
|
30
|
+
- [Benchmarks](./reports/benchmarks.md)
|
|
31
|
+
- [Audit](./docs/audit.md)
|
|
32
|
+
- [Spec and API](./docs/spec.md)
|
|
33
|
+
- [Goal and coverage map](./docs/goal.md)
|
|
34
|
+
- [Bundle strategy](./docs/bundle-strategy.md)
|
|
35
|
+
- [Extensions](./docs/extensions.md)
|
|
36
|
+
- [Recommendation](./docs/recommendation.md)
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import type { CalloutNode, MarkdownExtension } from '../types.js';
|
|
2
|
+
export declare function calloutsExtension(): MarkdownExtension;
|
|
3
|
+
export declare function parseCalloutBlock(context: Parameters<NonNullable<MarkdownExtension['parseBlock']>>[0]): CalloutNode | undefined;
|
|
4
|
+
//# sourceMappingURL=callouts.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"callouts.d.ts","sourceRoot":"","sources":["../../src/extensions/callouts.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAA;AAEjE,wBAAgB,iBAAiB,IAAI,iBAAiB,CAKrD;AAED,wBAAgB,iBAAiB,CAAC,OAAO,EAAE,UAAU,CAAC,WAAW,CAAC,iBAAiB,CAAC,YAAY,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,WAAW,GAAG,SAAS,CA+B/H"}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
export function calloutsExtension() {
|
|
2
|
+
return {
|
|
3
|
+
name: 'callouts',
|
|
4
|
+
parseBlock: parseCalloutBlock,
|
|
5
|
+
};
|
|
6
|
+
}
|
|
7
|
+
export function parseCalloutBlock(context) {
|
|
8
|
+
const first = context.lines[context.index] ?? '';
|
|
9
|
+
const match = first.match(/^ {0,3}>\s*\[!([A-Za-z]+)\](?:\s+(.*))?$/);
|
|
10
|
+
if (!match)
|
|
11
|
+
return undefined;
|
|
12
|
+
const kind = match[1].toLowerCase();
|
|
13
|
+
const title = match[2]?.trim() || titleCase(kind);
|
|
14
|
+
const body = [];
|
|
15
|
+
let cursor = context.index + 1;
|
|
16
|
+
while (cursor < context.lines.length) {
|
|
17
|
+
const line = context.lines[cursor] ?? '';
|
|
18
|
+
if (/^\s*$/.test(line)) {
|
|
19
|
+
body.push('');
|
|
20
|
+
cursor++;
|
|
21
|
+
continue;
|
|
22
|
+
}
|
|
23
|
+
const quoted = line.match(/^ {0,3}>\s?(.*)$/);
|
|
24
|
+
if (!quoted)
|
|
25
|
+
break;
|
|
26
|
+
body.push(quoted[1]);
|
|
27
|
+
cursor++;
|
|
28
|
+
}
|
|
29
|
+
context.consume(cursor - context.index);
|
|
30
|
+
return {
|
|
31
|
+
type: 'callout',
|
|
32
|
+
kind,
|
|
33
|
+
title,
|
|
34
|
+
children: context.parseBlocks(body.join('\n')),
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
function titleCase(value) {
|
|
38
|
+
return value.slice(0, 1).toUpperCase() + value.slice(1).toLowerCase();
|
|
39
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { ComponentNode, MarkdownExtension } from '../types.js';
|
|
2
|
+
export interface CommentComponentOptions {
|
|
3
|
+
transformComponent?: (node: ComponentNode) => ComponentNode;
|
|
4
|
+
}
|
|
5
|
+
export interface ComponentComment {
|
|
6
|
+
block: boolean;
|
|
7
|
+
name: string;
|
|
8
|
+
attributes: Record<string, string>;
|
|
9
|
+
}
|
|
10
|
+
export declare function commentComponentsExtension(options?: CommentComponentOptions): MarkdownExtension;
|
|
11
|
+
export declare function parseCommentComponentBlock(context: Parameters<NonNullable<MarkdownExtension['parseBlock']>>[0], options?: CommentComponentOptions): ComponentNode | undefined;
|
|
12
|
+
export declare function parseComponentComment(line: string): ComponentComment | undefined;
|
|
13
|
+
export declare function parseAttributes(value: string): Record<string, string>;
|
|
14
|
+
//# sourceMappingURL=comment-components.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"comment-components.d.ts","sourceRoot":"","sources":["../../src/extensions/comment-components.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAA;AAEnE,MAAM,WAAW,uBAAuB;IACtC,kBAAkB,CAAC,EAAE,CAAC,IAAI,EAAE,aAAa,KAAK,aAAa,CAAA;CAC5D;AAED,MAAM,WAAW,gBAAgB;IAC/B,KAAK,EAAE,OAAO,CAAA;IACd,IAAI,EAAE,MAAM,CAAA;IACZ,UAAU,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;CACnC;AAED,wBAAgB,0BAA0B,CAAC,OAAO,GAAE,uBAA4B,GAAG,iBAAiB,CAKnG;AAED,wBAAgB,0BAA0B,CACxC,OAAO,EAAE,UAAU,CAAC,WAAW,CAAC,iBAAiB,CAAC,YAAY,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,EACpE,OAAO,GAAE,uBAA4B,GACpC,aAAa,GAAG,SAAS,CA2C3B;AAED,wBAAgB,qBAAqB,CAAC,IAAI,EAAE,MAAM,GAAG,gBAAgB,GAAG,SAAS,CAQhF;AAED,wBAAgB,eAAe,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAOrE"}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
export function commentComponentsExtension(options = {}) {
|
|
2
|
+
return {
|
|
3
|
+
name: 'comment-components',
|
|
4
|
+
parseBlock: context => parseCommentComponentBlock(context, options),
|
|
5
|
+
};
|
|
6
|
+
}
|
|
7
|
+
export function parseCommentComponentBlock(context, options = {}) {
|
|
8
|
+
const line = context.lines[context.index] ?? '';
|
|
9
|
+
const start = parseComponentComment(line);
|
|
10
|
+
if (!start)
|
|
11
|
+
return undefined;
|
|
12
|
+
if (!start.block) {
|
|
13
|
+
context.consume(1);
|
|
14
|
+
return transform({
|
|
15
|
+
type: 'component',
|
|
16
|
+
name: start.name,
|
|
17
|
+
attributes: start.attributes,
|
|
18
|
+
children: [],
|
|
19
|
+
}, options);
|
|
20
|
+
}
|
|
21
|
+
const body = [];
|
|
22
|
+
let cursor = context.index + 1;
|
|
23
|
+
let foundEnd = false;
|
|
24
|
+
while (cursor < context.lines.length) {
|
|
25
|
+
const candidate = context.lines[cursor] ?? '';
|
|
26
|
+
if (isEndComment(candidate, start.name)) {
|
|
27
|
+
foundEnd = true;
|
|
28
|
+
cursor++;
|
|
29
|
+
break;
|
|
30
|
+
}
|
|
31
|
+
body.push(candidate);
|
|
32
|
+
cursor++;
|
|
33
|
+
}
|
|
34
|
+
context.consume(foundEnd ? cursor - context.index : 1);
|
|
35
|
+
return transform({
|
|
36
|
+
type: 'component',
|
|
37
|
+
name: start.name,
|
|
38
|
+
attributes: start.attributes,
|
|
39
|
+
children: foundEnd ? context.parseBlocks(body.join('\n')) : [],
|
|
40
|
+
}, options);
|
|
41
|
+
}
|
|
42
|
+
export function parseComponentComment(line) {
|
|
43
|
+
const match = line.match(/^ {0,3}<!--\s*::(start:)?([A-Za-z][\w-]*)(.*?)\s*-->\s*$/);
|
|
44
|
+
if (!match)
|
|
45
|
+
return undefined;
|
|
46
|
+
return {
|
|
47
|
+
block: Boolean(match[1]),
|
|
48
|
+
name: match[2].toLowerCase(),
|
|
49
|
+
attributes: parseAttributes(match[3] ?? ''),
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
export function parseAttributes(value) {
|
|
53
|
+
const attrs = {};
|
|
54
|
+
const regex = /([A-Za-z_][\w:-]*)(?:=(?:"([^"]*)"|'([^']*)'|([^\s"']+)))?/g;
|
|
55
|
+
for (const match of value.matchAll(regex)) {
|
|
56
|
+
attrs[match[1]] = match[2] ?? match[3] ?? match[4] ?? 'true';
|
|
57
|
+
}
|
|
58
|
+
return attrs;
|
|
59
|
+
}
|
|
60
|
+
function transform(node, options) {
|
|
61
|
+
return options.transformComponent?.(node) ?? node;
|
|
62
|
+
}
|
|
63
|
+
function isEndComment(line, name) {
|
|
64
|
+
return new RegExp(`^ {0,3}<!--\\s*::end:${escapeRegex(name)}\\s*-->\\s*$`, 'i').test(line);
|
|
65
|
+
}
|
|
66
|
+
function escapeRegex(value) {
|
|
67
|
+
return value.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
68
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { ComponentNode, MarkdownExtension } from '../types.js';
|
|
2
|
+
import { type HeadingCollectionOptions } from './headings.js';
|
|
3
|
+
export interface DocsMarkdownOptions {
|
|
4
|
+
collectHeadings?: boolean | HeadingCollectionOptions;
|
|
5
|
+
}
|
|
6
|
+
export declare function docsMarkdownExtensions(options?: DocsMarkdownOptions): MarkdownExtension[];
|
|
7
|
+
export declare function transformDocsComponent(node: ComponentNode): ComponentNode;
|
|
8
|
+
//# sourceMappingURL=docs.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"docs.d.ts","sourceRoot":"","sources":["../../src/extensions/docs.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAA;AAInE,OAAO,EAA8B,KAAK,wBAAwB,EAAE,MAAM,eAAe,CAAA;AAGzF,MAAM,WAAW,mBAAmB;IAClC,eAAe,CAAC,EAAE,OAAO,GAAG,wBAAwB,CAAA;CACrD;AAED,wBAAgB,sBAAsB,CAAC,OAAO,GAAE,mBAAwB,GAAG,iBAAiB,EAAE,CAiB7F;AAED,wBAAgB,sBAAsB,CAAC,IAAI,EAAE,aAAa,GAAG,aAAa,CAKzE"}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { calloutsExtension } from './callouts.js';
|
|
2
|
+
import { commentComponentsExtension } from './comment-components.js';
|
|
3
|
+
import { transformFrameworkComponent } from './framework.js';
|
|
4
|
+
import { headingCollectionExtension } from './headings.js';
|
|
5
|
+
import { transformTabsComponent } from './tabs.js';
|
|
6
|
+
export function docsMarkdownExtensions(options = {}) {
|
|
7
|
+
const extensions = [
|
|
8
|
+
calloutsExtension(),
|
|
9
|
+
commentComponentsExtension({
|
|
10
|
+
transformComponent: transformDocsComponent,
|
|
11
|
+
}),
|
|
12
|
+
];
|
|
13
|
+
if (options.collectHeadings !== false) {
|
|
14
|
+
extensions.push(headingCollectionExtension(typeof options.collectHeadings === 'object' ? options.collectHeadings : {}));
|
|
15
|
+
}
|
|
16
|
+
return extensions;
|
|
17
|
+
}
|
|
18
|
+
export function transformDocsComponent(node) {
|
|
19
|
+
const name = node.name.toLowerCase();
|
|
20
|
+
if (name === 'tabs')
|
|
21
|
+
return transformTabsComponent(node);
|
|
22
|
+
if (name === 'framework')
|
|
23
|
+
return transformFrameworkComponent(node);
|
|
24
|
+
return node;
|
|
25
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"framework.d.ts","sourceRoot":"","sources":["../../src/extensions/framework.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAa,aAAa,EAAE,MAAM,aAAa,CAAA;AAG3D,wBAAgB,2BAA2B,CAAC,IAAI,EAAE,aAAa,GAAG,aAAa,CAyC9E"}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { markFrameworkHeadings, splitByHeading } from './shared.js';
|
|
2
|
+
export function transformFrameworkComponent(node) {
|
|
3
|
+
const sections = splitByHeading(node.children, 1);
|
|
4
|
+
if (!sections.length)
|
|
5
|
+
return node;
|
|
6
|
+
const frameworks = sections.map(section => section.name.toLowerCase());
|
|
7
|
+
const panels = sections.map((section) => {
|
|
8
|
+
const framework = section.name.toLowerCase();
|
|
9
|
+
return {
|
|
10
|
+
type: 'component',
|
|
11
|
+
name: 'framework-panel',
|
|
12
|
+
tagName: 'md-framework-panel',
|
|
13
|
+
attributes: {},
|
|
14
|
+
properties: {
|
|
15
|
+
'data-framework': framework,
|
|
16
|
+
},
|
|
17
|
+
children: markFrameworkHeadings(section.children, framework),
|
|
18
|
+
};
|
|
19
|
+
});
|
|
20
|
+
return {
|
|
21
|
+
...node,
|
|
22
|
+
properties: {
|
|
23
|
+
...(node.properties ?? {}),
|
|
24
|
+
'data-available-frameworks': JSON.stringify(frameworks),
|
|
25
|
+
'data-framework-meta': JSON.stringify({
|
|
26
|
+
codeBlocksByFramework: Object.fromEntries(sections.map(section => [
|
|
27
|
+
section.name.toLowerCase(),
|
|
28
|
+
section.children
|
|
29
|
+
.filter((child) => child.type === 'code')
|
|
30
|
+
.map(child => ({
|
|
31
|
+
title: child.title ?? '',
|
|
32
|
+
code: child.value,
|
|
33
|
+
language: child.lang ?? 'plaintext',
|
|
34
|
+
})),
|
|
35
|
+
])),
|
|
36
|
+
}),
|
|
37
|
+
},
|
|
38
|
+
children: panels,
|
|
39
|
+
};
|
|
40
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { MarkdownDocument, MarkdownExtension, MarkdownHeading } from '../types.js';
|
|
2
|
+
export interface HeadingCollectionOptions {
|
|
3
|
+
skipComponentNames?: ReadonlySet<string> | string[];
|
|
4
|
+
}
|
|
5
|
+
export declare function headingCollectionExtension(options?: HeadingCollectionOptions): MarkdownExtension;
|
|
6
|
+
export declare function collectMarkdownHeadings(document: MarkdownDocument, options?: HeadingCollectionOptions): MarkdownHeading[];
|
|
7
|
+
//# sourceMappingURL=headings.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"headings.d.ts","sourceRoot":"","sources":["../../src/extensions/headings.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAa,gBAAgB,EAAE,iBAAiB,EAAE,eAAe,EAAE,MAAM,aAAa,CAAA;AAGlG,MAAM,WAAW,wBAAwB;IACvC,kBAAkB,CAAC,EAAE,WAAW,CAAC,MAAM,CAAC,GAAG,MAAM,EAAE,CAAA;CACpD;AAED,wBAAgB,0BAA0B,CAAC,OAAO,GAAE,wBAA6B,GAAG,iBAAiB,CAUpG;AAED,wBAAgB,uBAAuB,CAAC,QAAQ,EAAE,gBAAgB,EAAE,OAAO,GAAE,wBAA6B,GAAG,eAAe,EAAE,CAK7H"}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { plainText } from '../utils.js';
|
|
2
|
+
export function headingCollectionExtension(options = {}) {
|
|
3
|
+
return {
|
|
4
|
+
name: 'heading-collection',
|
|
5
|
+
transformDocument(document) {
|
|
6
|
+
return {
|
|
7
|
+
...document,
|
|
8
|
+
headings: collectMarkdownHeadings(document, options),
|
|
9
|
+
};
|
|
10
|
+
},
|
|
11
|
+
};
|
|
12
|
+
}
|
|
13
|
+
export function collectMarkdownHeadings(document, options = {}) {
|
|
14
|
+
const headings = [];
|
|
15
|
+
const skip = new Set(options.skipComponentNames ?? ['tabs']);
|
|
16
|
+
collectFromBlocks(document.children, headings, undefined, false, skip);
|
|
17
|
+
return headings;
|
|
18
|
+
}
|
|
19
|
+
function collectFromBlocks(blocks, headings, framework, insideSkippedComponent, skipComponentNames) {
|
|
20
|
+
for (const block of blocks) {
|
|
21
|
+
if (block.type === 'heading') {
|
|
22
|
+
if (!insideSkippedComponent && block.id) {
|
|
23
|
+
const heading = {
|
|
24
|
+
id: block.id,
|
|
25
|
+
text: plainText(block.children),
|
|
26
|
+
level: block.depth,
|
|
27
|
+
};
|
|
28
|
+
const headingFramework = block.framework ?? framework;
|
|
29
|
+
if (headingFramework)
|
|
30
|
+
heading.framework = headingFramework;
|
|
31
|
+
headings.push(heading);
|
|
32
|
+
}
|
|
33
|
+
continue;
|
|
34
|
+
}
|
|
35
|
+
if (block.type === 'list') {
|
|
36
|
+
for (const item of block.items)
|
|
37
|
+
collectFromBlocks(item.children, headings, framework, insideSkippedComponent, skipComponentNames);
|
|
38
|
+
continue;
|
|
39
|
+
}
|
|
40
|
+
if (block.type === 'blockquote' || block.type === 'callout') {
|
|
41
|
+
collectFromBlocks(block.children, headings, framework, insideSkippedComponent, skipComponentNames);
|
|
42
|
+
continue;
|
|
43
|
+
}
|
|
44
|
+
if (block.type === 'component') {
|
|
45
|
+
const name = block.name.toLowerCase();
|
|
46
|
+
const nextInsideSkippedComponent = insideSkippedComponent || skipComponentNames.has(name);
|
|
47
|
+
const nextFramework = block.tagName === 'md-framework-panel' ? block.properties?.['data-framework'] ?? framework : framework;
|
|
48
|
+
collectFromBlocks(block.children, headings, nextFramework, nextInsideSkippedComponent, skipComponentNames);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { BlockNode, ComponentNode } from '../types.js';
|
|
2
|
+
export interface HeadingSection {
|
|
3
|
+
id?: string;
|
|
4
|
+
name: string;
|
|
5
|
+
children: BlockNode[];
|
|
6
|
+
}
|
|
7
|
+
export declare function blocksToText(blocks: BlockNode[]): string;
|
|
8
|
+
export declare function splitByHeading(children: BlockNode[], forcedDepth?: number): HeadingSection[];
|
|
9
|
+
export declare function slugify(value: string, fallback: string): string;
|
|
10
|
+
export declare function markFrameworkHeadings(blocks: BlockNode[], framework: string): BlockNode[];
|
|
11
|
+
export declare function getComponentName(node: ComponentNode): string;
|
|
12
|
+
//# sourceMappingURL=shared.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"shared.d.ts","sourceRoot":"","sources":["../../src/extensions/shared.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,aAAa,EAAe,MAAM,aAAa,CAAA;AAGxE,MAAM,WAAW,cAAc;IAC7B,EAAE,CAAC,EAAE,MAAM,CAAA;IACX,IAAI,EAAE,MAAM,CAAA;IACZ,QAAQ,EAAE,SAAS,EAAE,CAAA;CACtB;AAED,wBAAgB,YAAY,CAAC,MAAM,EAAE,SAAS,EAAE,GAAG,MAAM,CAsBxD;AAED,wBAAgB,cAAc,CAAC,QAAQ,EAAE,SAAS,EAAE,EAAE,WAAW,CAAC,EAAE,MAAM,GAAG,cAAc,EAAE,CAsB5F;AAED,wBAAgB,OAAO,CAAC,KAAK,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,UAWtD;AAED,wBAAgB,qBAAqB,CAAC,MAAM,EAAE,SAAS,EAAE,EAAE,SAAS,EAAE,MAAM,GAAG,SAAS,EAAE,CAmCzF;AAED,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,aAAa,UAEnD"}
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import { plainText } from '../utils.js';
|
|
2
|
+
export function blocksToText(blocks) {
|
|
3
|
+
return blocks
|
|
4
|
+
.map(block => {
|
|
5
|
+
switch (block.type) {
|
|
6
|
+
case 'paragraph':
|
|
7
|
+
case 'heading':
|
|
8
|
+
return plainText(block.children);
|
|
9
|
+
case 'code':
|
|
10
|
+
return block.value;
|
|
11
|
+
case 'list':
|
|
12
|
+
return block.items.map(item => blocksToText(item.children)).join('\n');
|
|
13
|
+
case 'blockquote':
|
|
14
|
+
case 'callout':
|
|
15
|
+
case 'component':
|
|
16
|
+
return blocksToText(block.children);
|
|
17
|
+
case 'table':
|
|
18
|
+
return [block.header, ...block.rows].map(row => row.map(cell => plainText(cell.children)).join(' ')).join('\n');
|
|
19
|
+
default:
|
|
20
|
+
return '';
|
|
21
|
+
}
|
|
22
|
+
})
|
|
23
|
+
.join('\n');
|
|
24
|
+
}
|
|
25
|
+
export function splitByHeading(children, forcedDepth) {
|
|
26
|
+
const headings = children.filter((child) => child.type === 'heading');
|
|
27
|
+
const depth = forcedDepth ?? Math.min(...headings.map(heading => heading.depth));
|
|
28
|
+
if (!Number.isFinite(depth))
|
|
29
|
+
return [];
|
|
30
|
+
const sections = [];
|
|
31
|
+
let current;
|
|
32
|
+
for (const child of children) {
|
|
33
|
+
if (child.type === 'heading' && child.depth === depth) {
|
|
34
|
+
current = {
|
|
35
|
+
name: plainText(child.children),
|
|
36
|
+
children: [],
|
|
37
|
+
};
|
|
38
|
+
if (child.id)
|
|
39
|
+
current.id = child.id;
|
|
40
|
+
sections.push(current);
|
|
41
|
+
continue;
|
|
42
|
+
}
|
|
43
|
+
if (current)
|
|
44
|
+
current.children.push(child);
|
|
45
|
+
}
|
|
46
|
+
return sections;
|
|
47
|
+
}
|
|
48
|
+
export function slugify(value, fallback) {
|
|
49
|
+
return (value
|
|
50
|
+
.trim()
|
|
51
|
+
.toLowerCase()
|
|
52
|
+
.replace(/[^a-z0-9\s-]/g, '')
|
|
53
|
+
.replace(/\s+/g, '-')
|
|
54
|
+
.replace(/-+/g, '-')
|
|
55
|
+
.replace(/^-|-$/g, '')
|
|
56
|
+
.slice(0, 64) || fallback);
|
|
57
|
+
}
|
|
58
|
+
export function markFrameworkHeadings(blocks, framework) {
|
|
59
|
+
return blocks.map(block => {
|
|
60
|
+
if (block.type === 'heading' && block.depth > 1) {
|
|
61
|
+
return {
|
|
62
|
+
...block,
|
|
63
|
+
framework,
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
if (block.type === 'blockquote' || block.type === 'callout') {
|
|
67
|
+
return {
|
|
68
|
+
...block,
|
|
69
|
+
children: markFrameworkHeadings(block.children, framework),
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
if (block.type === 'list') {
|
|
73
|
+
return {
|
|
74
|
+
...block,
|
|
75
|
+
items: block.items.map(item => ({
|
|
76
|
+
...item,
|
|
77
|
+
children: markFrameworkHeadings(item.children, framework),
|
|
78
|
+
})),
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
if (block.type === 'component') {
|
|
82
|
+
return {
|
|
83
|
+
...block,
|
|
84
|
+
children: markFrameworkHeadings(block.children, framework),
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
return block;
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
export function getComponentName(node) {
|
|
91
|
+
return node.name.toLowerCase();
|
|
92
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { ComponentNode } from '../types.js';
|
|
2
|
+
export declare function transformTabsComponent(node: ComponentNode): ComponentNode;
|
|
3
|
+
export declare function transformFileTabs(node: ComponentNode): ComponentNode;
|
|
4
|
+
export declare function transformPackageManagerTabs(node: ComponentNode): ComponentNode;
|
|
5
|
+
export declare function transformBundlerTabs(node: ComponentNode): ComponentNode;
|
|
6
|
+
export declare function transformHeadingTabs(node: ComponentNode): ComponentNode;
|
|
7
|
+
//# sourceMappingURL=tabs.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tabs.d.ts","sourceRoot":"","sources":["../../src/extensions/tabs.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAa,aAAa,EAAE,MAAM,aAAa,CAAA;AAK3D,wBAAgB,sBAAsB,CAAC,IAAI,EAAE,aAAa,GAAG,aAAa,CAQzE;AAED,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,aAAa,GAAG,aAAa,CAkCpE;AAED,wBAAgB,2BAA2B,CAAC,IAAI,EAAE,aAAa,GAAG,aAAa,CA4B9E;AAED,wBAAgB,oBAAoB,CAAC,IAAI,EAAE,aAAa,GAAG,aAAa,CAiCvE;AAED,wBAAgB,oBAAoB,CAAC,IAAI,EAAE,aAAa,GAAG,aAAa,CA2BvE"}
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
import { blocksToText, slugify, splitByHeading } from './shared.js';
|
|
2
|
+
const bundlers = ['vite', 'rsbuild'];
|
|
3
|
+
export function transformTabsComponent(node) {
|
|
4
|
+
const variant = node.attributes.variant?.toLowerCase();
|
|
5
|
+
if (variant === 'files')
|
|
6
|
+
return transformFileTabs(node);
|
|
7
|
+
if (variant === 'package-manager' || variant === 'package-managers')
|
|
8
|
+
return transformPackageManagerTabs(node);
|
|
9
|
+
if (variant === 'bundler')
|
|
10
|
+
return transformBundlerTabs(node);
|
|
11
|
+
return transformHeadingTabs(node);
|
|
12
|
+
}
|
|
13
|
+
export function transformFileTabs(node) {
|
|
14
|
+
const files = node.children.filter((child) => child.type === 'code');
|
|
15
|
+
if (!files.length)
|
|
16
|
+
return node;
|
|
17
|
+
const tabs = files.map((file, index) => ({
|
|
18
|
+
slug: `file-${index}`,
|
|
19
|
+
name: file.title || file.file || 'Untitled',
|
|
20
|
+
}));
|
|
21
|
+
return {
|
|
22
|
+
...node,
|
|
23
|
+
properties: {
|
|
24
|
+
...(node.properties ?? {}),
|
|
25
|
+
'data-attributes': JSON.stringify({ tabs }),
|
|
26
|
+
'data-files-meta': JSON.stringify({
|
|
27
|
+
files: files.map(file => ({
|
|
28
|
+
title: file.title || file.file || 'Untitled',
|
|
29
|
+
code: file.value,
|
|
30
|
+
language: file.lang ?? 'plaintext',
|
|
31
|
+
})),
|
|
32
|
+
}),
|
|
33
|
+
},
|
|
34
|
+
children: files.map((file, index) => ({
|
|
35
|
+
type: 'component',
|
|
36
|
+
name: 'tab-panel',
|
|
37
|
+
tagName: 'md-tab-panel',
|
|
38
|
+
attributes: {},
|
|
39
|
+
properties: {
|
|
40
|
+
'data-tab-slug': `file-${index}`,
|
|
41
|
+
'data-tab-index': String(index),
|
|
42
|
+
},
|
|
43
|
+
children: [file],
|
|
44
|
+
})),
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
export function transformPackageManagerTabs(node) {
|
|
48
|
+
const packagesByFramework = {};
|
|
49
|
+
for (const line of blocksToText(node.children).split('\n')) {
|
|
50
|
+
const trimmed = line.trim();
|
|
51
|
+
if (!trimmed)
|
|
52
|
+
continue;
|
|
53
|
+
const colon = trimmed.indexOf(':');
|
|
54
|
+
if (colon === -1)
|
|
55
|
+
continue;
|
|
56
|
+
const framework = trimmed.slice(0, colon).trim().toLowerCase();
|
|
57
|
+
const packages = trimmed.slice(colon + 1).trim().split(/\s+/).filter(Boolean);
|
|
58
|
+
if (!framework || packages.length === 0)
|
|
59
|
+
continue;
|
|
60
|
+
packagesByFramework[framework] ??= [];
|
|
61
|
+
packagesByFramework[framework].push(packages);
|
|
62
|
+
}
|
|
63
|
+
if (!Object.keys(packagesByFramework).length)
|
|
64
|
+
return node;
|
|
65
|
+
return {
|
|
66
|
+
...node,
|
|
67
|
+
properties: {
|
|
68
|
+
...(node.properties ?? {}),
|
|
69
|
+
'data-package-manager-meta': JSON.stringify({
|
|
70
|
+
packagesByFramework,
|
|
71
|
+
mode: resolveInstallMode(node.attributes.mode),
|
|
72
|
+
}),
|
|
73
|
+
},
|
|
74
|
+
children: [],
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
export function transformBundlerTabs(node) {
|
|
78
|
+
const sections = splitByHeading(node.children);
|
|
79
|
+
const selected = sections.filter(section => isBundler(section.name.toLowerCase()));
|
|
80
|
+
if (!selected.length)
|
|
81
|
+
return node;
|
|
82
|
+
const tabs = bundlers
|
|
83
|
+
.map(bundler => selected.find(section => section.name.toLowerCase() === bundler))
|
|
84
|
+
.filter((section) => Boolean(section))
|
|
85
|
+
.map(section => ({ slug: section.name.toLowerCase(), name: section.name.toLowerCase() }));
|
|
86
|
+
return {
|
|
87
|
+
...node,
|
|
88
|
+
properties: {
|
|
89
|
+
...(node.properties ?? {}),
|
|
90
|
+
'data-attributes': JSON.stringify({ tabs }),
|
|
91
|
+
'data-bundler-meta': JSON.stringify({ bundlers: tabs.map(tab => tab.slug) }),
|
|
92
|
+
},
|
|
93
|
+
children: tabs.map((tab, index) => {
|
|
94
|
+
const section = selected.find(section => section.name.toLowerCase() === tab.slug);
|
|
95
|
+
return {
|
|
96
|
+
type: 'component',
|
|
97
|
+
name: 'tab-panel',
|
|
98
|
+
tagName: 'md-tab-panel',
|
|
99
|
+
attributes: {},
|
|
100
|
+
properties: {
|
|
101
|
+
'data-tab-slug': tab.slug,
|
|
102
|
+
'data-tab-index': String(index),
|
|
103
|
+
'data-content': section.children.length === 1 && section.children[0]?.type === 'code' ? 'code-only' : 'mixed',
|
|
104
|
+
},
|
|
105
|
+
children: section.children,
|
|
106
|
+
};
|
|
107
|
+
}),
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
export function transformHeadingTabs(node) {
|
|
111
|
+
const sections = splitByHeading(node.children);
|
|
112
|
+
if (!sections.length)
|
|
113
|
+
return node;
|
|
114
|
+
const tabs = sections.map((section, index) => ({
|
|
115
|
+
slug: section.id || slugify(section.name, `tab-${index + 1}`),
|
|
116
|
+
name: section.name,
|
|
117
|
+
}));
|
|
118
|
+
return {
|
|
119
|
+
...node,
|
|
120
|
+
properties: {
|
|
121
|
+
...(node.properties ?? {}),
|
|
122
|
+
'data-attributes': JSON.stringify({ tabs }),
|
|
123
|
+
},
|
|
124
|
+
children: sections.map((section, index) => ({
|
|
125
|
+
type: 'component',
|
|
126
|
+
name: 'tab-panel',
|
|
127
|
+
tagName: 'md-tab-panel',
|
|
128
|
+
attributes: {},
|
|
129
|
+
properties: {
|
|
130
|
+
'data-tab-slug': tabs[index]?.slug ?? `tab-${index + 1}`,
|
|
131
|
+
'data-tab-index': String(index),
|
|
132
|
+
},
|
|
133
|
+
children: section.children,
|
|
134
|
+
})),
|
|
135
|
+
};
|
|
136
|
+
}
|
|
137
|
+
function resolveInstallMode(value) {
|
|
138
|
+
const mode = value?.toLowerCase();
|
|
139
|
+
if (mode === 'dev-install' || mode === 'local-install')
|
|
140
|
+
return mode;
|
|
141
|
+
return 'install';
|
|
142
|
+
}
|
|
143
|
+
function isBundler(value) {
|
|
144
|
+
return bundlers.includes(value);
|
|
145
|
+
}
|
package/dist/html.d.ts
ADDED
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import type { BlockNode, InlineNode, MarkdownDocument, MarkdownInput, RenderOptions } from './types.js';
|
|
2
|
+
export declare function renderHtml(input: MarkdownInput, options?: RenderOptions): string;
|
|
3
|
+
export declare function renderBlock(node: BlockNode, options?: RenderOptions): string;
|
|
4
|
+
export declare function renderInline(node: InlineNode, options?: RenderOptions): string;
|
|
5
|
+
export declare function renderDocument(document: MarkdownDocument, options?: RenderOptions): string;
|
|
6
|
+
//# sourceMappingURL=html.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"html.d.ts","sourceRoot":"","sources":["../src/html.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,SAAS,EAAuC,UAAU,EAAE,gBAAgB,EAAE,aAAa,EAAE,aAAa,EAAiB,MAAM,YAAY,CAAA;AAG3J,wBAAgB,UAAU,CAAC,KAAK,EAAE,aAAa,EAAE,OAAO,GAAE,aAAkB,GAAG,MAAM,CAGpF;AAED,wBAAgB,WAAW,CAAC,IAAI,EAAE,SAAS,EAAE,OAAO,GAAE,aAAkB,GAAG,MAAM,CA8ChF;AAED,wBAAgB,YAAY,CAAC,IAAI,EAAE,UAAU,EAAE,OAAO,GAAE,aAAkB,GAAG,MAAM,CAwBlF;AAED,wBAAgB,cAAc,CAAC,QAAQ,EAAE,gBAAgB,EAAE,OAAO,GAAE,aAAkB,GAAG,MAAM,CAE9F"}
|