@astrojs/markdoc 1.0.0-beta.0 → 1.0.0-beta.3
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/LICENSE +0 -2
- package/README.md +21 -436
- package/components/Renderer.astro +8 -9
- package/components/TreeNode.ts +90 -78
- package/dist/content-entry-type.js +182 -81
- package/dist/extensions/shiki.d.ts +1 -1
- package/dist/extensions/shiki.js +14 -85
- package/dist/heading-ids.d.ts +2 -2
- package/dist/heading-ids.js +1 -4
- package/dist/html/css/parse-inline-css-to-react.js +1 -1
- package/dist/html/css/parse-inline-styles.js +8 -14
- package/dist/html/tagdefs/html.tag.js +35 -0
- package/dist/html/transform/html-token-transform.d.ts +1 -0
- package/dist/index.js +3 -3
- package/dist/load-config.js +4 -7
- package/dist/options.d.ts +2 -0
- package/dist/{experimental-assets-config.d.ts → runtime-assets-config.d.ts} +1 -1
- package/dist/{experimental-assets-config.js → runtime-assets-config.js} +3 -3
- package/dist/runtime.js +3 -6
- package/dist/tokenizer.js +6 -0
- package/dist/utils.d.ts +0 -9
- package/dist/utils.js +1 -9
- package/package.json +20 -28
- package/dist/html/index.d.ts +0 -2
- package/dist/html/index.js +0 -6
package/components/TreeNode.ts
CHANGED
|
@@ -1,20 +1,23 @@
|
|
|
1
|
-
import type {
|
|
2
|
-
import type { RenderableTreeNode } from '@markdoc/markdoc';
|
|
1
|
+
import type { RenderableTreeNodes } from '@markdoc/markdoc';
|
|
3
2
|
import Markdoc from '@markdoc/markdoc';
|
|
3
|
+
import type { AstroInstance, SSRResult } from 'astro';
|
|
4
|
+
import type { HTMLString } from 'astro/runtime/server/index.js';
|
|
4
5
|
import {
|
|
5
6
|
createComponent,
|
|
6
|
-
|
|
7
|
+
createHeadAndContent,
|
|
8
|
+
isHTMLString,
|
|
7
9
|
render,
|
|
10
|
+
renderComponent,
|
|
8
11
|
renderScriptElement,
|
|
12
|
+
renderTemplate,
|
|
9
13
|
renderUniqueStylesheet,
|
|
10
|
-
createHeadAndContent,
|
|
11
14
|
unescapeHTML,
|
|
12
|
-
renderTemplate,
|
|
13
|
-
HTMLString,
|
|
14
|
-
isHTMLString,
|
|
15
15
|
} from 'astro/runtime/server/index.js';
|
|
16
16
|
|
|
17
|
-
|
|
17
|
+
type TreeNode =
|
|
18
|
+
// Markdoc `if` tag often returns an array of nodes in the AST, which gets translated
|
|
19
|
+
// here as an array of `TreeNode`s, which we'll render all without a wrapper.
|
|
20
|
+
| TreeNode[]
|
|
18
21
|
| {
|
|
19
22
|
type: 'text';
|
|
20
23
|
content: string | HTMLString;
|
|
@@ -35,81 +38,90 @@ export type TreeNode =
|
|
|
35
38
|
children: TreeNode[];
|
|
36
39
|
};
|
|
37
40
|
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
result,
|
|
84
|
-
treeNode.component.name,
|
|
85
|
-
treeNode.component,
|
|
86
|
-
treeNode.props,
|
|
87
|
-
slots
|
|
88
|
-
)}`
|
|
89
|
-
);
|
|
90
|
-
|
|
91
|
-
// Let the runtime know that this component is being used.
|
|
92
|
-
// `result.propagators` has been moved to `result._metadata.propagators`
|
|
93
|
-
// TODO: remove this fallback in the next markdoc integration major
|
|
94
|
-
const propagators = result._metadata.propagators || result.propagators;
|
|
95
|
-
propagators.set(
|
|
96
|
-
{},
|
|
97
|
-
{
|
|
98
|
-
init() {
|
|
99
|
-
return headAndContent;
|
|
100
|
-
},
|
|
101
|
-
}
|
|
102
|
-
);
|
|
103
|
-
|
|
104
|
-
return headAndContent;
|
|
41
|
+
function renderTreeNodeToFactoryResult(result: SSRResult, treeNode: TreeNode) {
|
|
42
|
+
if (Array.isArray(treeNode)) {
|
|
43
|
+
return Promise.all(
|
|
44
|
+
treeNode.map((node) =>
|
|
45
|
+
renderComponent(result, 'ComponentNode', ComponentNode, { treeNode: node }),
|
|
46
|
+
),
|
|
47
|
+
);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
if (treeNode.type === 'text') return render`${treeNode.content}`;
|
|
51
|
+
|
|
52
|
+
const slots = {
|
|
53
|
+
default: () =>
|
|
54
|
+
render`${treeNode.children.map((child) =>
|
|
55
|
+
renderComponent(result, 'ComponentNode', ComponentNode, { treeNode: child }),
|
|
56
|
+
)}`,
|
|
57
|
+
};
|
|
58
|
+
if (treeNode.type === 'component') {
|
|
59
|
+
let styles = '',
|
|
60
|
+
links = '',
|
|
61
|
+
scripts = '';
|
|
62
|
+
if (Array.isArray(treeNode.collectedStyles)) {
|
|
63
|
+
styles = treeNode.collectedStyles
|
|
64
|
+
.map((style: any) =>
|
|
65
|
+
renderUniqueStylesheet(result, {
|
|
66
|
+
type: 'inline',
|
|
67
|
+
content: style,
|
|
68
|
+
}),
|
|
69
|
+
)
|
|
70
|
+
.join('');
|
|
71
|
+
}
|
|
72
|
+
if (Array.isArray(treeNode.collectedLinks)) {
|
|
73
|
+
links = treeNode.collectedLinks
|
|
74
|
+
.map((link: any) => {
|
|
75
|
+
return renderUniqueStylesheet(result, {
|
|
76
|
+
type: 'external',
|
|
77
|
+
src: link[0] === '/' ? link : '/' + link,
|
|
78
|
+
});
|
|
79
|
+
})
|
|
80
|
+
.join('');
|
|
81
|
+
}
|
|
82
|
+
if (Array.isArray(treeNode.collectedScripts)) {
|
|
83
|
+
scripts = treeNode.collectedScripts
|
|
84
|
+
.map((script: any) => renderScriptElement(script))
|
|
85
|
+
.join('');
|
|
105
86
|
}
|
|
106
|
-
|
|
87
|
+
|
|
88
|
+
const head = unescapeHTML(styles + links + scripts);
|
|
89
|
+
|
|
90
|
+
let headAndContent = createHeadAndContent(
|
|
91
|
+
head,
|
|
92
|
+
renderTemplate`${renderComponent(
|
|
93
|
+
result,
|
|
94
|
+
treeNode.component.name,
|
|
95
|
+
treeNode.component,
|
|
96
|
+
treeNode.props,
|
|
97
|
+
slots,
|
|
98
|
+
)}`,
|
|
99
|
+
);
|
|
100
|
+
|
|
101
|
+
// Let the runtime know that this component is being used.
|
|
102
|
+
// @ts-expect-error Astro only uses `init()` so specify it only (plus `_metadata` is internal)
|
|
103
|
+
result._metadata.propagators.add({
|
|
104
|
+
init() {
|
|
105
|
+
return headAndContent;
|
|
106
|
+
},
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
return headAndContent;
|
|
110
|
+
}
|
|
111
|
+
return renderComponent(result, treeNode.tag, treeNode.tag, treeNode.attributes, slots);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
export const ComponentNode = createComponent({
|
|
115
|
+
factory(result: SSRResult, { treeNode }: { treeNode: TreeNode | TreeNode[] }) {
|
|
116
|
+
return renderTreeNodeToFactoryResult(result, treeNode);
|
|
107
117
|
},
|
|
108
118
|
propagation: 'self',
|
|
109
119
|
});
|
|
110
120
|
|
|
111
|
-
export async function createTreeNode(node:
|
|
112
|
-
if (
|
|
121
|
+
export async function createTreeNode(node: RenderableTreeNodes): Promise<TreeNode> {
|
|
122
|
+
if (Array.isArray(node)) {
|
|
123
|
+
return Promise.all(node.map((child) => createTreeNode(child)));
|
|
124
|
+
} else if (isHTMLString(node)) {
|
|
113
125
|
return { type: 'text', content: node as HTMLString };
|
|
114
126
|
} else if (typeof node === 'string' || typeof node === 'number') {
|
|
115
127
|
return { type: 'text', content: String(node) };
|
|
@@ -1,13 +1,13 @@
|
|
|
1
|
-
import Markdoc from "@markdoc/markdoc";
|
|
2
|
-
import matter from "gray-matter";
|
|
3
1
|
import fs from "node:fs";
|
|
4
2
|
import path from "node:path";
|
|
5
|
-
import { fileURLToPath } from "node:url";
|
|
6
|
-
import {
|
|
7
|
-
import
|
|
3
|
+
import { fileURLToPath, pathToFileURL } from "node:url";
|
|
4
|
+
import { parseFrontmatter } from "@astrojs/markdown-remark";
|
|
5
|
+
import Markdoc from "@markdoc/markdoc";
|
|
6
|
+
import { emitImageMetadata } from "astro/assets/utils/node";
|
|
8
7
|
import { htmlTokenTransform } from "./html/transform/html-token-transform.js";
|
|
9
8
|
import { setupConfig } from "./runtime.js";
|
|
10
9
|
import { getMarkdocTokenizer } from "./tokenizer.js";
|
|
10
|
+
import { isComponentConfig, isValidUrl, MarkdocError, prependForwardSlash } from "./utils.js";
|
|
11
11
|
async function getContentEntryType({
|
|
12
12
|
markdocConfigResult,
|
|
13
13
|
astroConfig,
|
|
@@ -15,76 +15,82 @@ async function getContentEntryType({
|
|
|
15
15
|
}) {
|
|
16
16
|
return {
|
|
17
17
|
extensions: [".mdoc"],
|
|
18
|
-
getEntryInfo,
|
|
18
|
+
getEntryInfo({ fileUrl, contents }) {
|
|
19
|
+
const parsed = safeParseFrontmatter(contents, fileURLToPath(fileUrl));
|
|
20
|
+
return {
|
|
21
|
+
data: parsed.frontmatter,
|
|
22
|
+
body: parsed.content.trim(),
|
|
23
|
+
slug: parsed.frontmatter.slug,
|
|
24
|
+
rawData: parsed.rawFrontmatter
|
|
25
|
+
};
|
|
26
|
+
},
|
|
19
27
|
handlePropagation: true,
|
|
20
28
|
async getRenderModule({ contents, fileUrl, viteId }) {
|
|
21
|
-
const
|
|
29
|
+
const parsed = safeParseFrontmatter(contents, fileURLToPath(fileUrl));
|
|
22
30
|
const tokenizer = getMarkdocTokenizer(options);
|
|
23
|
-
let tokens = tokenizer.tokenize(
|
|
31
|
+
let tokens = tokenizer.tokenize(parsed.content);
|
|
24
32
|
if (options?.allowHTML) {
|
|
25
33
|
tokens = htmlTokenTransform(tokenizer, tokens);
|
|
26
34
|
}
|
|
27
35
|
const ast = Markdoc.parse(tokens);
|
|
28
|
-
const usedTags = getUsedTags(ast);
|
|
29
36
|
const userMarkdocConfig = markdocConfigResult?.config ?? {};
|
|
30
37
|
const markdocConfigUrl = markdocConfigResult?.fileUrl;
|
|
38
|
+
const pluginContext = this;
|
|
39
|
+
const markdocConfig = await setupConfig(userMarkdocConfig, options);
|
|
40
|
+
const filePath = fileURLToPath(fileUrl);
|
|
41
|
+
raiseValidationErrors({
|
|
42
|
+
ast,
|
|
43
|
+
/* Raised generics issue with Markdoc core https://github.com/markdoc/markdoc/discussions/400 */
|
|
44
|
+
markdocConfig,
|
|
45
|
+
viteId,
|
|
46
|
+
astroConfig,
|
|
47
|
+
filePath
|
|
48
|
+
});
|
|
49
|
+
await resolvePartials({
|
|
50
|
+
ast,
|
|
51
|
+
markdocConfig,
|
|
52
|
+
fileUrl,
|
|
53
|
+
allowHTML: options?.allowHTML,
|
|
54
|
+
tokenizer,
|
|
55
|
+
pluginContext,
|
|
56
|
+
root: astroConfig.root,
|
|
57
|
+
raisePartialValidationErrors: (partialAst, partialPath) => {
|
|
58
|
+
raiseValidationErrors({
|
|
59
|
+
ast: partialAst,
|
|
60
|
+
markdocConfig,
|
|
61
|
+
viteId,
|
|
62
|
+
astroConfig,
|
|
63
|
+
filePath: partialPath
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
});
|
|
67
|
+
const usedTags = getUsedTags(ast);
|
|
31
68
|
let componentConfigByTagMap = {};
|
|
32
69
|
for (const tag of usedTags) {
|
|
33
|
-
const render =
|
|
70
|
+
const render = markdocConfig.tags?.[tag]?.render;
|
|
34
71
|
if (isComponentConfig(render)) {
|
|
35
72
|
componentConfigByTagMap[tag] = render;
|
|
36
73
|
}
|
|
37
74
|
}
|
|
38
75
|
let componentConfigByNodeMap = {};
|
|
39
|
-
for (const [nodeType, schema] of Object.entries(
|
|
76
|
+
for (const [nodeType, schema] of Object.entries(markdocConfig.nodes ?? {})) {
|
|
40
77
|
const render = schema?.render;
|
|
41
78
|
if (isComponentConfig(render)) {
|
|
42
79
|
componentConfigByNodeMap[nodeType] = render;
|
|
43
80
|
}
|
|
44
81
|
}
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
/* Raised generics issue with Markdoc core https://github.com/markdoc/markdoc/discussions/400 */
|
|
51
|
-
markdocConfig
|
|
52
|
-
).filter((e) => {
|
|
53
|
-
return (
|
|
54
|
-
// Ignore `variable-undefined` errors.
|
|
55
|
-
// Variables can be configured at runtime,
|
|
56
|
-
// so we cannot validate them at build time.
|
|
57
|
-
e.error.id !== "variable-undefined" && (e.error.level === "error" || e.error.level === "critical")
|
|
58
|
-
);
|
|
82
|
+
await emitOptimizedImages(ast.children, {
|
|
83
|
+
hasDefaultImage: Boolean(markdocConfig.nodes.image),
|
|
84
|
+
astroConfig,
|
|
85
|
+
pluginContext,
|
|
86
|
+
filePath
|
|
59
87
|
});
|
|
60
|
-
if (validationErrors.length) {
|
|
61
|
-
const frontmatterBlockOffset = entry.rawData.split("\n").length + 2;
|
|
62
|
-
const rootRelativePath = path.relative(fileURLToPath(astroConfig.root), filePath);
|
|
63
|
-
throw new MarkdocError({
|
|
64
|
-
message: [
|
|
65
|
-
`**${String(rootRelativePath)}** contains invalid content:`,
|
|
66
|
-
...validationErrors.map((e) => `- ${e.error.message}`)
|
|
67
|
-
].join("\n"),
|
|
68
|
-
location: {
|
|
69
|
-
// Error overlay does not support multi-line or ranges.
|
|
70
|
-
// Just point to the first line.
|
|
71
|
-
line: frontmatterBlockOffset + validationErrors[0].lines[0],
|
|
72
|
-
file: viteId
|
|
73
|
-
}
|
|
74
|
-
});
|
|
75
|
-
}
|
|
76
|
-
if (astroConfig.experimental.assets) {
|
|
77
|
-
await emitOptimizedImages(ast.children, {
|
|
78
|
-
astroConfig,
|
|
79
|
-
pluginContext,
|
|
80
|
-
filePath
|
|
81
|
-
});
|
|
82
|
-
}
|
|
83
88
|
const res = `import { Renderer } from '@astrojs/markdoc/components';
|
|
84
89
|
import { createGetHeadings, createContentComponent } from '@astrojs/markdoc/runtime';
|
|
85
|
-
${markdocConfigUrl ? `import markdocConfig from ${JSON.stringify(markdocConfigUrl
|
|
86
|
-
|
|
87
|
-
|
|
90
|
+
${markdocConfigUrl ? `import markdocConfig from ${JSON.stringify(fileURLToPath(markdocConfigUrl))};` : "const markdocConfig = {};"}
|
|
91
|
+
|
|
92
|
+
import { assetsConfig } from '@astrojs/markdoc/runtime-assets-config';
|
|
93
|
+
markdocConfig.nodes = { ...assetsConfig.nodes, ...markdocConfig.nodes };
|
|
88
94
|
|
|
89
95
|
${getStringifiedImports(componentConfigByTagMap, "Tag", astroConfig.root)}
|
|
90
96
|
${getStringifiedImports(componentConfigByNodeMap, "Node", astroConfig.root)}
|
|
@@ -104,7 +110,7 @@ export const Content = createContentComponent(
|
|
|
104
110
|
Renderer,
|
|
105
111
|
stringifiedAst,
|
|
106
112
|
markdocConfig,
|
|
107
|
-
|
|
113
|
+
options,
|
|
108
114
|
tagComponentMap,
|
|
109
115
|
nodeComponentMap,
|
|
110
116
|
)`;
|
|
@@ -116,43 +122,139 @@ export const Content = createContentComponent(
|
|
|
116
122
|
)
|
|
117
123
|
};
|
|
118
124
|
}
|
|
125
|
+
async function resolvePartials({
|
|
126
|
+
ast,
|
|
127
|
+
fileUrl,
|
|
128
|
+
root,
|
|
129
|
+
tokenizer,
|
|
130
|
+
allowHTML,
|
|
131
|
+
markdocConfig,
|
|
132
|
+
pluginContext,
|
|
133
|
+
raisePartialValidationErrors
|
|
134
|
+
}) {
|
|
135
|
+
const relativePartialPath = path.relative(fileURLToPath(root), fileURLToPath(fileUrl));
|
|
136
|
+
for (const node of ast.walk()) {
|
|
137
|
+
if (node.type === "tag" && node.tag === "partial") {
|
|
138
|
+
const { file } = node.attributes;
|
|
139
|
+
if (!file) {
|
|
140
|
+
throw new MarkdocError({
|
|
141
|
+
// Should be caught by Markdoc validation step.
|
|
142
|
+
message: `(Uncaught error) Partial tag requires a 'file' attribute`
|
|
143
|
+
});
|
|
144
|
+
}
|
|
145
|
+
if (markdocConfig.partials?.[file]) continue;
|
|
146
|
+
let partialPath;
|
|
147
|
+
let partialContents;
|
|
148
|
+
try {
|
|
149
|
+
const resolved = await pluginContext.resolve(file, fileURLToPath(fileUrl));
|
|
150
|
+
let partialId = resolved?.id;
|
|
151
|
+
if (!partialId) {
|
|
152
|
+
const attemptResolveAsRelative = await pluginContext.resolve(
|
|
153
|
+
"./" + file,
|
|
154
|
+
fileURLToPath(fileUrl)
|
|
155
|
+
);
|
|
156
|
+
if (!attemptResolveAsRelative?.id) throw new Error();
|
|
157
|
+
partialId = attemptResolveAsRelative.id;
|
|
158
|
+
}
|
|
159
|
+
partialPath = fileURLToPath(new URL(prependForwardSlash(partialId), "file://"));
|
|
160
|
+
partialContents = await fs.promises.readFile(partialPath, "utf-8");
|
|
161
|
+
} catch {
|
|
162
|
+
throw new MarkdocError({
|
|
163
|
+
message: [
|
|
164
|
+
`**${String(relativePartialPath)}** contains invalid content:`,
|
|
165
|
+
`Could not read partial file \`${file}\`. Does the file exist?`
|
|
166
|
+
].join("\n")
|
|
167
|
+
});
|
|
168
|
+
}
|
|
169
|
+
if (pluginContext.meta.watchMode) pluginContext.addWatchFile(partialPath);
|
|
170
|
+
let partialTokens = tokenizer.tokenize(partialContents);
|
|
171
|
+
if (allowHTML) {
|
|
172
|
+
partialTokens = htmlTokenTransform(tokenizer, partialTokens);
|
|
173
|
+
}
|
|
174
|
+
const partialAst = Markdoc.parse(partialTokens);
|
|
175
|
+
raisePartialValidationErrors(partialAst, partialPath);
|
|
176
|
+
await resolvePartials({
|
|
177
|
+
ast: partialAst,
|
|
178
|
+
root,
|
|
179
|
+
fileUrl: pathToFileURL(partialPath),
|
|
180
|
+
tokenizer,
|
|
181
|
+
allowHTML,
|
|
182
|
+
markdocConfig,
|
|
183
|
+
pluginContext,
|
|
184
|
+
raisePartialValidationErrors
|
|
185
|
+
});
|
|
186
|
+
Object.assign(node, partialAst);
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
function raiseValidationErrors({
|
|
191
|
+
ast,
|
|
192
|
+
markdocConfig,
|
|
193
|
+
viteId,
|
|
194
|
+
astroConfig,
|
|
195
|
+
filePath
|
|
196
|
+
}) {
|
|
197
|
+
const validationErrors = Markdoc.validate(ast, markdocConfig).filter((e) => {
|
|
198
|
+
return (e.error.level === "error" || e.error.level === "critical") && // Ignore `variable-undefined` errors.
|
|
199
|
+
// Variables can be configured at runtime,
|
|
200
|
+
// so we cannot validate them at build time.
|
|
201
|
+
e.error.id !== "variable-undefined" && // Ignore missing partial errors.
|
|
202
|
+
// We will resolve these in `resolvePartials`.
|
|
203
|
+
!(e.error.id === "attribute-value-invalid" && /^Partial .+ not found/.test(e.error.message));
|
|
204
|
+
});
|
|
205
|
+
if (validationErrors.length) {
|
|
206
|
+
const rootRelativePath = path.relative(fileURLToPath(astroConfig.root), filePath);
|
|
207
|
+
throw new MarkdocError({
|
|
208
|
+
message: [
|
|
209
|
+
`**${String(rootRelativePath)}** contains invalid content:`,
|
|
210
|
+
...validationErrors.map((e) => `- ${e.error.message}`)
|
|
211
|
+
].join("\n"),
|
|
212
|
+
location: {
|
|
213
|
+
// Error overlay does not support multi-line or ranges.
|
|
214
|
+
// Just point to the first line.
|
|
215
|
+
line: validationErrors[0].lines[0],
|
|
216
|
+
file: viteId
|
|
217
|
+
}
|
|
218
|
+
});
|
|
219
|
+
}
|
|
220
|
+
}
|
|
119
221
|
function getUsedTags(markdocAst) {
|
|
120
222
|
const tags = /* @__PURE__ */ new Set();
|
|
121
223
|
const validationErrors = Markdoc.validate(markdocAst);
|
|
122
224
|
for (const { error } of validationErrors) {
|
|
123
225
|
if (error.id === "tag-undefined") {
|
|
124
|
-
const [, tagName] =
|
|
226
|
+
const [, tagName] = /Undefined tag: '(.*)'/.exec(error.message) ?? [];
|
|
125
227
|
tags.add(tagName);
|
|
126
228
|
}
|
|
127
229
|
}
|
|
128
230
|
return tags;
|
|
129
231
|
}
|
|
130
|
-
function getEntryInfo({ fileUrl, contents }) {
|
|
131
|
-
const parsed = parseFrontmatter(contents, fileURLToPath(fileUrl));
|
|
132
|
-
return {
|
|
133
|
-
data: parsed.data,
|
|
134
|
-
body: parsed.content,
|
|
135
|
-
slug: parsed.data.slug,
|
|
136
|
-
rawData: parsed.matter
|
|
137
|
-
};
|
|
138
|
-
}
|
|
139
232
|
async function emitOptimizedImages(nodeChildren, ctx) {
|
|
140
233
|
for (const node of nodeChildren) {
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
ctx.pluginContext.emitFile
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
234
|
+
let isComponent = node.type === "tag" && node.tag === "image" || node.type === "image" && ctx.hasDefaultImage;
|
|
235
|
+
if ((node.type === "image" || isComponent) && typeof node.attributes.src === "string") {
|
|
236
|
+
let attributeName = isComponent ? "src" : "__optimizedSrc";
|
|
237
|
+
if (shouldOptimizeImage(node.attributes.src)) {
|
|
238
|
+
const resolved = await ctx.pluginContext.resolve(node.attributes.src, ctx.filePath);
|
|
239
|
+
if (resolved?.id && fs.existsSync(new URL(prependForwardSlash(resolved.id), "file://"))) {
|
|
240
|
+
const src = await emitImageMetadata(resolved.id, ctx.pluginContext.emitFile);
|
|
241
|
+
const fsPath = resolved.id;
|
|
242
|
+
if (src) {
|
|
243
|
+
if (ctx.astroConfig.output === "static") {
|
|
244
|
+
if (globalThis.astroAsset.referencedImages)
|
|
245
|
+
globalThis.astroAsset.referencedImages.add(fsPath);
|
|
246
|
+
}
|
|
247
|
+
node.attributes[attributeName] = { ...src, fsPath };
|
|
248
|
+
}
|
|
249
|
+
} else {
|
|
250
|
+
throw new MarkdocError({
|
|
251
|
+
message: `Could not resolve image ${JSON.stringify(
|
|
252
|
+
node.attributes.src
|
|
253
|
+
)} from ${JSON.stringify(ctx.filePath)}. Does the file exist?`
|
|
254
|
+
});
|
|
255
|
+
}
|
|
256
|
+
} else if (isComponent) {
|
|
257
|
+
node.attributes[attributeName] = node.attributes.src;
|
|
156
258
|
}
|
|
157
259
|
}
|
|
158
260
|
await emitOptimizedImages(node.children, ctx);
|
|
@@ -165,7 +267,7 @@ function getStringifiedImports(componentConfigMap, componentNamePrefix, root) {
|
|
|
165
267
|
let stringifiedComponentImports = "";
|
|
166
268
|
for (const [key, config] of Object.entries(componentConfigMap)) {
|
|
167
269
|
const importName = config.namedExport ? `{ ${config.namedExport} as ${componentNamePrefix + toImportName(key)} }` : componentNamePrefix + toImportName(key);
|
|
168
|
-
const resolvedPath = config.type === "local" ? new URL(config.path, root)
|
|
270
|
+
const resolvedPath = config.type === "local" ? fileURLToPath(new URL(config.path, root)) : config.path;
|
|
169
271
|
stringifiedComponentImports += `import ${importName} from ${JSON.stringify(resolvedPath)};
|
|
170
272
|
`;
|
|
171
273
|
}
|
|
@@ -183,10 +285,9 @@ function getStringifiedMap(componentConfigMap, componentNamePrefix) {
|
|
|
183
285
|
stringifiedComponentMap += "}";
|
|
184
286
|
return stringifiedComponentMap;
|
|
185
287
|
}
|
|
186
|
-
function
|
|
288
|
+
function safeParseFrontmatter(fileContents, filePath) {
|
|
187
289
|
try {
|
|
188
|
-
|
|
189
|
-
return matter(fileContents);
|
|
290
|
+
return parseFrontmatter(fileContents, { frontmatter: "empty-with-lines" });
|
|
190
291
|
} catch (e) {
|
|
191
292
|
if (e.name === "YAMLException") {
|
|
192
293
|
const err = e;
|
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
import type { ShikiConfig } from 'astro';
|
|
2
2
|
import type { AstroMarkdocConfig } from '../config.js';
|
|
3
|
-
export default function shiki(
|
|
3
|
+
export default function shiki(config?: ShikiConfig): Promise<AstroMarkdocConfig>;
|
package/dist/extensions/shiki.js
CHANGED
|
@@ -1,94 +1,23 @@
|
|
|
1
|
+
import { createShikiHighlighter } from "@astrojs/markdown-remark";
|
|
1
2
|
import Markdoc from "@markdoc/markdoc";
|
|
2
3
|
import { unescapeHTML } from "astro/runtime/server/index.js";
|
|
3
|
-
|
|
4
|
-
const
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
"material-palenight": "material-theme-palenight"
|
|
10
|
-
};
|
|
11
|
-
const normalizeTheme = (theme) => {
|
|
12
|
-
if (typeof theme === "string") {
|
|
13
|
-
return compatThemes[theme] || theme;
|
|
14
|
-
} else if (compatThemes[theme.name]) {
|
|
15
|
-
return { ...theme, name: compatThemes[theme.name] };
|
|
16
|
-
} else {
|
|
17
|
-
return theme;
|
|
18
|
-
}
|
|
19
|
-
};
|
|
20
|
-
const ASTRO_COLOR_REPLACEMENTS = {
|
|
21
|
-
"#000001": "var(--astro-code-color-text)",
|
|
22
|
-
"#000002": "var(--astro-code-color-background)",
|
|
23
|
-
"#000004": "var(--astro-code-token-constant)",
|
|
24
|
-
"#000005": "var(--astro-code-token-string)",
|
|
25
|
-
"#000006": "var(--astro-code-token-comment)",
|
|
26
|
-
"#000007": "var(--astro-code-token-keyword)",
|
|
27
|
-
"#000008": "var(--astro-code-token-parameter)",
|
|
28
|
-
"#000009": "var(--astro-code-token-function)",
|
|
29
|
-
"#000010": "var(--astro-code-token-string-expression)",
|
|
30
|
-
"#000011": "var(--astro-code-token-punctuation)",
|
|
31
|
-
"#000012": "var(--astro-code-token-link)"
|
|
32
|
-
};
|
|
33
|
-
const PRE_SELECTOR = /<pre class="(.*?)shiki(.*?)"/;
|
|
34
|
-
const LINE_SELECTOR = /<span class="line"><span style="(.*?)">([\+|\-])/g;
|
|
35
|
-
const INLINE_STYLE_SELECTOR = /style="(.*?)"/;
|
|
36
|
-
const highlighterCache = /* @__PURE__ */ new Map();
|
|
37
|
-
async function shiki({
|
|
38
|
-
langs = [],
|
|
39
|
-
theme = "github-dark",
|
|
40
|
-
wrap = false
|
|
41
|
-
} = {}) {
|
|
42
|
-
theme = normalizeTheme(theme);
|
|
43
|
-
const cacheID = typeof theme === "string" ? theme : theme.name;
|
|
44
|
-
if (!highlighterCache.has(cacheID)) {
|
|
45
|
-
highlighterCache.set(
|
|
46
|
-
cacheID,
|
|
47
|
-
await getHighlighter({ theme }).then((hl) => {
|
|
48
|
-
hl.setColorReplacements(ASTRO_COLOR_REPLACEMENTS);
|
|
49
|
-
return hl;
|
|
50
|
-
})
|
|
51
|
-
);
|
|
52
|
-
}
|
|
53
|
-
const highlighter = highlighterCache.get(cacheID);
|
|
54
|
-
for (const lang of langs) {
|
|
55
|
-
await highlighter.loadLanguage(lang);
|
|
56
|
-
}
|
|
4
|
+
async function shiki(config) {
|
|
5
|
+
const highlighter = await createShikiHighlighter({
|
|
6
|
+
langs: config?.langs,
|
|
7
|
+
theme: config?.theme,
|
|
8
|
+
themes: config?.themes
|
|
9
|
+
});
|
|
57
10
|
return {
|
|
58
11
|
nodes: {
|
|
59
12
|
fence: {
|
|
60
13
|
attributes: Markdoc.nodes.fence.attributes,
|
|
61
|
-
transform({ attributes }) {
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
console.warn(
|
|
69
|
-
`[Shiki highlighter] The language "${attributes.language}" doesn't exist, falling back to plaintext.`
|
|
70
|
-
);
|
|
71
|
-
lang = "plaintext";
|
|
72
|
-
}
|
|
73
|
-
} else {
|
|
74
|
-
lang = "plaintext";
|
|
75
|
-
}
|
|
76
|
-
let html = highlighter.codeToHtml(attributes.content, { lang });
|
|
77
|
-
html = html.replace(PRE_SELECTOR, `<pre class="$1astro-code$2"`);
|
|
78
|
-
if (attributes.language === "diff") {
|
|
79
|
-
html = html.replace(
|
|
80
|
-
LINE_SELECTOR,
|
|
81
|
-
'<span class="line"><span style="$1"><span style="user-select: none;">$2</span>'
|
|
82
|
-
);
|
|
83
|
-
}
|
|
84
|
-
if (wrap === false) {
|
|
85
|
-
html = html.replace(INLINE_STYLE_SELECTOR, 'style="$1; overflow-x: auto;"');
|
|
86
|
-
} else if (wrap === true) {
|
|
87
|
-
html = html.replace(
|
|
88
|
-
INLINE_STYLE_SELECTOR,
|
|
89
|
-
'style="$1; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;"'
|
|
90
|
-
);
|
|
91
|
-
}
|
|
14
|
+
async transform({ attributes }) {
|
|
15
|
+
const lang = typeof attributes.language === "string" ? attributes.language : "plaintext";
|
|
16
|
+
const html = await highlighter.codeToHtml(attributes.content, lang, {
|
|
17
|
+
wrap: config?.wrap,
|
|
18
|
+
defaultColor: config?.defaultColor,
|
|
19
|
+
transformers: config?.transformers
|
|
20
|
+
});
|
|
92
21
|
return unescapeHTML(html);
|
|
93
22
|
}
|
|
94
23
|
}
|
package/dist/heading-ids.d.ts
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import { type Config as MarkdocConfig, type Schema } from '@markdoc/markdoc';
|
|
2
2
|
import Slugger from 'github-slugger';
|
|
3
|
-
|
|
3
|
+
interface HeadingIdConfig extends MarkdocConfig {
|
|
4
4
|
ctx: {
|
|
5
5
|
headingSlugger: Slugger;
|
|
6
6
|
};
|
|
7
|
-
}
|
|
7
|
+
}
|
|
8
8
|
export declare const heading: Schema;
|
|
9
9
|
export declare function setupHeadingConfig(): HeadingIdConfig;
|
|
10
10
|
export {};
|
package/dist/heading-ids.js
CHANGED
|
@@ -8,10 +8,7 @@ function getSlug(attributes, children, headingSlugger) {
|
|
|
8
8
|
return attributes.id;
|
|
9
9
|
}
|
|
10
10
|
const textContent = attributes.content ?? getTextContent(children);
|
|
11
|
-
|
|
12
|
-
if (slug.endsWith("-"))
|
|
13
|
-
slug = slug.slice(0, -1);
|
|
14
|
-
return slug;
|
|
11
|
+
return headingSlugger.slug(textContent);
|
|
15
12
|
}
|
|
16
13
|
const heading = {
|
|
17
14
|
children: ["inline"],
|