@lobehub/ui
Version:
Lobe UI is an open-source UI component library for building AIGC web apps
111 lines (109 loc) • 3.85 kB
JavaScript
import { visit } from "../../node_modules/unist-util-visit/lib/index.mjs";
//#region src/Markdown/plugins/remarkVideo.ts
/**
* Remark plugin to handle <video> tags in markdown text
* This plugin converts <video> tags to proper video elements
* without requiring allowHtml to be enabled
*
* @example
* <video src="https://example.com/video.mp4" />
* <video src="https://example.com/video.mp4" controls width="400" height="300" />
*/
const remarkVideo = (options = {}) => {
const { videoTags = ["video"] } = options;
return (tree) => {
visit(tree, "html", (node, index = 0, parent) => {
if (!node.value || typeof node.value !== "string") return;
for (const tagName of videoTags) {
const selfClosingPattern = `^<${tagName}([^>]*?)\\s*\\/?\\s*>$`;
const selfClosingMatch = node.value.trim().match(new RegExp(selfClosingPattern, "i"));
if (selfClosingMatch) {
const attributesStr = selfClosingMatch[1]?.trim() || "";
const properties = {};
const attrRegex = /(\w+)=["']([^"']*?)["']/g;
let attrMatch;
while ((attrMatch = attrRegex.exec(attributesStr)) !== null) properties[attrMatch[1]] = attrMatch[2];
const newNode = {
children: [],
data: {
hName: tagName,
hProperties: properties
},
type: tagName
};
parent.children.splice(index, 1, newNode);
return index;
}
const pairedPattern = `^<${tagName}([^>]*?)>(.*?)<\\/${tagName}>$`;
const pairedMatch = node.value.trim().match(new RegExp(pairedPattern, "is"));
if (pairedMatch) {
const attributesStr = pairedMatch[1]?.trim() || "";
const content = pairedMatch[2] || "";
const properties = {};
const attrRegex = /(\w+)=["']([^"']*?)["']/g;
let attrMatch;
while ((attrMatch = attrRegex.exec(attributesStr)) !== null) properties[attrMatch[1]] = attrMatch[2];
const newNode = {
children: content ? [{
type: "text",
value: content
}] : [],
data: {
hName: tagName,
hProperties: properties
},
type: tagName
};
parent.children.splice(index, 1, newNode);
return index;
}
}
});
visit(tree, "text", (node, index = 0, parent) => {
if (!node.value || typeof node.value !== "string") return;
for (const tagName of videoTags) {
const encodedSelfClosingPattern = `<${tagName}([^&]*?)\\s*\\/?\\s*>`;
const encodedSelfClosingRegex = new RegExp(encodedSelfClosingPattern, "gi");
if (!encodedSelfClosingRegex.test(node.value)) continue;
encodedSelfClosingRegex.lastIndex = 0;
const text = node.value;
const newNodes = [];
let lastIndex = 0;
let match;
while ((match = encodedSelfClosingRegex.exec(text)) !== null) {
const [fullMatch, attributesStr] = match;
const startIndex = match.index;
if (startIndex > lastIndex) newNodes.push({
type: "text",
value: text.slice(lastIndex, startIndex)
});
const decodedAttrs = attributesStr.replaceAll(""", "\"").replaceAll("'", "'").replaceAll("&", "&").replaceAll("<", "<").replaceAll(">", ">");
const properties = {};
const attrRegex = /(\w+)=["']([^"']*?)["']/g;
let attrMatch;
while ((attrMatch = attrRegex.exec(decodedAttrs)) !== null) properties[attrMatch[1]] = attrMatch[2];
newNodes.push({
children: [],
data: {
hName: tagName,
hProperties: properties
},
type: tagName
});
lastIndex = startIndex + fullMatch.length;
}
if (lastIndex < text.length) newNodes.push({
type: "text",
value: text.slice(lastIndex)
});
if (newNodes.length > 0 && parent) {
parent.children.splice(index, 1, ...newNodes);
return index + newNodes.length - 1;
}
}
});
};
};
//#endregion
export { remarkVideo };
//# sourceMappingURL=remarkVideo.mjs.map