/** * # 脚本 导入页脚 Elementor 模板(legacy elementor_library post 58) * 背景:WordPress 导入管线未迁移 elementor_library 类型(仅 page/post/landing), * 页脚模板(post 58,含 46d7afa / d834160 两节及其内嵌 28c7f0b8 / a428bb4)因此缺失。 * 数据源:legacy/dusuniot-db-20261029.sql 中 `(meta_id,58,'_elementor_data','…')` 行。 * 补丁:线上页脚另有 f95e22e(Google Preferred Source 图,dump 快照之后新增), * 依线上 DOM(/tmp/orig-footer.html 取证:class elementor-widget__width-inherit、 * img 528×64、链接 google.com/preferences/source?q=dusuniot.com)补入 2ccb47d 列末位。 * 结果写入 contents(type=template, slug=footer, meta.elementor),供 Layout 渲染。 * 运行:bun scripts/import-footer-template.ts */ import { SQL } from "bun"; const DUMP = "/home/bigcode/data/dev/sites/dusuniot/legacy/dusuniot-db-20261029.sql"; const db = new SQL("postgres://dusuniot:dusuniot@127.0.0.1:25432/dusuniot"); /** # 函数 从 dump 提取 post 58 的 _elementor_data 并反转义为 JSON * 返回值 Elementor 元素数组 */ async function extractTemplate(): Promise[]> { const text = await Bun.file(DUMP).text(); const m = text.match(/\(\d+,58,'_elementor_data','((?:[^'\\]|\\.)*)'\)/); if (!m) throw new Error("dump 中未找到 post 58 的 _elementor_data"); const raw = m[1]!; // SQL dump 反斜杠转义:\" → "、\' → '、\\ → \ const json = raw.replace(/\\'/g, "'").replace(/\\"/g, '"').replace(/\\\\/g, "\\"); const data = JSON.parse(json); if (!Array.isArray(data)) throw new Error("_elementor_data 不是数组"); return data as Record[]; } /** # 函数 深度查找指定 id 的元素节点 */ function findElement(els: Record[], id: string): Record | null { for (const el of els) { if (el.id === id) return el; const children = el.elements as Record[] | undefined; if (children?.length) { const hit = findElement(children, id); if (hit) return hit; } } return null; } /** # 函数 线上快照新增的 f95e22e(Google Preferred Source 图) * 线上 post-58.css 取证:.elementor-element-f95e22e img{width:300px}、 * > .elementor-widget-container{margin:10px 0 0};dump 快照之后新增,故在此补全。 */ function preferredSourceWidget(): Record { return { id: "f95e22e", elType: "widget", settings: { image: { url: "http://www.dusuniot.com/wp-content/uploads/2022/06/google-preffered-source.png", id: 161805, alt: "google prefferd source", source: "library", size: "", }, image_size: "full", width: { unit: "px", size: 300, sizes: [] }, align: "left", _element_width: "inherit", _margin: { unit: "px", top: "10", right: "0", bottom: "0", left: "0", isLinked: false }, link: { url: "https://www.google.com/preferences/source?q=dusuniot.com", is_external: "on", nofollow: "on", custom_attributes: "", }, }, elements: [], widgetType: "image", }; } const elements = await extractTemplate(); // 补丁:2ccb47d 列末尾追加 f95e22e(幂等:已存在则跳过) const col = findElement(elements, "2ccb47d"); if (!col) throw new Error("模板缺少 2ccb47d 列"); const colChildren = (col.elements as Record[]) || []; const psIdx = colChildren.findIndex((el) => el.id === "f95e22e"); if (psIdx >= 0) colChildren[psIdx] = preferredSourceWidget(); else colChildren.push(preferredSourceWidget()); col.elements = colChildren; // 补丁:社交图标行 405d251 宽度模式(线上 class=elementor-widget__width-auto、 // 生成 CSS width:auto;dump 快照为 inherit,实测线上为 auto → 与 gtranslate 同排) const social = findElement(elements, "405d251"); if (social) { const st = (social.settings as Record) || {}; st._element_width = "auto"; social.settings = st; } // 补丁:gtranslate 短代码控件 2481635 桌面外距(线上 post-58.css:margin -10px 0 0 20px; // dump 只有 _margin_mobile,桌面值来自快照之后的编辑) const gt = findElement(elements, "2481635"); if (gt) { const st = (gt.settings as Record) || {}; st._margin = { unit: "px", top: "-10", right: "0", bottom: "0", left: "20", isLinked: false }; gt.settings = st; } const meta = JSON.stringify({ elementor: elements }); // 写入 contents:type=template / slug=footer;已存在则更新 const existing = await db`SELECT id FROM contents WHERE slug='footer' AND type='template'`; if (existing.length) { await db`UPDATE contents SET meta=${meta}::text::jsonb, status='published', updated_at=now() WHERE id=${existing[0]!.id}`; console.log("已更新 footer 模板,contents.id =", existing[0]!.id); } else { const inserted = await db` INSERT INTO contents (type, slug, title, status, meta, legacy_id, published_at) VALUES ('template', 'footer', 'Footer', 'published', ${meta}::text::jsonb, 58, now()) RETURNING id`; console.log("已插入 footer 模板,contents.id =", inserted[0]!.id); } // 验证:节数量与顶层 id const top = elements.map((el) => el.id).join(", "); console.log("顶层节:", top); await db.end();