// ===== write-with-ai/insert.js — content processing + Gutenberg insertion ===== // Ports the proven helpers from the legacy inline template // (WriteWithAI::writeWithAIForm): strip code fences, unwrap , drop the // first

, wrap headings + keywords in , then convert // to blocks via rawHandler and insert (overwrite vs. insert-after). import { dispatch, select } from "@wordpress/data"; import { rawHandler } from "@wordpress/blocks"; const escapeHtml = (str) => String(str).replace(/&/g, "&").replace(//g, ">"); const escapeRegex = (str) => String(str).replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); const stripCodeFences = (htmlString) => { if (!htmlString) return ""; return htmlString .replace(/^\s*```(?:html|HTML)?\s*\n?/, "") .replace(/\n?\s*```\s*$/, ""); }; const getBodyContent = (htmlString) => { if (!htmlString) return ""; const cleaned = stripCodeFences(htmlString); const match = cleaned.match(/]*>([\s\S]*?)<\/body>/i); if (match) { return match[1].replace(/

\s+/g, "

"); } return cleaned; }; // Pull the AI-generated title out of the raw response: the first

's text. // Used when title generation was requested; the h1 itself is stripped from the // body by removeFirstHeading/cleanGeneratedHtml so it never lands in the content. export const extractFirstHeading = (rawHtml) => { const html = getBodyContent(rawHtml); const host = document.createElement("div"); host.innerHTML = html; const h1 = host.querySelector("h1"); return h1 ? (h1.textContent || "").trim() : ""; }; const removeFirstHeading = (htmlString) => { const newDiv = document.createElement("div"); newDiv.innerHTML = htmlString; const firstHeading = newDiv.querySelector("h1"); if (firstHeading) { firstHeading.parentNode.removeChild(firstHeading); } return newDiv.innerHTML; }; const wrapWithHighlight = (inputString, keywords) => { if (!inputString) return ""; const container = document.createElement("div"); container.innerHTML = inputString; // Wrap each heading's visible text with the highlight span (skip if present) container.querySelectorAll("h1, h2, h3, h4, h5, h6").forEach((heading) => { if (heading.querySelector(".highlight")) return; const text = heading.textContent; if (!text || !text.trim()) return; heading.innerHTML = '' + escapeHtml(text) + ""; }); // Wrap keywords only inside text nodes — never inside attributes/code/highlights const keywordList = (keywords || "") .split(",") .map((k) => k.trim()) .filter(Boolean); if (keywordList.length) { const pattern = new RegExp("\\b(" + keywordList.map(escapeRegex).join("|") + ")\\b", "gi"); const walker = document.createTreeWalker(container, NodeFilter.SHOW_TEXT, { acceptNode: (node) => { let parent = node.parentNode; while (parent && parent !== container) { if (parent.nodeType === 1) { const tag = parent.tagName.toLowerCase(); if (tag === "code" || tag === "pre") return NodeFilter.FILTER_REJECT; if (parent.classList && parent.classList.contains("highlight")) return NodeFilter.FILTER_REJECT; } parent = parent.parentNode; } return NodeFilter.FILTER_ACCEPT; }, }); const textNodes = []; let current; while ((current = walker.nextNode())) { textNodes.push(current); } textNodes.forEach((textNode) => { pattern.lastIndex = 0; if (!pattern.test(textNode.nodeValue)) return; pattern.lastIndex = 0; const fragmentHost = document.createElement("span"); fragmentHost.innerHTML = escapeHtml(textNode.nodeValue).replace(pattern, '$1'); const parent = textNode.parentNode; while (fragmentHost.firstChild) { parent.insertBefore(fragmentHost.firstChild, textNode); } parent.removeChild(textNode); }); } return container.innerHTML; }; // Normalize the raw OpenAI HTML into clean body content (used by the preview too). export const cleanGeneratedHtml = (rawHtml, keywords = "") => { let html = getBodyContent(rawHtml); html = removeFirstHeading(html); html = wrapWithHighlight(html, keywords); return html; }; // Insert processed HTML into the editor. `isOverwrite` replaces all blocks; // otherwise inserts after the selected block (or at the end). export const insertContentToEditor = ({ title, html, isOverwrite }) => { const blocks = rawHandler({ HTML: html }); if (title) { dispatch("core/editor").editPost({ title }); } const hasExistingContent = (select("core/block-editor").getBlocks() || []).length > 0; const selectedBlockClientId = select("core/block-editor").getSelectedBlockClientId(); if (isOverwrite || !hasExistingContent) { const allBlocks = select("core/block-editor").getBlocks(); const ids = allBlocks.map((b) => b.clientId); if (ids.length) { dispatch("core/block-editor").removeBlocks(ids); } dispatch("core/block-editor").insertBlocks(blocks); } else if (selectedBlockClientId) { const selectedIndex = select("core/block-editor").getBlockIndex(selectedBlockClientId); dispatch("core/block-editor").insertBlocks(blocks, selectedIndex + 1); } else { dispatch("core/block-editor").insertBlocks(blocks); } };