Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 0 additions & 2 deletions packages/documentation-framework/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@
"@babel/core": "^7.29.0",
"@babel/preset-env": "7.29.0",
"@babel/preset-react": "^7.28.5",
"@mdx-js/util": "1.6.22",
"@patternfly/ast-helpers": "^1.26.2",
"@reach/router": "npm:@gatsbyjs/reach-router@1.3.9",
"@rspack/core": "^1.7.7",
Expand All @@ -30,7 +29,6 @@
"fs-extra": "9.1.0",
"glob": "12.0.0",
"handlebars": "4.7.8",
"hast-to-hyperscript": "9.0.1",
"hast-util-to-text": "2.0.1",
"html-formatter": "0.1.9",
"html-webpack-plugin": "5.6.6",
Expand Down
47 changes: 20 additions & 27 deletions packages/documentation-framework/scripts/md/auto-link-url.js
Original file line number Diff line number Diff line change
@@ -1,33 +1,26 @@
// https://github.com/remarkjs/remark/tree/main/packages/remark-parse#processoruseparse-options
// https://github.com/remarkjs/remark/blob/main/packages/remark-parse/lib/tokenize/auto-link.js
const decode = require('parse-entities');
// Re-adds support for <https://...> autolinks in MDX.
// remark-mdx treats angle brackets as JSX, so <https://google.com>
// fails to parse. This plugin converts autolinks to standard markdown
// link syntax [url](url) before the parser sees them.
//
// Replaces the old this.Parser.prototype.inlineTokenizers approach
// (removed in remark-parse v11). Compatible with unified v9 and v11.
function plugin() {
const self = this;
const originalParse = self.parse;

// Support `<https://google.com>`
// This is removed in MDX 2: https://github.com/mdx-js/mdx/issues/1049
function plugin({ beginMarker = '<http', endMarker = '>' } = {}) {
const Parser = this.Parser;
const tokenizers = Parser.prototype.inlineTokenizers;
const methods = Parser.prototype.inlineMethods;

tokenizers.autoLinkUrl = function autoLinkUrl(eat, value) {
if (!value.startsWith(beginMarker)) {
return;
self.parse = function (file) {
const str = String(file.contents || file.value || '');
// Match <http://...> or <https://...> autolinks and convert to [url](url)
const converted = str.replace(/<(https?:\/\/[^>]+)>/g, '[$1]($1)');
if (file.contents !== undefined) {
file.contents = converted;
}
const endIndex = value.indexOf(endMarker);
if (endIndex >= 1) {
const link = value.substr(1, endIndex);
return eat(value.substr(0, endIndex))({
type: 'link',
title: null,
url: decode(link, { nonTerminated: false })
});
if (file.value !== undefined) {
file.value = converted;
}
return true;
}
tokenizers.autoLinkUrl.locator = (value, fromIndex) => value.indexOf(beginMarker, fromIndex);

// Put before existing `autoLink` which will be deleted in remark-mdx
methods.splice(methods.indexOf('autoLink'), 0, 'autoLinkUrl')
return originalParse.call(this, file);
};
}

module.exports = plugin;
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,50 @@ const path = require('path');
const toHAST = require('mdast-util-to-hast');
const detab = require('detab');
const normalize = require('mdurl/encode');
const all = require('mdast-util-to-hast/lib/all');
// Inlined from mdast-util-to-hast/lib/all and lib/one (private APIs).
// `all` recursively transforms a node's children through the handler
// dispatch system. `one` dispatches a single node to the appropriate
// handler registered on `h`.
function one(h, node, parent) {
const type = node && node.type;
if (!type) {
throw new Error('Expected node, got `' + node + '`');
}
const fn = h.handlers[type] || h.unknownHandler;
if (typeof fn === 'function') {
return fn(h, node, parent);
}
// Fallback for unknown node types: wrap children in a div, or
// return a text value if the node carries one.
if (node.children) {
return h(node, 'div', all(h, node));
}
if (node.value) {
return { type: 'text', value: node.value };
}
}

function all(h, parent) {
const nodes = parent.children || [];
const values = [];
for (let i = 0; i < nodes.length; i++) {
const result = one(h, nodes[i], parent);
if (result) {
// After a break node, trim leading whitespace from the next sibling
if (i && nodes[i - 1].type === 'break') {
if (result.value) {
result.value = result.value.replace(/^\s+/, '');
}
const head = result.children && result.children[0];
if (head && head.value) {
head.value = head.value.replace(/^\s+/, '');
}
}
values.push(...(Array.isArray(result) ? result : [result]));
}
}
return values;
}
const styleToObject = require('style-to-object');
const camelCaseCSS = require('camelcase-css');
const { parseJSXAttributes } = require('./jsxAttributes');
Expand Down
85 changes: 63 additions & 22 deletions packages/documentation-framework/scripts/md/mdx-hast-to-jsx.js
Original file line number Diff line number Diff line change
@@ -1,14 +1,72 @@
const path = require('path');
const fs = require('fs');
const { serializeTags } = require('remark-mdx/lib/serialize/mdx-element');
const serializeMdxExpression = require('remark-mdx/lib/serialize/mdx-expression');
const toH = require('hast-to-hyperscript');
const { toTemplateLiteral } = require('@mdx-js/util');
const { parse } = require('@patternfly/ast-helpers');
const { capitalize } = require('../../helpers/capitalize');
const { slugger } = require('../../helpers/slugger');
const { liveCodeTypes } = require('../../helpers/liveCodeTypes');

// Inlined from remark-mdx/lib/serialize/mdx-expression (private API)
function serializeMdxExpression(node) {
const value = node.value || '';
const block = node.type === 'mdxBlockExpression';
const around = block ? '\n' : '';
const content = block ? indentStr(value) : value;
return '{' + around + content + around + '}';
}

// Inlined from remark-mdx/lib/util/indent (private API)
function indentStr(value) {
return value.split('\n').map(line => /\S/.test(line) ? ' ' + line : line).join('\n');
}

// Inlined from remark-mdx/lib/serialize/mdx-element serializeTags (private API)
function serializeTags(node) {
const name = String(node.name || '');
const selfClosing = name && node.children.length === 0;
const attrs = (node.attributes || []).map(attr => {
if (attr.type === 'mdxAttributeExpression') {
return serializeMdxExpression(attr);
}
const attrName = String(attr.name || '');
if (attr.value === null || attr.value === undefined) {
return attrName;
}
if (typeof attr.value === 'object') {
return attrName + '=' + serializeMdxExpression(attr.value);
}
return attrName + '="' + attr.value.replace(/"/g, '&quot;') + '"';
});
const attrStr = attrs.length > 0 ? ' ' + attrs.join(' ') : '';

return {
open: '<' + name + attrStr + (selfClosing ? '/' : '') + '>',
close: selfClosing ? null : '</' + name + '>'
};
}

// Inlined from @mdx-js/util toTemplateLiteral (private API)
function toTemplateLiteral(text) {
const escaped = text
.replace(/\\(?!\$)/g, '\\\\')
.replace(/`/g, '\\`')
.replace(/(\\\$)/g, '\\$1')
.replace(/(\\\$)(\{)/g, '\\$1\\$2')
.replace(/\$\{/g, '\\${');
return '{`' + escaped + '`}';
}

// Replacement for hast-to-hyperscript + fakeReactCreateElement.
// Extracts type and props from a HAST element node, normalizing
// className arrays to space-separated strings.
function hastToProps(node) {
const props = {};
for (const [key, val] of Object.entries(node.properties || {})) {
if (val === null || val === undefined) continue;
props[key] = Array.isArray(val) ? val.join(' ') : val;
}
return { type: node.tagName, props };
}

// Adapted from https://github.com/mdx-js/mdx/blob/next/packages/mdx/mdx-hast-to-jsx.js
function toJSX(node, parentNode = {}, options = {}) {
options.examples = options.examples || {};
Expand Down Expand Up @@ -133,15 +191,9 @@ export default Component;\n`;

function serializeElement(node, options) {
const { indent, examples } = options;
const { type, props } = toH(
fakeReactCreateElement,
Object.assign({}, node, {children: []}),
{prefix: false}
);
const { type, props } = hastToProps(node);
const content = serializeChildren(node, {...options});

delete props.key;

const srcImport = node.properties.src;
if (type === 'img') {
delete props.src;
Expand Down Expand Up @@ -236,15 +288,4 @@ function serializeChildren(node, options) {
.join(`\n${indentText}`);
}

// We only do this for the props, so we’re ignoring children.
function fakeReactCreateElement(name, props) {
return {
type: name,
props: props,
// Needed for `toH` to think this is React.
key: null,
_owner: null
}
}

module.exports = compile;
35 changes: 19 additions & 16 deletions packages/documentation-framework/scripts/md/remove-comments.js
Original file line number Diff line number Diff line change
@@ -1,21 +1,24 @@
// https://github.com/remarkjs/remark/tree/main/packages/remark-parse#processoruseparse-options
// Strips HTML comments from markdown source before the parser sees them.
// This replaces the old tokenizer-based approach that used deprecated
// this.Parser.prototype.blockTokenizers APIs (removed in remark-parse v11).
//
// Wraps the processor's parse method to strip comments from the raw
// vfile contents before remark-parse and remark-mdx process the text.
function plugin() {
const self = this;
const originalParse = self.parse;

function plugin({ beginMarker = '<!--', endMarker = '-->' } = {}) {
const Parser = this.Parser
const tokenizers = Parser.prototype.blockTokenizers
const methods = Parser.prototype.blockMethods

tokenizers.comments = function tokenizeComment(eat, value) {
const trimmed = value.trimRight();
const endIndex = trimmed.indexOf(endMarker);
if (trimmed.startsWith(beginMarker) && endIndex >= 1) {
eat(value.substr(0, endIndex + endMarker.length));
self.parse = function (file) {
const str = String(file.contents || file.value || '');
const stripped = str.replace(/<!--[\s\S]*?-->/g, '');
if (file.contents !== undefined) {
file.contents = stripped;
}
return true;
}

// Run it just before `html`.
methods.splice(methods.indexOf('html'), 0, 'comments')
if (file.value !== undefined) {
file.value = stripped;
}
return originalParse.call(this, file);
};
}

module.exports = plugin;
Loading