import path from "path"; import { createClient } from "@sanity/client"; import dotenv from "dotenv"; dotenv.config({ path: path.resolve(".env.local") }); const projectId = process.env.SANITY_STUDIO_PROJECT_ID || "gdvj8e5j"; const dataset = process.env.SANITY_STUDIO_DATASET || "production"; const token = process.env.SANITY_AUTH_TOKEN; const client = createClient({ projectId, dataset, token, apiVersion: "2024-10-31", useCdn: false, }); function preprocessBlocks(blocks) { if (!Array.isArray(blocks)) return blocks; const result = []; let i = 0; while (i < blocks.length) { const block = blocks[i]; const text = block.children ? block.children.map((c) => c.text).join("") : ""; const trimmed = text.trim(); // 0. Skip frontmatter metadata blocks leftover from markdown import if ( i < 12 && /^(title|slug|customPath|pageId|createdTime|lastEditedTime|cover):/i.test( trimmed ) ) { i++; continue; } // 0b. Skip redundant duplicate title H1/H2 at the very beginning of the article if ( i < 5 && (block.style === "h1" || block.style === "h2") && (/vietnam luxury wedding cost/i.test(trimmed) || /ngân sách tiệc cưới cao cấp/i.test(trimmed)) ) { i++; continue; } // 1. Detect Notion Callout pattern (
or > 💡 **Callout:**) if ( text.includes("
") || text.includes("Callout:") || text.startsWith("> 💡") ) { let title = "Vietnam Wedding Guide's Advice"; const contentParts = []; let j = i; let foundEnd = false; while (j < blocks.length && j < i + 15) { const bText = blocks[j].children ? blocks[j].children.map((c) => c.text).join("") : ""; const summaryMatch = bText.match( /\s*\**\s*(.*?)\s*\**\s*<\/summary>/i ); if (summaryMatch) { title = summaryMatch[1].replace(/[*#]/g, "").trim(); } else if ( !bText.includes("
") && !bText.includes("
") && !bText.includes("Callout:") && !bText.startsWith("> 💡") ) { if (bText.trim()) { contentParts.push(bText.trim()); } } if (bText.includes("
")) { foundEnd = true; j++; break; } j++; } if (foundEnd || contentParts.length > 0) { result.push({ _key: block._key || "callout-" + i, _type: "callout", tone: "gold", title, content: contentParts.join("\n\n"), }); i = j; continue; } } // 2. Detect Scenario 1, Scenario 2, Scenario 3 pattern (English & Vietnamese) const isScenarioStart = /Scenario\s*1/i.test(text) || /Kịch bản\s*1/i.test(text) || text.includes("Nếu hai bạn là người trực tiếp") || text.includes("Nếu hai bạn"); if (isScenarioStart) { const scenarios = []; let j = i; let curTitle = ""; let curUpside = ""; let curDownside = ""; while (j < blocks.length && j < i + 25) { const bText = blocks[j].children ? blocks[j].children.map((c) => c.text).join("") : ""; const scenMatch = bText.match( /(?:Scenario\s*(\d+)[:\s]+|Kịch bản\s*(\d+)[:\s]+|→\s*(?:Nếu\s+|Scenario\s*\d+:?\s*))(.*?)(?:\*\*|$)/i ); const isHeader = scenMatch || bText.includes("→ Nếu") || (blocks[j].style === "h4" && (bText.includes("Scenario") || bText.includes("Nếu"))); if (isHeader) { if (curTitle) { scenarios.push({ title: curTitle, upside: curUpside, downside: curDownside, }); curUpside = ""; curDownside = ""; } if (scenMatch && scenMatch[1]) { curTitle = `Scenario ${scenMatch[1]}: ${scenMatch[3] ?.replace(/[*#→_]/g, "") .trim()}`; } else { curTitle = bText.replace(/[*#→_]/g, "").trim(); } } else if ( bText.includes("Upside:") || bText.includes("Reality:") || bText.includes("Lợi ích:") || bText.includes("Thực tế:") ) { curUpside = bText .replace(/.*?(Upside|Reality|Lợi ích|Thực tế):\s*/i, "") .replace(/[*]/g, "") .trim(); } else if ( bText.includes("Challenge:") || bText.includes("Trade-off:") || bText.includes("Rationale:") || bText.includes("Thách thức:") || bText.includes("Đánh đổi:") || bText.includes("Lý do:") ) { curDownside = bText .replace( /.*?(Challenge|Trade-off|Rationale|Thách thức|Đánh đổi|Lý do):\s*/i, "" ) .replace(/[*]/g, "") .trim(); } else if ( scenarios.length >= 2 && (blocks[j].style === "h2" || blocks[j].style === "h3" || (!bText.includes("Scenario") && !bText.includes("Nếu") && !bText.includes("Upside") && !bText.includes("Lợi ích") && !bText.includes("Trade-off") && !bText.includes("Thách thức") && !bText.includes("Challenge") && !bText.includes("Reality") && !bText.includes("Thực tế") && !bText.includes("Rationale") && !bText.includes("Lý do"))) ) { break; } j++; } if (curTitle) { scenarios.push({ title: curTitle, upside: curUpside, downside: curDownside, }); } if (scenarios.length >= 2) { result.push({ _key: block._key || "scenarios-" + i, _type: "scenariosGrid", items: scenarios, }); i = j; continue; } } // 3. Skip standalone
or
leftover tags if ( trimmed === "
" || trimmed === "
" || trimmed === "> 💡 **Callout:**" ) { i++; continue; } // 4. Standalone quotes starting with `>` or `> ***` if (trimmed.startsWith(">")) { const cleanQuote = trimmed .replace(/^>\s*/, "") .replace(/^\*+|\*+$/g, "") .trim(); result.push({ _key: block._key || "quote-" + i, _type: "block", style: "blockquote", markDefs: [], children: [ { _key: "span-" + i, _type: "span", marks: [], text: cleanQuote, }, ], }); i++; continue; } result.push(block); i++; } return result; } const posts = await client.fetch('*[_type == "post"]{ _id, slug, body }'); console.log(`Checking ${posts.length} posts for block cleanup...`); for (const post of posts) { if (!post.body || !Array.isArray(post.body)) continue; const processed = preprocessBlocks(post.body); if (processed.length !== post.body.length) { console.log( `Post ${post.slug?.current || post._id}: cleaned from ${ post.body.length } to ${processed.length} blocks` ); await client.patch(post._id).set({ body: processed }).commit(); } } console.log("Cleanup complete!");