@astrojs/markdoc 0.4.2 → 0.4.4

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 CHANGED
@@ -93,7 +93,7 @@ const { Content } = await entry.render();
93
93
 
94
94
  📚 See the [Astro Content Collection docs][astro-content-collections] for more information.
95
95
 
96
- ## Configuration
96
+ ## Markdoc config
97
97
 
98
98
  `@astrojs/markdoc` offers configuration options to use all of Markdoc's features and connect UI components to your content.
99
99
 
@@ -401,6 +401,34 @@ const { Content } = await entry.render();
401
401
 
402
402
  This can now be accessed as `$frontmatter` in your Markdoc.
403
403
 
404
+ ## Integration config options
405
+
406
+ The Astro Markdoc integration handles configuring Markdoc options and capabilities that are not available through the `markdoc.config.js` file.
407
+
408
+ ### `allowHTML`
409
+
410
+ Enables writing HTML markup alongside Markdoc tags and nodes.
411
+
412
+ By default, Markdoc will not recognize HTML markup as semantic content.
413
+
414
+ To achieve a more Markdown-like experience, where HTML elements can be included alongside your content, set `allowHTML:true` as a `markdoc` integration option. This will enable HTML parsing in Markdoc markup.
415
+
416
+ ```js {7} "allowHTML: true"
417
+ // astro.config.mjs
418
+ import { defineConfig } from 'astro/config';
419
+ import markdoc from '@astrojs/markdoc';
420
+
421
+ export default defineConfig({
422
+ // ...
423
+ integrations: [markdoc({ allowHTML: true })],
424
+ });
425
+ ```
426
+
427
+ > **Warning**
428
+ > When `allowHTML` is enabled, HTML markup inside Markdoc documents will be rendered as actual HTML elements (including `<script>`), making attack vectors like XSS possible.
429
+ >
430
+ > Ensure that any HTML markup comes from trusted sources.
431
+
404
432
  ## Examples
405
433
 
406
434
  - The [Astro Markdoc starter template](https://github.com/withastro/astro/tree/latest/examples/with-markdoc) shows how to use Markdoc files in your Astro project.
@@ -1,6 +1,8 @@
1
1
  import type { AstroConfig, ContentEntryType } from 'astro';
2
2
  import type { MarkdocConfigResult } from './load-config.js';
3
- export declare function getContentEntryType({ markdocConfigResult, astroConfig, }: {
3
+ import type { MarkdocIntegrationOptions } from './options.js';
4
+ export declare function getContentEntryType({ markdocConfigResult, astroConfig, options, }: {
4
5
  astroConfig: AstroConfig;
5
6
  markdocConfigResult?: MarkdocConfigResult;
7
+ options?: MarkdocIntegrationOptions;
6
8
  }): Promise<ContentEntryType>;
@@ -2,13 +2,16 @@ import Markdoc from "@markdoc/markdoc";
2
2
  import matter from "gray-matter";
3
3
  import fs from "node:fs";
4
4
  import { fileURLToPath } from "node:url";
5
- import { isComponentConfig, isValidUrl, MarkdocError, prependForwardSlash } from "./utils.js";
6
- import { emitESMImage } from "astro/assets";
5
+ import { MarkdocError, isComponentConfig, isValidUrl, prependForwardSlash } from "./utils.js";
6
+ import { emitESMImage } from "astro/assets/utils";
7
7
  import path from "node:path";
8
+ import { htmlTokenTransform } from "./html/transform/html-token-transform.js";
8
9
  import { setupConfig } from "./runtime.js";
10
+ import { getMarkdocTokenizer } from "./tokenizer.js";
9
11
  async function getContentEntryType({
10
12
  markdocConfigResult,
11
- astroConfig
13
+ astroConfig,
14
+ options
12
15
  }) {
13
16
  return {
14
17
  extensions: [".mdoc"],
@@ -17,7 +20,11 @@ async function getContentEntryType({
17
20
  async getRenderModule({ contents, fileUrl, viteId }) {
18
21
  var _a, _b;
19
22
  const entry = getEntryInfo({ contents, fileUrl });
20
- const tokens = markdocTokenizer.tokenize(entry.body);
23
+ const tokenizer = getMarkdocTokenizer(options);
24
+ let tokens = tokenizer.tokenize(entry.body);
25
+ if (options == null ? void 0 : options.allowHTML) {
26
+ tokens = htmlTokenTransform(tokenizer, tokens);
27
+ }
21
28
  const ast = Markdoc.parse(tokens);
22
29
  const usedTags = getUsedTags(ast);
23
30
  const userMarkdocConfig = (markdocConfigResult == null ? void 0 : markdocConfigResult.config) ?? {};
@@ -37,7 +44,7 @@ async function getContentEntryType({
37
44
  }
38
45
  }
39
46
  const pluginContext = this;
40
- const markdocConfig = await setupConfig(userMarkdocConfig);
47
+ const markdocConfig = await setupConfig(userMarkdocConfig, options);
41
48
  const filePath = fileURLToPath(fileUrl);
42
49
  const validationErrors = Markdoc.validate(
43
50
  ast,
@@ -86,16 +93,19 @@ ${getStringifiedImports(componentConfigByNodeMap, "Node", astroConfig.root)}
86
93
  const tagComponentMap = ${getStringifiedMap(componentConfigByTagMap, "Tag")};
87
94
  const nodeComponentMap = ${getStringifiedMap(componentConfigByNodeMap, "Node")};
88
95
 
96
+ const options = ${JSON.stringify(options)};
97
+
89
98
  const stringifiedAst = ${JSON.stringify(
90
99
  /* Double stringify to encode *as* stringified JSON */
91
100
  JSON.stringify(ast)
92
101
  )};
93
102
 
94
- export const getHeadings = createGetHeadings(stringifiedAst, markdocConfig);
103
+ export const getHeadings = createGetHeadings(stringifiedAst, markdocConfig, options);
95
104
  export const Content = createContentComponent(
96
105
  Renderer,
97
106
  stringifiedAst,
98
107
  markdocConfig,
108
+ options,
99
109
  tagComponentMap,
100
110
  nodeComponentMap,
101
111
  )`;
@@ -107,11 +117,6 @@ export const Content = createContentComponent(
107
117
  )
108
118
  };
109
119
  }
110
- const markdocTokenizer = new Markdoc.Tokenizer({
111
- // Strip <!-- comments --> from rendered output
112
- // Without this, they're rendered as strings!
113
- allowComments: true
114
- });
115
120
  function getUsedTags(markdocAst) {
116
121
  const tags = /* @__PURE__ */ new Set();
117
122
  const validationErrors = Markdoc.validate(markdocAst);
@@ -0,0 +1 @@
1
+ export declare function parseInlineCSSToReactLikeObject(css: string | undefined | null): React.CSSProperties | undefined;
@@ -0,0 +1,21 @@
1
+ import { styleToObject } from "./style-to-object.js";
2
+ function parseInlineCSSToReactLikeObject(css) {
3
+ if (typeof css === "string") {
4
+ const cssObject = {};
5
+ styleToObject(css, (originalCssDirective, value) => {
6
+ const reactCssDirective = convertCssDirectiveNameToReactCamelCase(originalCssDirective);
7
+ cssObject[reactCssDirective] = value;
8
+ });
9
+ return cssObject;
10
+ }
11
+ return void 0;
12
+ }
13
+ function convertCssDirectiveNameToReactCamelCase(original) {
14
+ const replaced = original.replace(/-([a-z0-9])/gi, (_match, char) => {
15
+ return char.toUpperCase();
16
+ });
17
+ return replaced;
18
+ }
19
+ export {
20
+ parseInlineCSSToReactLikeObject
21
+ };
@@ -0,0 +1,8 @@
1
+ /**
2
+ * @param {String} style
3
+ * @param {Object} [options]
4
+ * @return {Object[]}
5
+ * @throws {TypeError}
6
+ * @throws {Error}
7
+ */
8
+ export declare function parseInlineStyles(style: any, options: any): any[];
@@ -0,0 +1,153 @@
1
+ /**
2
+ * @license MIT
3
+ *
4
+ * (The MIT License)
5
+ *
6
+ * Copyright (c) 2012 TJ Holowaychuk <tj@vision-media.ca>
7
+ *
8
+ * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the 'Software'), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
9
+ *
10
+ * The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
11
+ *
12
+ * THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
13
+ */
14
+ const COMMENT_REGEX = /\/\*[^*]*\*+([^/*][^*]*\*+)*\//g;
15
+ const NEWLINE_REGEX = /\n/g;
16
+ const WHITESPACE_REGEX = /^\s*/;
17
+ const PROPERTY_REGEX = /^(\*?[-#/*\\\w]+(\[[0-9a-z_-]+\])?)\s*/;
18
+ const COLON_REGEX = /^:\s*/;
19
+ const VALUE_REGEX = /^((?:'(?:\\'|.)*?'|"(?:\\"|.)*?"|\([^)]*?\)|[^};])+)/;
20
+ const SEMICOLON_REGEX = /^[;\s]*/;
21
+ const TRIM_REGEX = /^\s+|\s+$/g;
22
+ const NEWLINE = "\n";
23
+ const FORWARD_SLASH = "/";
24
+ const ASTERISK = "*";
25
+ const EMPTY_STRING = "";
26
+ const TYPE_COMMENT = "comment";
27
+ const TYPE_DECLARATION = "declaration";
28
+ function parseInlineStyles(style, options) {
29
+ if (typeof style !== "string") {
30
+ throw new TypeError("First argument must be a string");
31
+ }
32
+ if (!style)
33
+ return [];
34
+ options = options || {};
35
+ let lineno = 1;
36
+ let column = 1;
37
+ function updatePosition(str) {
38
+ let lines = str.match(NEWLINE_REGEX);
39
+ if (lines)
40
+ lineno += lines.length;
41
+ let i = str.lastIndexOf(NEWLINE);
42
+ column = ~i ? str.length - i : column + str.length;
43
+ }
44
+ function position() {
45
+ let start = { line: lineno, column };
46
+ return function(node) {
47
+ node.position = new Position(start);
48
+ whitespace();
49
+ return node;
50
+ };
51
+ }
52
+ function Position(start) {
53
+ this.start = start;
54
+ this.end = { line: lineno, column };
55
+ this.source = options.source;
56
+ }
57
+ Position.prototype.content = style;
58
+ const errorsList = [];
59
+ function error(msg) {
60
+ const err = new Error(options.source + ":" + lineno + ":" + column + ": " + msg);
61
+ err.reason = msg;
62
+ err.filename = options.source;
63
+ err.line = lineno;
64
+ err.column = column;
65
+ err.source = style;
66
+ if (options.silent) {
67
+ errorsList.push(err);
68
+ } else {
69
+ throw err;
70
+ }
71
+ }
72
+ function match(re) {
73
+ const m = re.exec(style);
74
+ if (!m)
75
+ return;
76
+ const str = m[0];
77
+ updatePosition(str);
78
+ style = style.slice(str.length);
79
+ return m;
80
+ }
81
+ function whitespace() {
82
+ match(WHITESPACE_REGEX);
83
+ }
84
+ function comments(rules) {
85
+ let c;
86
+ rules = rules || [];
87
+ while (c = comment()) {
88
+ if (c !== false) {
89
+ rules.push(c);
90
+ }
91
+ }
92
+ return rules;
93
+ }
94
+ function comment() {
95
+ const pos = position();
96
+ if (FORWARD_SLASH != style.charAt(0) || ASTERISK != style.charAt(1))
97
+ return;
98
+ let i = 2;
99
+ while (EMPTY_STRING != style.charAt(i) && (ASTERISK != style.charAt(i) || FORWARD_SLASH != style.charAt(i + 1))) {
100
+ ++i;
101
+ }
102
+ i += 2;
103
+ if (EMPTY_STRING === style.charAt(i - 1)) {
104
+ return error("End of comment missing");
105
+ }
106
+ const str = style.slice(2, i - 2);
107
+ column += 2;
108
+ updatePosition(str);
109
+ style = style.slice(i);
110
+ column += 2;
111
+ return pos({
112
+ type: TYPE_COMMENT,
113
+ comment: str
114
+ });
115
+ }
116
+ function declaration() {
117
+ const pos = position();
118
+ const prop = match(PROPERTY_REGEX);
119
+ if (!prop)
120
+ return;
121
+ comment();
122
+ if (!match(COLON_REGEX))
123
+ return error("property missing ':'");
124
+ const val = match(VALUE_REGEX);
125
+ const ret = pos({
126
+ type: TYPE_DECLARATION,
127
+ property: trim(prop[0].replace(COMMENT_REGEX, EMPTY_STRING)),
128
+ value: val ? trim(val[0].replace(COMMENT_REGEX, EMPTY_STRING)) : EMPTY_STRING
129
+ });
130
+ match(SEMICOLON_REGEX);
131
+ return ret;
132
+ }
133
+ function declarations() {
134
+ const decls = [];
135
+ comments(decls);
136
+ let decl;
137
+ while (decl = declaration()) {
138
+ if (decl !== false) {
139
+ decls.push(decl);
140
+ comments(decls);
141
+ }
142
+ }
143
+ return decls;
144
+ }
145
+ whitespace();
146
+ return declarations();
147
+ }
148
+ function trim(str) {
149
+ return str ? str.replace(TRIM_REGEX, EMPTY_STRING) : EMPTY_STRING;
150
+ }
151
+ export {
152
+ parseInlineStyles
153
+ };
@@ -0,0 +1,12 @@
1
+ /**
2
+ * Parses inline style to object.
3
+ *
4
+ * @example
5
+ * // returns { 'line-height': '42' }
6
+ * styleToObject('line-height: 42;');
7
+ *
8
+ * @param {String} style - The inline style.
9
+ * @param {Function} [iterator] - The iterator function.
10
+ * @return {null|Object}
11
+ */
12
+ export declare function styleToObject(style: any, iterator: any): {} | null;
@@ -0,0 +1,53 @@
1
+ /**
2
+ * @license MIT
3
+ *
4
+ * The MIT License (MIT)
5
+ *
6
+ * Copyright (c) 2017 Menglin "Mark" Xu <mark@remarkablemark.org>
7
+ *
8
+ * Permission is hereby granted, free of charge, to any person obtaining
9
+ * a copy of this software and associated documentation files (the
10
+ * "Software"), to deal in the Software without restriction, including
11
+ * without limitation the rights to use, copy, modify, merge, publish,
12
+ * distribute, sublicense, and/or sell copies of the Software, and to
13
+ * permit persons to whom the Software is furnished to do so, subject to
14
+ * the following conditions:
15
+ *
16
+ * The above copyright notice and this permission notice shall be
17
+ * included in all copies or substantial portions of the Software.
18
+ *
19
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
20
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
21
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
22
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
23
+ * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
24
+ * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
25
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
26
+ */
27
+ import { parseInlineStyles } from "./parse-inline-styles.js";
28
+ function styleToObject(style, iterator) {
29
+ let output = null;
30
+ if (!style || typeof style !== "string") {
31
+ return output;
32
+ }
33
+ let declaration;
34
+ let declarations = parseInlineStyles(style);
35
+ let hasIterator = typeof iterator === "function";
36
+ let property;
37
+ let value;
38
+ for (let i = 0, len = declarations.length; i < len; i++) {
39
+ declaration = declarations[i];
40
+ property = declaration.property;
41
+ value = declaration.value;
42
+ if (hasIterator) {
43
+ iterator(property, value, declaration);
44
+ } else if (value) {
45
+ output || (output = {});
46
+ output[property] = value;
47
+ }
48
+ }
49
+ return output;
50
+ }
51
+ export {
52
+ styleToObject
53
+ };
@@ -0,0 +1,2 @@
1
+ export { htmlTag } from './tagdefs/html.tag';
2
+ export { htmlTokenTransform } from './transform/html-token-transform';
@@ -0,0 +1,6 @@
1
+ import { htmlTag } from "./tagdefs/html.tag";
2
+ import { htmlTokenTransform } from "./transform/html-token-transform";
3
+ export {
4
+ htmlTag,
5
+ htmlTokenTransform
6
+ };
@@ -0,0 +1,2 @@
1
+ import type { Config, Schema } from '@markdoc/markdoc';
2
+ export declare const htmlTag: Schema<Config, never>;
@@ -0,0 +1,21 @@
1
+ import Markdoc from "@markdoc/markdoc";
2
+ import { parseInlineCSSToReactLikeObject } from "../css/parse-inline-css-to-react.js";
3
+ const htmlTag = {
4
+ attributes: {
5
+ name: { type: String, required: true },
6
+ attrs: { type: Object }
7
+ },
8
+ transform(node, config) {
9
+ const { name, attrs: unsafeAttributes } = node.attributes;
10
+ const children = node.transformChildren(config);
11
+ const { style, ...safeAttributes } = unsafeAttributes;
12
+ if (typeof style === "string") {
13
+ const styleObject = parseInlineCSSToReactLikeObject(style);
14
+ safeAttributes.style = styleObject;
15
+ }
16
+ return new Markdoc.Tag(name, safeAttributes, children);
17
+ }
18
+ };
19
+ export {
20
+ htmlTag
21
+ };
@@ -0,0 +1,3 @@
1
+ import { Tokenizer } from '@markdoc/markdoc';
2
+ import type * as Token from 'markdown-it/lib/token';
3
+ export declare function htmlTokenTransform(tokenizer: Tokenizer, tokens: Token[]): Token[];
@@ -0,0 +1,154 @@
1
+ import { Parser } from "htmlparser2";
2
+ function htmlTokenTransform(tokenizer, tokens) {
3
+ const output = [];
4
+ let textBuffer = "";
5
+ let inCDATA = false;
6
+ const appendText = (text) => {
7
+ textBuffer += text;
8
+ };
9
+ const processTextBuffer = () => {
10
+ if (textBuffer.length > 0) {
11
+ const toks = tokenizer.tokenize(textBuffer);
12
+ if (toks.length === 3) {
13
+ const first = toks[0];
14
+ const second = toks[1];
15
+ const third = toks.at(2);
16
+ if (first.type === "paragraph_open" && second.type === "inline" && third && third.type === "paragraph_close" && Array.isArray(second.children)) {
17
+ for (const tok of second.children) {
18
+ if (tok.type === "text") {
19
+ if (tok.content.trim() == textBuffer.trim()) {
20
+ tok.content = textBuffer;
21
+ }
22
+ }
23
+ output.push(tok);
24
+ }
25
+ } else {
26
+ for (const tok of toks) {
27
+ output.push(tok);
28
+ }
29
+ }
30
+ } else {
31
+ for (const tok of toks) {
32
+ output.push(tok);
33
+ }
34
+ }
35
+ textBuffer = "";
36
+ }
37
+ };
38
+ const parser = new Parser(
39
+ {
40
+ oncdatastart() {
41
+ inCDATA = true;
42
+ },
43
+ oncdataend() {
44
+ inCDATA = false;
45
+ },
46
+ // when an HTML tag opens...
47
+ onopentag(name, attrs) {
48
+ processTextBuffer();
49
+ output.push({
50
+ type: "tag_open",
51
+ nesting: 1,
52
+ meta: {
53
+ tag: "html-tag",
54
+ attributes: [
55
+ { type: "attribute", name: "name", value: name },
56
+ { type: "attribute", name: "attrs", value: attrs }
57
+ ]
58
+ }
59
+ });
60
+ },
61
+ ontext(content) {
62
+ if (inCDATA) {
63
+ return;
64
+ }
65
+ if (typeof content === "string") {
66
+ appendText(content);
67
+ }
68
+ },
69
+ // when an HTML tag closes...
70
+ onclosetag(name) {
71
+ processTextBuffer();
72
+ output.push({
73
+ type: "tag_close",
74
+ nesting: -1,
75
+ meta: {
76
+ tag: "html-tag",
77
+ attributes: [{ type: "attribute", name: "name", value: name }]
78
+ }
79
+ });
80
+ }
81
+ },
82
+ {
83
+ decodeEntities: false,
84
+ recognizeCDATA: true,
85
+ recognizeSelfClosing: true
86
+ }
87
+ );
88
+ for (const token of tokens) {
89
+ if (token.type.startsWith("html")) {
90
+ parser.write(token.content);
91
+ continue;
92
+ }
93
+ if (token.type === "inline") {
94
+ if (token.children) {
95
+ token.children = htmlTokenTransform(tokenizer, token.children);
96
+ }
97
+ }
98
+ output.push(token);
99
+ }
100
+ processTextBuffer();
101
+ mutateAndCollapseExtraParagraphsUnderHtml(output);
102
+ return output;
103
+ }
104
+ function mutateAndCollapseExtraParagraphsUnderHtml(tokens) {
105
+ let done = false;
106
+ while (!done) {
107
+ const idx = findExtraParagraphUnderHtml(tokens);
108
+ if (typeof idx === "number") {
109
+ const actualChildTokens = tokens[idx + 2].children ?? [];
110
+ tokens.splice(idx, 5, ...actualChildTokens);
111
+ } else {
112
+ done = true;
113
+ }
114
+ }
115
+ }
116
+ function findExtraParagraphUnderHtml(tokens) {
117
+ if (tokens.length < 5) {
118
+ return null;
119
+ }
120
+ for (let i = 0; i < tokens.length; i++) {
121
+ const last = i + 4;
122
+ if (last > tokens.length - 1) {
123
+ break;
124
+ }
125
+ const slice = tokens.slice(i, last + 1);
126
+ const isMatch = isExtraParagraphPatternMatch(slice);
127
+ if (isMatch) {
128
+ return i;
129
+ }
130
+ }
131
+ return null;
132
+ }
133
+ function isExtraParagraphPatternMatch(slice) {
134
+ const match = isHtmlTagOpen(slice[0]) && isParagraphOpen(slice[1]) && isInline(slice[2]) && isParagraphClose(slice[3]) && isHtmlTagClose(slice[4]);
135
+ return match;
136
+ }
137
+ function isHtmlTagOpen(token) {
138
+ return token.type === "tag_open" && token.meta && token.meta.tag === "html-tag";
139
+ }
140
+ function isHtmlTagClose(token) {
141
+ return token.type === "tag_close" && token.meta && token.meta.tag === "html-tag";
142
+ }
143
+ function isParagraphOpen(token) {
144
+ return token.type === "paragraph_open";
145
+ }
146
+ function isParagraphClose(token) {
147
+ return token.type === "paragraph_close";
148
+ }
149
+ function isInline(token) {
150
+ return token.type === "inline";
151
+ }
152
+ export {
153
+ htmlTokenTransform
154
+ };
package/dist/index.d.ts CHANGED
@@ -1,2 +1,3 @@
1
1
  import type { AstroIntegration } from 'astro';
2
- export default function markdocIntegration(legacyConfig?: any): AstroIntegration;
2
+ import type { MarkdocIntegrationOptions } from './options.js';
3
+ export default function markdocIntegration(options?: MarkdocIntegrationOptions): AstroIntegration;
package/dist/index.js CHANGED
@@ -1,18 +1,9 @@
1
- import { bold, red } from "kleur/colors";
2
1
  import { getContentEntryType } from "./content-entry-type.js";
3
2
  import {
4
- loadMarkdocConfig,
5
- SUPPORTED_MARKDOC_CONFIG_FILES
3
+ SUPPORTED_MARKDOC_CONFIG_FILES,
4
+ loadMarkdocConfig
6
5
  } from "./load-config.js";
7
- function markdocIntegration(legacyConfig) {
8
- if (legacyConfig) {
9
- console.log(
10
- `${red(
11
- bold("[Markdoc]")
12
- )} Passing Markdoc config from your \`astro.config\` is no longer supported. Configuration should be exported from a \`markdoc.config.mjs\` file. See the configuration docs for more: https://docs.astro.build/en/guides/integrations-guide/markdoc/#configuration`
13
- );
14
- process.exit(0);
15
- }
6
+ function markdocIntegration(options) {
16
7
  let markdocConfigResult;
17
8
  let astroConfig;
18
9
  return {
@@ -22,7 +13,9 @@ function markdocIntegration(legacyConfig) {
22
13
  const { updateConfig, addContentEntryType } = params;
23
14
  astroConfig = params.config;
24
15
  markdocConfigResult = await loadMarkdocConfig(astroConfig);
25
- addContentEntryType(await getContentEntryType({ markdocConfigResult, astroConfig }));
16
+ addContentEntryType(
17
+ await getContentEntryType({ markdocConfigResult, astroConfig, options })
18
+ );
26
19
  updateConfig({
27
20
  vite: {
28
21
  ssr: {
@@ -0,0 +1,3 @@
1
+ export interface MarkdocIntegrationOptions {
2
+ allowHTML?: boolean;
3
+ }
File without changes
package/dist/runtime.d.ts CHANGED
@@ -2,18 +2,19 @@ import type { MarkdownHeading } from '@astrojs/markdown-remark';
2
2
  import { type NodeType, type RenderableTreeNode } from '@markdoc/markdoc';
3
3
  import type { AstroInstance } from 'astro';
4
4
  import type { AstroMarkdocConfig } from './config.js';
5
+ import type { MarkdocIntegrationOptions } from './options.js';
5
6
  /**
6
7
  * Merge user config with default config and set up context (ex. heading ID slugger)
7
8
  * Called on each file's individual transform.
8
9
  * TODO: virtual module to merge configs per-build instead of per-file?
9
10
  */
10
- export declare function setupConfig(userConfig?: AstroMarkdocConfig): Promise<MergedConfig>;
11
+ export declare function setupConfig(userConfig: AstroMarkdocConfig | undefined, options: MarkdocIntegrationOptions | undefined): Promise<MergedConfig>;
11
12
  /** Used for synchronous `getHeadings()` function */
12
- export declare function setupConfigSync(userConfig?: AstroMarkdocConfig): MergedConfig;
13
+ export declare function setupConfigSync(userConfig: AstroMarkdocConfig | undefined, options: MarkdocIntegrationOptions | undefined): MergedConfig;
13
14
  type MergedConfig = Required<Omit<AstroMarkdocConfig, 'extends'>>;
14
15
  /** Merge function from `@markdoc/markdoc` internals */
15
16
  export declare function mergeConfig(configA: AstroMarkdocConfig, configB: AstroMarkdocConfig): MergedConfig;
16
- export declare function resolveComponentImports(markdocConfig: Required<Pick<AstroMarkdocConfig, 'tags' | 'nodes'>>, tagComponentMap: Record<string, AstroInstance['default']>, nodeComponentMap: Record<NodeType, AstroInstance['default']>): Required<Pick<AstroMarkdocConfig, "tags" | "nodes">>;
17
+ export declare function resolveComponentImports(markdocConfig: Required<Pick<AstroMarkdocConfig, 'tags' | 'nodes'>>, tagComponentMap: Record<string, AstroInstance['default']>, nodeComponentMap: Record<NodeType, AstroInstance['default']>): Required<Pick<AstroMarkdocConfig, "nodes" | "tags">>;
17
18
  /**
18
19
  * Get text content as a string from a Markdoc transform AST
19
20
  */
@@ -23,6 +24,6 @@ export declare function getTextContent(childNodes: RenderableTreeNode[]): string
23
24
  * for `headings` result on `render()` return value
24
25
  */
25
26
  export declare function collectHeadings(children: RenderableTreeNode[], collectedHeadings: MarkdownHeading[]): void;
26
- export declare function createGetHeadings(stringifiedAst: string, userConfig: AstroMarkdocConfig): () => MarkdownHeading[];
27
- export declare function createContentComponent(Renderer: AstroInstance['default'], stringifiedAst: string, userConfig: AstroMarkdocConfig, tagComponentMap: Record<string, AstroInstance['default']>, nodeComponentMap: Record<NodeType, AstroInstance['default']>): any;
27
+ export declare function createGetHeadings(stringifiedAst: string, userConfig: AstroMarkdocConfig, options: MarkdocIntegrationOptions | undefined): () => MarkdownHeading[];
28
+ export declare function createContentComponent(Renderer: AstroInstance['default'], stringifiedAst: string, userConfig: AstroMarkdocConfig, options: MarkdocIntegrationOptions | undefined, tagComponentMap: Record<string, AstroInstance['default']>, nodeComponentMap: Record<NodeType, AstroInstance['default']>): any;
28
29
  export {};
package/dist/runtime.js CHANGED
@@ -1,7 +1,8 @@
1
1
  import Markdoc from "@markdoc/markdoc";
2
2
  import { createComponent, renderComponent } from "astro/runtime/server/index.js";
3
3
  import { setupHeadingConfig } from "./heading-ids.js";
4
- async function setupConfig(userConfig = {}) {
4
+ import { htmlTag } from "./html/tagdefs/html.tag.js";
5
+ async function setupConfig(userConfig = {}, options) {
5
6
  let defaultConfig = setupHeadingConfig();
6
7
  if (userConfig.extends) {
7
8
  for (let extension of userConfig.extends) {
@@ -11,11 +12,19 @@ async function setupConfig(userConfig = {}) {
11
12
  defaultConfig = mergeConfig(defaultConfig, extension);
12
13
  }
13
14
  }
14
- return mergeConfig(defaultConfig, userConfig);
15
+ let merged = mergeConfig(defaultConfig, userConfig);
16
+ if (options == null ? void 0 : options.allowHTML) {
17
+ merged = mergeConfig(merged, HTML_CONFIG);
18
+ }
19
+ return merged;
15
20
  }
16
- function setupConfigSync(userConfig = {}) {
21
+ function setupConfigSync(userConfig = {}, options) {
17
22
  const defaultConfig = setupHeadingConfig();
18
- return mergeConfig(defaultConfig, userConfig);
23
+ let merged = mergeConfig(defaultConfig, userConfig);
24
+ if (options == null ? void 0 : options.allowHTML) {
25
+ merged = mergeConfig(merged, HTML_CONFIG);
26
+ }
27
+ return merged;
19
28
  }
20
29
  function mergeConfig(configA, configB) {
21
30
  return {
@@ -100,9 +109,9 @@ function collectHeadings(children, collectedHeadings) {
100
109
  collectHeadings(node.children, collectedHeadings);
101
110
  }
102
111
  }
103
- function createGetHeadings(stringifiedAst, userConfig) {
112
+ function createGetHeadings(stringifiedAst, userConfig, options) {
104
113
  return function getHeadings() {
105
- const config = setupConfigSync(userConfig);
114
+ const config = setupConfigSync(userConfig, options);
106
115
  const ast = Markdoc.Ast.fromJSON(stringifiedAst);
107
116
  const content = Markdoc.transform(ast, config);
108
117
  let collectedHeadings = [];
@@ -110,12 +119,12 @@ function createGetHeadings(stringifiedAst, userConfig) {
110
119
  return collectedHeadings;
111
120
  };
112
121
  }
113
- function createContentComponent(Renderer, stringifiedAst, userConfig, tagComponentMap, nodeComponentMap) {
122
+ function createContentComponent(Renderer, stringifiedAst, userConfig, options, tagComponentMap, nodeComponentMap) {
114
123
  return createComponent({
115
124
  async factory(result, props) {
116
125
  const withVariables = mergeConfig(userConfig, { variables: props });
117
126
  const config = resolveComponentImports(
118
- await setupConfig(withVariables),
127
+ await setupConfig(withVariables, options),
119
128
  tagComponentMap,
120
129
  nodeComponentMap
121
130
  );
@@ -124,6 +133,11 @@ function createContentComponent(Renderer, stringifiedAst, userConfig, tagCompone
124
133
  propagation: "self"
125
134
  });
126
135
  }
136
+ const HTML_CONFIG = {
137
+ tags: {
138
+ "html-tag": htmlTag
139
+ }
140
+ };
127
141
  export {
128
142
  collectHeadings,
129
143
  createContentComponent,
@@ -0,0 +1,3 @@
1
+ import type { Tokenizer } from '@markdoc/markdoc';
2
+ import type { MarkdocIntegrationOptions } from './options.js';
3
+ export declare function getMarkdocTokenizer(options: MarkdocIntegrationOptions | undefined): Tokenizer;
@@ -0,0 +1,24 @@
1
+ import Markdoc from "@markdoc/markdoc";
2
+ function getMarkdocTokenizer(options) {
3
+ const key = cacheKey(options);
4
+ if (!_cachedMarkdocTokenizers[key]) {
5
+ const tokenizerOptions = {
6
+ // Strip <!-- comments --> from rendered output
7
+ // Without this, they're rendered as strings!
8
+ allowComments: true
9
+ };
10
+ if (options == null ? void 0 : options.allowHTML) {
11
+ tokenizerOptions.allowIndentation = true;
12
+ tokenizerOptions.html = true;
13
+ }
14
+ _cachedMarkdocTokenizers[key] = new Markdoc.Tokenizer(tokenizerOptions);
15
+ }
16
+ return _cachedMarkdocTokenizers[key];
17
+ }
18
+ let _cachedMarkdocTokenizers = {};
19
+ function cacheKey(options) {
20
+ return JSON.stringify(options);
21
+ }
22
+ export {
23
+ getMarkdocTokenizer
24
+ };
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@astrojs/markdoc",
3
3
  "description": "Add support for Markdoc in your Astro site",
4
- "version": "0.4.2",
4
+ "version": "0.4.4",
5
5
  "type": "module",
6
6
  "types": "./dist/index.d.ts",
7
7
  "author": "withastro",
@@ -62,25 +62,27 @@
62
62
  "esbuild": "^0.17.19",
63
63
  "github-slugger": "^2.0.0",
64
64
  "gray-matter": "^4.0.3",
65
+ "htmlparser2": "^9.0.0",
65
66
  "kleur": "^4.1.5",
66
67
  "shiki": "^0.14.1",
67
68
  "zod": "^3.17.3"
68
69
  },
69
70
  "peerDependencies": {
70
- "astro": "^2.8.1"
71
+ "astro": "^2.9.3"
71
72
  },
72
73
  "devDependencies": {
73
74
  "@astrojs/markdown-remark": "^2.2.1",
74
75
  "@types/chai": "^4.3.5",
75
76
  "@types/html-escaper": "^3.0.0",
77
+ "@types/markdown-it": "^12.2.3",
76
78
  "@types/mocha": "^9.1.1",
77
79
  "chai": "^4.3.7",
78
80
  "devalue": "^4.3.2",
79
81
  "linkedom": "^0.14.26",
80
82
  "mocha": "^9.2.2",
81
83
  "rollup": "^3.25.1",
82
- "vite": "^4.3.9",
83
- "astro": "2.8.1",
84
+ "vite": "^4.4.6",
85
+ "astro": "2.9.3",
84
86
  "astro-scripts": "0.0.14"
85
87
  },
86
88
  "engines": {