fix(web): remove redundant prop-types and unblock lint pipeline
Some checks failed
Build & Deploy / 🔍 Prepare (push) Successful in 10s
Build & Deploy / 🧪 QA (push) Failing after 2m24s
Build & Deploy / 🏗️ Build (push) Failing after 3m40s
Build & Deploy / 🚀 Deploy (push) Has been skipped
Build & Deploy / 🩺 Health Check (push) Has been skipped
Build & Deploy / 🔔 Notify (push) Successful in 3s
Some checks failed
Build & Deploy / 🔍 Prepare (push) Successful in 10s
Build & Deploy / 🧪 QA (push) Failing after 2m24s
Build & Deploy / 🏗️ Build (push) Failing after 3m40s
Build & Deploy / 🚀 Deploy (push) Has been skipped
Build & Deploy / 🩺 Health Check (push) Has been skipped
Build & Deploy / 🔔 Notify (push) Successful in 3s
This commit is contained in:
640
apps/web/src/payload/utils/lexicalParser.ts
Normal file
640
apps/web/src/payload/utils/lexicalParser.ts
Normal file
@@ -0,0 +1,640 @@
|
||||
/**
|
||||
* Converts a Markdown+JSX string into a Lexical AST node array.
|
||||
* Handles all registered Payload blocks and standard markdown formatting.
|
||||
*/
|
||||
|
||||
function propValue(chunk: string, prop: string): string {
|
||||
// Match prop="value" or prop='value' or prop={value}
|
||||
const match =
|
||||
chunk.match(new RegExp(`${prop}=["']([^"']+)["']`)) ||
|
||||
chunk.match(new RegExp(`${prop}=\\{([^}]+)\\}`));
|
||||
return match ? match[1] : "";
|
||||
}
|
||||
|
||||
function innerContent(chunk: string, tag: string): string {
|
||||
const match = chunk.match(
|
||||
new RegExp(`<${tag}(?:\\s[^>]*)?>([\\s\\S]*?)<\\/${tag}>`),
|
||||
);
|
||||
return match ? match[1].trim() : "";
|
||||
}
|
||||
|
||||
function blockNode(blockType: string, fields: Record<string, any>) {
|
||||
return {
|
||||
type: "block",
|
||||
format: "",
|
||||
version: 2,
|
||||
fields: { blockType, ...fields },
|
||||
};
|
||||
}
|
||||
|
||||
export function parseMarkdownToLexical(markdown: string): any[] {
|
||||
const textNode = (text: string) => ({
|
||||
type: "paragraph",
|
||||
format: "",
|
||||
indent: 0,
|
||||
version: 1,
|
||||
children: [{ mode: "normal", type: "text", text, version: 1 }],
|
||||
});
|
||||
|
||||
const nodes: any[] = [];
|
||||
|
||||
// Strip frontmatter
|
||||
let content = markdown;
|
||||
const fm = content.match(/^---\s*\n[\s\S]*?\n---/);
|
||||
if (fm) content = content.replace(fm[0], "").trim();
|
||||
|
||||
// Pre-process: reassemble multi-line JSX tags that got split by double-newline chunking.
|
||||
// This handles tags like <IconList>\n\n<IconListItem ... />\n\n</IconList>
|
||||
content = reassembleMultiLineJSX(content);
|
||||
|
||||
const rawChunks = content.split(/\n\s*\n/);
|
||||
|
||||
for (let chunk of rawChunks) {
|
||||
chunk = chunk.trim();
|
||||
if (!chunk) continue;
|
||||
|
||||
// === Self-closing tags (no children) ===
|
||||
|
||||
// ArticleMeme / MemeCard
|
||||
if (chunk.includes("<ArticleMeme") || chunk.includes("<MemeCard")) {
|
||||
nodes.push(
|
||||
blockNode("memeCard", {
|
||||
template: propValue(chunk, "template"),
|
||||
captions: propValue(chunk, "captions"),
|
||||
}),
|
||||
);
|
||||
continue;
|
||||
}
|
||||
|
||||
// BoldNumber
|
||||
if (chunk.includes("<BoldNumber")) {
|
||||
nodes.push(
|
||||
blockNode("boldNumber", {
|
||||
value: propValue(chunk, "value"),
|
||||
label: propValue(chunk, "label"),
|
||||
source: propValue(chunk, "source"),
|
||||
sourceUrl: propValue(chunk, "sourceUrl"),
|
||||
}),
|
||||
);
|
||||
continue;
|
||||
}
|
||||
|
||||
// WebVitalsScore
|
||||
if (chunk.includes("<WebVitalsScore")) {
|
||||
nodes.push(
|
||||
blockNode("webVitalsScore", {
|
||||
lcp: parseFloat(propValue(chunk, "lcp")) || 0,
|
||||
inp: parseFloat(propValue(chunk, "inp")) || 0,
|
||||
cls: parseFloat(propValue(chunk, "cls")) || 0,
|
||||
description: propValue(chunk, "description"),
|
||||
}),
|
||||
);
|
||||
continue;
|
||||
}
|
||||
|
||||
// LeadMagnet
|
||||
if (chunk.includes("<LeadMagnet")) {
|
||||
nodes.push(
|
||||
blockNode("leadMagnet", {
|
||||
title: propValue(chunk, "title"),
|
||||
description: propValue(chunk, "description"),
|
||||
buttonText: propValue(chunk, "buttonText") || "Jetzt anfragen",
|
||||
href: propValue(chunk, "href") || "/contact",
|
||||
variant: propValue(chunk, "variant") || "standard",
|
||||
}),
|
||||
);
|
||||
continue;
|
||||
}
|
||||
|
||||
// ComparisonRow
|
||||
if (chunk.includes("<ComparisonRow")) {
|
||||
nodes.push(
|
||||
blockNode("comparisonRow", {
|
||||
description: propValue(chunk, "description"),
|
||||
negativeLabel: propValue(chunk, "negativeLabel"),
|
||||
negativeText: propValue(chunk, "negativeText"),
|
||||
positiveLabel: propValue(chunk, "positiveLabel"),
|
||||
positiveText: propValue(chunk, "positiveText"),
|
||||
reverse: chunk.includes("reverse={true}"),
|
||||
}),
|
||||
);
|
||||
continue;
|
||||
}
|
||||
|
||||
// StatsDisplay
|
||||
if (chunk.includes("<StatsDisplay")) {
|
||||
nodes.push(
|
||||
blockNode("statsDisplay", {
|
||||
label: propValue(chunk, "label"),
|
||||
value: propValue(chunk, "value"),
|
||||
subtext: propValue(chunk, "subtext"),
|
||||
}),
|
||||
);
|
||||
continue;
|
||||
}
|
||||
|
||||
// MetricBar
|
||||
if (chunk.includes("<MetricBar")) {
|
||||
nodes.push(
|
||||
blockNode("metricBar", {
|
||||
label: propValue(chunk, "label"),
|
||||
value: parseFloat(propValue(chunk, "value")) || 0,
|
||||
max: parseFloat(propValue(chunk, "max")) || 100,
|
||||
unit: propValue(chunk, "unit") || "%",
|
||||
}),
|
||||
);
|
||||
continue;
|
||||
}
|
||||
|
||||
// ExternalLink
|
||||
if (chunk.includes("<ExternalLink")) {
|
||||
nodes.push(
|
||||
blockNode("externalLink", {
|
||||
href: propValue(chunk, "href"),
|
||||
label:
|
||||
propValue(chunk, "label") || innerContent(chunk, "ExternalLink"),
|
||||
}),
|
||||
);
|
||||
continue;
|
||||
}
|
||||
|
||||
// TrackedLink
|
||||
if (chunk.includes("<TrackedLink")) {
|
||||
nodes.push(
|
||||
blockNode("trackedLink", {
|
||||
href: propValue(chunk, "href"),
|
||||
label:
|
||||
propValue(chunk, "label") || innerContent(chunk, "TrackedLink"),
|
||||
eventName: propValue(chunk, "eventName"),
|
||||
}),
|
||||
);
|
||||
continue;
|
||||
}
|
||||
|
||||
// YouTube
|
||||
if (chunk.includes("<YouTubeEmbed")) {
|
||||
nodes.push(
|
||||
blockNode("youTubeEmbed", {
|
||||
videoId: propValue(chunk, "videoId"),
|
||||
}),
|
||||
);
|
||||
continue;
|
||||
}
|
||||
|
||||
// LinkedIn
|
||||
if (chunk.includes("<LinkedInEmbed")) {
|
||||
nodes.push(
|
||||
blockNode("linkedInEmbed", {
|
||||
url: propValue(chunk, "url"),
|
||||
}),
|
||||
);
|
||||
continue;
|
||||
}
|
||||
|
||||
// Twitter
|
||||
if (chunk.includes("<TwitterEmbed")) {
|
||||
nodes.push(
|
||||
blockNode("twitterEmbed", {
|
||||
url: propValue(chunk, "url"),
|
||||
}),
|
||||
);
|
||||
continue;
|
||||
}
|
||||
|
||||
// Interactive (self-closing, defaults only)
|
||||
if (chunk.includes("<RevenueLossCalculator")) {
|
||||
nodes.push(
|
||||
blockNode("revenueLossCalculator", {
|
||||
title: propValue(chunk, "title") || "Performance Revenue Simulator",
|
||||
}),
|
||||
);
|
||||
continue;
|
||||
}
|
||||
if (chunk.includes("<PerformanceChart")) {
|
||||
nodes.push(
|
||||
blockNode("performanceChart", {
|
||||
title: propValue(chunk, "title") || "Website Performance",
|
||||
}),
|
||||
);
|
||||
continue;
|
||||
}
|
||||
if (chunk.includes("<PerformanceROICalculator")) {
|
||||
nodes.push(
|
||||
blockNode("performanceROICalculator", {
|
||||
baseConversionRate:
|
||||
parseFloat(propValue(chunk, "baseConversionRate")) || 2.5,
|
||||
monthlyVisitors:
|
||||
parseInt(propValue(chunk, "monthlyVisitors")) || 50000,
|
||||
}),
|
||||
);
|
||||
continue;
|
||||
}
|
||||
if (chunk.includes("<LoadTimeSimulator")) {
|
||||
nodes.push(
|
||||
blockNode("loadTimeSimulator", {
|
||||
initialLoadTime:
|
||||
parseFloat(propValue(chunk, "initialLoadTime")) || 3.5,
|
||||
}),
|
||||
);
|
||||
continue;
|
||||
}
|
||||
if (chunk.includes("<ArchitectureBuilder")) {
|
||||
nodes.push(
|
||||
blockNode("architectureBuilder", {
|
||||
preset: propValue(chunk, "preset") || "standard",
|
||||
}),
|
||||
);
|
||||
continue;
|
||||
}
|
||||
if (chunk.includes("<DigitalAssetVisualizer")) {
|
||||
nodes.push(
|
||||
blockNode("digitalAssetVisualizer", {
|
||||
assetId: propValue(chunk, "assetId"),
|
||||
}),
|
||||
);
|
||||
continue;
|
||||
}
|
||||
|
||||
// === Tags with inner content ===
|
||||
|
||||
// TLDR
|
||||
if (chunk.includes("<TLDR>")) {
|
||||
const inner = innerContent(chunk, "TLDR");
|
||||
if (inner) {
|
||||
nodes.push(blockNode("mintelTldr", { content: inner }));
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
// Paragraph (handles <Paragraph>, <Paragraph ...attrs>)
|
||||
if (/<Paragraph[\s>]/.test(chunk)) {
|
||||
const inner = innerContent(chunk, "Paragraph");
|
||||
if (inner) {
|
||||
nodes.push(blockNode("mintelP", { text: inner }));
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
// H2 (handles <H2>, <H2 id="...">)
|
||||
if (/<H2[\s>]/.test(chunk)) {
|
||||
const inner = innerContent(chunk, "H2");
|
||||
if (inner) {
|
||||
nodes.push(
|
||||
blockNode("mintelHeading", {
|
||||
text: inner,
|
||||
seoLevel: "h2",
|
||||
displayLevel: "h2",
|
||||
}),
|
||||
);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
// H3 (handles <H3>, <H3 id="...">)
|
||||
if (/<H3[\s>]/.test(chunk)) {
|
||||
const inner = innerContent(chunk, "H3");
|
||||
if (inner) {
|
||||
nodes.push(
|
||||
blockNode("mintelHeading", {
|
||||
text: inner,
|
||||
seoLevel: "h3",
|
||||
displayLevel: "h3",
|
||||
}),
|
||||
);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
// Marker (inline highlight, usually inside Paragraph – store as standalone block)
|
||||
if (chunk.includes("<Marker>") && !chunk.includes("<Paragraph")) {
|
||||
const inner = innerContent(chunk, "Marker");
|
||||
if (inner) {
|
||||
nodes.push(blockNode("marker", { text: inner }));
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
// LeadParagraph
|
||||
if (chunk.includes("<LeadParagraph>")) {
|
||||
const inner = innerContent(chunk, "LeadParagraph");
|
||||
if (inner) {
|
||||
nodes.push(blockNode("leadParagraph", { text: inner }));
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
// ArticleBlockquote
|
||||
if (chunk.includes("<ArticleBlockquote")) {
|
||||
nodes.push(
|
||||
blockNode("articleBlockquote", {
|
||||
quote: innerContent(chunk, "ArticleBlockquote"),
|
||||
author: propValue(chunk, "author"),
|
||||
role: propValue(chunk, "role"),
|
||||
}),
|
||||
);
|
||||
continue;
|
||||
}
|
||||
|
||||
// ArticleQuote
|
||||
if (chunk.includes("<ArticleQuote")) {
|
||||
nodes.push(
|
||||
blockNode("articleQuote", {
|
||||
quote:
|
||||
innerContent(chunk, "ArticleQuote") || propValue(chunk, "quote"),
|
||||
author: propValue(chunk, "author"),
|
||||
role: propValue(chunk, "role"),
|
||||
source: propValue(chunk, "source"),
|
||||
}),
|
||||
);
|
||||
continue;
|
||||
}
|
||||
|
||||
// Mermaid
|
||||
if (chunk.includes("<Mermaid")) {
|
||||
nodes.push(
|
||||
blockNode("mermaid", {
|
||||
id: propValue(chunk, "id") || `chart-${Date.now()}`,
|
||||
title: propValue(chunk, "title"),
|
||||
showShare: chunk.includes("showShare={true}"),
|
||||
chartDefinition: innerContent(chunk, "Mermaid"),
|
||||
}),
|
||||
);
|
||||
continue;
|
||||
}
|
||||
|
||||
// Diagram variants (prefer inner definition, fall back to raw chunk text)
|
||||
if (chunk.includes("<DiagramFlow")) {
|
||||
nodes.push(
|
||||
blockNode("diagramFlow", {
|
||||
definition:
|
||||
innerContent(chunk, "DiagramFlow") ||
|
||||
propValue(chunk, "definition") ||
|
||||
chunk,
|
||||
}),
|
||||
);
|
||||
continue;
|
||||
}
|
||||
if (chunk.includes("<DiagramSequence")) {
|
||||
nodes.push(
|
||||
blockNode("diagramSequence", {
|
||||
definition:
|
||||
innerContent(chunk, "DiagramSequence") ||
|
||||
propValue(chunk, "definition") ||
|
||||
chunk,
|
||||
}),
|
||||
);
|
||||
continue;
|
||||
}
|
||||
if (chunk.includes("<DiagramGantt")) {
|
||||
nodes.push(
|
||||
blockNode("diagramGantt", {
|
||||
definition:
|
||||
innerContent(chunk, "DiagramGantt") ||
|
||||
propValue(chunk, "definition") ||
|
||||
chunk,
|
||||
}),
|
||||
);
|
||||
continue;
|
||||
}
|
||||
if (chunk.includes("<DiagramPie")) {
|
||||
nodes.push(
|
||||
blockNode("diagramPie", {
|
||||
definition:
|
||||
innerContent(chunk, "DiagramPie") ||
|
||||
propValue(chunk, "definition") ||
|
||||
chunk,
|
||||
}),
|
||||
);
|
||||
continue;
|
||||
}
|
||||
if (chunk.includes("<DiagramState")) {
|
||||
nodes.push(
|
||||
blockNode("diagramState", {
|
||||
definition:
|
||||
innerContent(chunk, "DiagramState") ||
|
||||
propValue(chunk, "definition") ||
|
||||
chunk,
|
||||
}),
|
||||
);
|
||||
continue;
|
||||
}
|
||||
if (chunk.includes("<DiagramTimeline")) {
|
||||
nodes.push(
|
||||
blockNode("diagramTimeline", {
|
||||
definition:
|
||||
innerContent(chunk, "DiagramTimeline") ||
|
||||
propValue(chunk, "definition") ||
|
||||
chunk,
|
||||
}),
|
||||
);
|
||||
continue;
|
||||
}
|
||||
|
||||
// Section (wrapping container – unwrap and parse inner content as top-level blocks)
|
||||
if (chunk.includes("<Section")) {
|
||||
const inner = innerContent(chunk, "Section");
|
||||
if (inner) {
|
||||
const innerNodes = parseMarkdownToLexical(inner);
|
||||
nodes.push(...innerNodes);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
// FAQSection (wrapping container)
|
||||
if (chunk.includes("<FAQSection")) {
|
||||
// FAQSection contains nested H3/Paragraph pairs.
|
||||
// We extract them as individual blocks instead.
|
||||
const faqContent = innerContent(chunk, "FAQSection");
|
||||
if (faqContent) {
|
||||
// Parse nested content recursively
|
||||
const innerNodes = parseMarkdownToLexical(faqContent);
|
||||
nodes.push(...innerNodes);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
// IconList with IconListItems
|
||||
if (chunk.includes("<IconList")) {
|
||||
const items: any[] = [];
|
||||
// Self-closing: <IconListItem icon="Check" title="..." description="..." />
|
||||
const itemMatches = chunk.matchAll(/<IconListItem\s+([^>]*?)\/>/g);
|
||||
for (const m of itemMatches) {
|
||||
const attrs = m[1];
|
||||
const title = (attrs.match(/title=["']([^"']+)["']/) || [])[1] || "";
|
||||
const desc =
|
||||
(attrs.match(/description=["']([^"']+)["']/) || [])[1] || "";
|
||||
items.push({
|
||||
icon: (attrs.match(/icon=["']([^"']+)["']/) || [])[1] || "Check",
|
||||
title: title || "•",
|
||||
description: desc,
|
||||
});
|
||||
}
|
||||
// Content-wrapped: <IconListItem check>HTML content</IconListItem>
|
||||
const itemMatches2 = chunk.matchAll(
|
||||
/<IconListItem([^>]*)>([\s\S]*?)<\/IconListItem>/g,
|
||||
);
|
||||
for (const m of itemMatches2) {
|
||||
const attrs = m[1] || "";
|
||||
const innerHtml = m[2].trim();
|
||||
// Use title attr if present, otherwise use inner HTML (stripped of tags) as title
|
||||
const titleAttr = (attrs.match(/title=["']([^"']+)["']/) || [])[1];
|
||||
const strippedInner = innerHtml.replace(/<[^>]+>/g, "").trim();
|
||||
items.push({
|
||||
icon: (attrs.match(/icon=["']([^"']+)["']/) || [])[1] || "Check",
|
||||
title: titleAttr || strippedInner || "•",
|
||||
description: "",
|
||||
});
|
||||
}
|
||||
if (items.length > 0) {
|
||||
nodes.push(blockNode("iconList", { items }));
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
// StatsGrid
|
||||
if (chunk.includes("<StatsGrid")) {
|
||||
const stats: any[] = [];
|
||||
const statMatches = chunk.matchAll(/<StatItem\s+([^>]*?)\/>/g);
|
||||
for (const m of statMatches) {
|
||||
const attrs = m[1];
|
||||
stats.push({
|
||||
label: (attrs.match(/label=["']([^"']+)["']/) || [])[1] || "",
|
||||
value: (attrs.match(/value=["']([^"']+)["']/) || [])[1] || "",
|
||||
});
|
||||
}
|
||||
// Also try inline props pattern
|
||||
if (stats.length === 0) {
|
||||
const innerStats = innerContent(chunk, "StatsGrid");
|
||||
if (innerStats) {
|
||||
// fallback: store the raw content
|
||||
nodes.push(blockNode("statsGrid", { stats: [] }));
|
||||
continue;
|
||||
}
|
||||
}
|
||||
nodes.push(blockNode("statsGrid", { stats }));
|
||||
continue;
|
||||
}
|
||||
|
||||
// PremiumComparisonChart
|
||||
if (chunk.includes("<PremiumComparisonChart")) {
|
||||
nodes.push(
|
||||
blockNode("premiumComparisonChart", {
|
||||
title: propValue(chunk, "title"),
|
||||
}),
|
||||
);
|
||||
continue;
|
||||
}
|
||||
|
||||
// WaterfallChart
|
||||
if (chunk.includes("<WaterfallChart")) {
|
||||
nodes.push(
|
||||
blockNode("waterfallChart", {
|
||||
title: propValue(chunk, "title"),
|
||||
}),
|
||||
);
|
||||
continue;
|
||||
}
|
||||
|
||||
// Reveal (animation wrapper – just pass through)
|
||||
if (chunk.includes("<Reveal")) {
|
||||
const inner = innerContent(chunk, "Reveal");
|
||||
if (inner) {
|
||||
// Parse inner content as regular nodes
|
||||
const innerNodes = parseMarkdownToLexical(inner);
|
||||
nodes.push(...innerNodes);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
// Standalone IconListItem (outside IconList context)
|
||||
if (chunk.includes("<IconListItem")) {
|
||||
// Skip – these should be inside an IconList
|
||||
continue;
|
||||
}
|
||||
|
||||
// Skip wrapper divs (like <div className="my-8">)
|
||||
if (/^<div\s/.test(chunk) || chunk === "</div>") {
|
||||
continue;
|
||||
}
|
||||
|
||||
// === Standard Markdown ===
|
||||
// CarouselBlock
|
||||
if (chunk.includes("<Carousel")) {
|
||||
const slides: any[] = [];
|
||||
const slideMatches = chunk.matchAll(/<Slide\s+([^>]*?)\/>/g);
|
||||
for (const m of slideMatches) {
|
||||
const attrs = m[1];
|
||||
slides.push({
|
||||
image: (attrs.match(/image=["']([^"']+)["']/) || [])[1] || "",
|
||||
caption: (attrs.match(/caption=["']([^"']+)["']/) || [])[1] || "",
|
||||
});
|
||||
}
|
||||
if (slides.length > 0) {
|
||||
nodes.push(blockNode("carousel", { slides }));
|
||||
continue;
|
||||
}
|
||||
}
|
||||
// Headings
|
||||
const headingMatch = chunk.match(/^(#{1,6})\s+(.*)/);
|
||||
if (headingMatch) {
|
||||
nodes.push({
|
||||
type: "heading",
|
||||
tag: `h${headingMatch[1].length}`,
|
||||
format: "",
|
||||
indent: 0,
|
||||
version: 1,
|
||||
direction: "ltr",
|
||||
children: [
|
||||
{ mode: "normal", type: "text", text: headingMatch[2], version: 1 },
|
||||
],
|
||||
});
|
||||
continue;
|
||||
}
|
||||
|
||||
// Default: plain text paragraph
|
||||
nodes.push(textNode(chunk));
|
||||
}
|
||||
|
||||
return nodes;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reassembles multi-line JSX tags that span across double-newline boundaries.
|
||||
* E.g. <IconList>\n\n<IconListItem.../>\n\n</IconList> becomes a single chunk.
|
||||
*/
|
||||
function reassembleMultiLineJSX(content: string): string {
|
||||
// Tags that wrap other content across paragraph breaks
|
||||
const wrapperTags = [
|
||||
"IconList",
|
||||
"StatsGrid",
|
||||
"FAQSection",
|
||||
"Section",
|
||||
"Reveal",
|
||||
"Carousel",
|
||||
];
|
||||
|
||||
for (const tag of wrapperTags) {
|
||||
const openRegex = new RegExp(`<${tag}[^>]*>`, "g");
|
||||
let match;
|
||||
while ((match = openRegex.exec(content)) !== null) {
|
||||
const openPos = match.index;
|
||||
const closeTag = `</${tag}>`;
|
||||
const closePos = content.indexOf(closeTag, openPos);
|
||||
if (closePos === -1) continue;
|
||||
|
||||
const fullEnd = closePos + closeTag.length;
|
||||
const fullBlock = content.substring(openPos, fullEnd);
|
||||
|
||||
// Replace double newlines inside this block with single newlines
|
||||
// so it stays as one chunk during splitting
|
||||
const collapsed = fullBlock.replace(/\n\s*\n/g, "\n");
|
||||
content =
|
||||
content.substring(0, openPos) + collapsed + content.substring(fullEnd);
|
||||
|
||||
// Adjust regex position
|
||||
openRegex.lastIndex = openPos + collapsed.length;
|
||||
}
|
||||
}
|
||||
|
||||
return content;
|
||||
}
|
||||
Reference in New Issue
Block a user