v0.10.19: G2 页脚 Footer 一比一复刻(原站 elementor_library post 58)

- 导入脚本 scripts/import-footer-template.ts:从 legacy dump 提取 post 58 的 _elementor_data
  写入 contents(type=template, slug=footer),并按线上 post-58.css/DOM 补齐快照后改动
  (f95e22e Google Preferred Source、405d251 width:auto、2481635 桌面外距)
- Layout/getLayoutData 下发 footerDoc,Footer 改用 ElementorRenderer 直出原模板,
  短代码按 widget id 注入:menu-2 移动端折叠菜单、gtranslate 语言下拉(原生 select.gt_selector)
- Renderer 增强:section html_tag/边框/content_position、column 响应式宽度与
  populated padding/margin(tablet/mobile)、widget 响应式盒模型、icon widget(fixed 返回顶部)、
  icon-list 图标/间距/文字排版与 CSS 变量悬停、image 宽度、divider Elementor DOM、
  text-editor 对齐与 current-date-time 动态年份、shortcode 预渲染上下文
- slim 白名单补齐响应式/图标/动态标签字段;app.css 新增 FA Solid/Regular 字体、
  页脚 scoped 规则、elementor-hidden-* 响应式可见性
- 实测 1436:页脚总高 590.4 与原站全等(列宽/内层列/logo/社交/下拉逐项一致);
  390:56/15/347/337/97/46.4/61 逐项全等;语言切换写 ui_lang cookie;返回顶部回滚 0
- bun test 11 pass;版本 0.10.19
This commit is contained in:
bigcode
2026-11-02 11:20:29 +08:00
parent 686dbe18de
commit a7614e5485
12 changed files with 765 additions and 93 deletions
+124
View File
@@ -0,0 +1,124 @@
/**
* # 脚本 导入页脚 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<Record<string, unknown>[]> {
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<string, unknown>[];
}
/** # 函数 深度查找指定 id 的元素节点 */
function findElement(els: Record<string, unknown>[], id: string): Record<string, unknown> | null {
for (const el of els) {
if (el.id === id) return el;
const children = el.elements as Record<string, unknown>[] | 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<string, unknown> {
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<string, unknown>[]) || [];
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<string, unknown>) || {};
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<string, unknown>) || {};
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}::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}::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();