docusaurus-plugin-openapi-docs
Version:
OpenAPI plugin for Docusaurus.
167 lines (166 loc) • 6.5 kB
JavaScript
;
/* ============================================================================
* Copyright (c) Palo Alto Networks
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
* ========================================================================== */
Object.defineProperty(exports, "__esModule", { value: true });
exports.codeBlock = exports.curlyBrackets = exports.codeFence = exports.greaterThan = exports.lessThan = void 0;
exports.runWithExternalization = runWithExternalization;
exports.create = create;
exports.guard = guard;
exports.render = render;
exports.clean = clean;
/**
* Module-level externalization context.
* Note: AsyncLocalStorage would be cleaner but isn't available in browser bundles.
*/
let externalizationContext = null;
/**
* Components whose props should be externalized to separate JSON files.
* These are the components that typically receive large JSON objects.
*/
const EXTERNALIZABLE_COMPONENTS = new Set([
"StatusCodes",
"ParamsDetails",
"RequestSchema",
"Schema",
"SchemaItem",
]);
/**
* Runs a function with externalization enabled.
* Any calls to create() within the function will externalize eligible component props.
*
* @param baseFilename - Base filename for the MDX file (without extension)
* @param fn - Function to run with externalization enabled
* @returns The function result and any external files that were collected
*
* @example
* const { result: mdx, files } = runWithExternalization("add-pet", () => {
* return createApiPageMD(item);
* });
*/
function runWithExternalization(baseFilename, fn) {
// Set up context
externalizationContext = {
baseFilename,
componentCounters: {},
files: [],
};
try {
const result = fn();
const files = externalizationContext.files;
return { result, files };
}
finally {
// Always clear context
externalizationContext = null;
}
}
/**
* Creates a JSX component string with the given tag, props, and options.
* When called within runWithExternalization(), props for eligible
* components are externalized to a single JSON file and spread.
*/
function create(tag, props, options = {}) {
const { children, ...rest } = props;
let propString = "";
// Check if this component's props should be externalized
if (shouldExternalizeComponent(tag, rest)) {
const filename = generateExternalFilename(tag);
const content = JSON.stringify(rest);
// Add to external files
externalizationContext.files.push({
filename,
content,
});
// Use spread syntax with require
propString = `\n {...require("./${filename}")}`;
}
else {
// Inline props as usual
for (const [key, value] of Object.entries(rest)) {
propString += `\n ${key}={${JSON.stringify(value)}}`;
}
}
let indentedChildren = render(children).replace(/^/gm, " ");
if (options.inline) {
propString += `\n children={${JSON.stringify(children)}}`;
indentedChildren = "";
}
propString += propString ? "\n" : "";
indentedChildren += indentedChildren ? "\n" : "";
return `<${tag}${propString}>\n${indentedChildren}</${tag}>`;
}
/**
* Determines if a component's props should be externalized.
*/
function shouldExternalizeComponent(tag, props) {
// No context means externalization is not enabled
if (!externalizationContext) {
return false;
}
if (!EXTERNALIZABLE_COMPONENTS.has(tag)) {
return false;
}
// Don't externalize if props are empty or only contain undefined/null
const hasContent = Object.values(props).some((v) => v !== undefined && v !== null);
if (!hasContent) {
return false;
}
return true;
}
/**
* Generates a unique filename for an externalized component's props.
*/
function generateExternalFilename(componentName) {
var _a;
if (!externalizationContext) {
throw new Error("Externalization context not set");
}
const count = ((_a = externalizationContext.componentCounters[componentName]) !== null && _a !== void 0 ? _a : 0) + 1;
externalizationContext.componentCounters[componentName] = count;
const suffix = count > 1 ? `.${count}` : "";
return `${externalizationContext.baseFilename}.${componentName}${suffix}.json`;
}
function guard(value, cb) {
if (!!value || value === 0) {
const children = cb(value);
return render(children);
}
return "";
}
function render(children) {
if (Array.isArray(children)) {
const filteredChildren = children.filter((c) => c !== undefined);
return filteredChildren
.map((i) => (Array.isArray(i) ? i.join("") : i))
.join("");
}
return children !== null && children !== void 0 ? children : "";
}
// Regex to selectively URL-encode '>' and '<' chars
exports.lessThan = /<=?(?!(=|button|\s?\/button|code|\s?\/code|details|\s?\/details|summary|\s?\/summary|hr|\s?\/hr|br|\s?\/br|span|\s?\/span|strong|\s?\/strong|small|\s?\/small|table|\s?\/table|thead|\s?\/thead|tbody|\s?\/tbody|td|\s?\/td|tr|\s?\/tr|th|\s?\/th|h1|\s?\/h1|h2|\s?\/h2|h3|\s?\/h3|h4|\s?\/h4|h5|\s?\/h5|h6|\s?\/h6|title|\s?\/title|p|\s?\/p|em|\s?\/em|b|\s?\/b|i|\s?\/i|u|\s?\/u|strike|\s?\/strike|bold|\s?\/bold|a|\s?\/a|table|\s?\/table|li|\s?\/li|ol|\s?\/ol|ul|\s?\/ul|img|\s?\/img|svg|\s?\/svg|div|\s?\/div|center|\s?\/center))/gu;
exports.greaterThan = /(?<!(button|code|details|summary|hr|br|span|strong|small|table|thead|tbody|td|tr|th|h1|h2|h3|h4|h5|h6|title|p|em|b|i|u|strike|bold|a|li|ol|ul|img|svg|div|center|\/|\s|"|'))>/gu;
exports.codeFence = /`{1,3}[\s\S]*?`{1,3}/g;
exports.curlyBrackets = /([{}])/g;
exports.codeBlock = /(^```.*[\s\S]*?```$|`[^`].+?`)/gm;
function clean(value) {
if (!value) {
return "";
}
let sections = value.split(exports.codeBlock);
for (let sectionIndex in sections) {
if (!sections[sectionIndex].startsWith("`")) {
sections[sectionIndex] = sections[sectionIndex]
.replace(exports.lessThan, "<")
.replace(exports.greaterThan, ">")
.replace(exports.codeFence, function (match) {
return match.replace(/\\>/g, ">");
})
.replace(exports.curlyBrackets, "\\$1");
}
}
return sections.join("");
}