- image-box 支持 title_typography_*/description_typography_*(36px/700/#ED6C00 标题、 18px 描述)、image_space(图标下距 6px)、text_align、title_color 落样式; 对应键进 slim 白名单 - 六列图标宽 30% 仅存 post-18 生成 CSS(DB 无 image_size),scoped 补规则 (efe1577/d48a588/46684e1/17330e2/a7cd0a1/e6b1413) - 双端节内相对几何/计算样式 JSON 逐字节一致:sec h 208→235 与原站全等, 列边框 #E0E5F9(globals ee1917e)、末列四边;H0-H13.5 全页高度回归零差异
230 lines
7.5 KiB
TypeScript
230 lines
7.5 KiB
TypeScript
/**
|
||
* # 模块 Elementor 文档瘦身
|
||
* WordPress 导入的 Elementor JSON 包含大量桌面/平板/移动端响应式字段与空值,
|
||
* 整份文档会随 TanStack Router 脱水序列化进 SSR HTML(首页曾达 391KB),
|
||
* 严重影响 SEO 与首字节体积。本模块只保留渲染器实际读取的字段。
|
||
*/
|
||
|
||
/** # 成员变量 渲染器(Renderer.tsx)实际读取的 settings 字段白名单 **/
|
||
const SETTING_KEYS = new Set([
|
||
// 通用布局/背景/边框
|
||
"__globals__",
|
||
"align",
|
||
"background_background",
|
||
"background_color",
|
||
"background_color_b",
|
||
"background_color_stop_b",
|
||
"background_image",
|
||
// section 级背景定位/平铺(H11 底图 bottom center + no-repeat)
|
||
"background_position",
|
||
"background_repeat",
|
||
// 背景尺寸(initial → 100% auto,H13 列背景楼体图)
|
||
"background_size",
|
||
// icon-list 视图(inline = 横排链接条,H13)
|
||
"view",
|
||
"border_border",
|
||
"border_color",
|
||
"border_radius",
|
||
"border_width",
|
||
"content_width",
|
||
"custom_height",
|
||
"padding",
|
||
"layout",
|
||
"height",
|
||
"size",
|
||
"space",
|
||
"color",
|
||
"text_color",
|
||
"title_color",
|
||
"button_text_color",
|
||
"button_background_hover_color",
|
||
"hover_color",
|
||
"text_padding",
|
||
// image-box 系列:图定位(position 默认 top)与标题标签(title_size 默认 h3)
|
||
"position",
|
||
"title_size",
|
||
// image-box 行布局(H11):图宽 image_size%、标题底距、内容垂直对齐
|
||
"image_size",
|
||
"title_bottom_space",
|
||
"content_vertical_alignment",
|
||
// image-box 图标间距/文本对齐/标题与描述排版(H13.5 counter 条)
|
||
"image_space",
|
||
"text_align",
|
||
"title_typography_typography",
|
||
"title_typography_font_size",
|
||
"title_typography_font_size_mobile",
|
||
"title_typography_font_size_tablet",
|
||
"title_typography_font_weight",
|
||
"description_typography_typography",
|
||
"description_typography_font_size",
|
||
"description_typography_font_size_mobile",
|
||
"description_typography_font_size_tablet",
|
||
"gradient_angle",
|
||
"color_a",
|
||
"color_b",
|
||
"header_size",
|
||
// 组件自带盒间距与列 margin(heading/text-editor 的 _padding;内层列 margin)
|
||
"_padding",
|
||
"margin",
|
||
// Typography 组合字段(typography_typography=custom 时逐键采用)
|
||
"typography_typography",
|
||
"typography_font_family",
|
||
"typography_font_size",
|
||
"typography_font_size_tablet",
|
||
"typography_font_size_mobile",
|
||
"typography_font_weight",
|
||
"typography_line_height",
|
||
"typography_letter_spacing",
|
||
"typography_word_spacing",
|
||
"typography_text_transform",
|
||
"typography_font_style",
|
||
"typography_text_decoration",
|
||
// 列结构
|
||
"_column_size",
|
||
"_inline_size",
|
||
// section 间隙档位(决定子列默认 populated padding:no 0 / default 10 / …)
|
||
"gap",
|
||
// 各 widget 数据
|
||
"heading_title",
|
||
"title",
|
||
"title_text",
|
||
"description",
|
||
"description_text",
|
||
"selected_icon",
|
||
"url",
|
||
"width",
|
||
"text",
|
||
"editor",
|
||
"html",
|
||
"source_code",
|
||
"image",
|
||
"link",
|
||
"prefix",
|
||
"suffix",
|
||
"ending_number",
|
||
"icon_list",
|
||
"social_icon_list",
|
||
"slides",
|
||
"slides_height",
|
||
"tabs",
|
||
"tabs_align_horizontal",
|
||
"tab_active_color",
|
||
"video_url",
|
||
"youtube_url",
|
||
"map",
|
||
"posts_per_page",
|
||
"cards_posts_per_page",
|
||
"post_type",
|
||
"eael_ticker_tag_text",
|
||
// EAEL Infobox / flip-box / 高级标签页 / 数据表 / 评价等(Renderer 对应组件读取)
|
||
"custom_css",
|
||
"_css_classes",
|
||
"eael_infobox_title",
|
||
"eael_infobox_text",
|
||
"eael_infobox_title_tag",
|
||
"eael_infobox_image",
|
||
"eael_infobox_img_type",
|
||
"eael_infobox_img_margin",
|
||
"eael_infobox_title_margin",
|
||
"eael_infobox_title_margin_mobile",
|
||
"eael_infobox_title_typography_typography",
|
||
"eael_infobox_title_typography_font_size",
|
||
"hover_animation",
|
||
// widget 高级盒模型(如 eael-info-box 边框卡:_border_* + _padding/_margin)
|
||
"_margin",
|
||
// widget 宽度模式(auto → 按内容宽横排,如 H7 图标行)
|
||
"_element_width",
|
||
// widget 容器背景(如 H4 标题下的橙线图)
|
||
"_background_image",
|
||
"_background_position",
|
||
"_background_repeat",
|
||
// section 背景遮罩(.elementor-background-overlay,H11 电路板图+渐变)
|
||
"background_overlay_background",
|
||
"background_overlay_color",
|
||
"background_overlay_color_b",
|
||
"background_overlay_opacity",
|
||
"background_overlay_gradient_angle",
|
||
"_border_border",
|
||
"_border_color",
|
||
"_border_width",
|
||
"_border_radius",
|
||
"title_text_a",
|
||
"description_text_a",
|
||
"button_text",
|
||
"background_a_image",
|
||
"eael_adv_tabs_tab",
|
||
"eael_data_table_header_cols_data",
|
||
"eael_data_table_content_rows",
|
||
"eael_testimonial_name",
|
||
"eael_testimonial_company_title",
|
||
"eael_testimonial_description",
|
||
]);
|
||
|
||
/** # 函数 收窄对象型 settings 值(渲染器只读 url/alt/target 等少数字段)
|
||
* 参数 key settings 键名;val 原始值
|
||
* 返回值 收窄后的值;非命中键原样返回
|
||
*/
|
||
function slimSettingValue(key: string, val: unknown): unknown {
|
||
if (!val || typeof val !== "object" || Array.isArray(val)) return val;
|
||
const v = val as Record<string, unknown>;
|
||
if (key === "image" || key === "background_image" || key === "background_a_image" || key === "eael_infobox_image") {
|
||
// 渲染器只读 url / alt
|
||
return { url: v.url, alt: v.alt };
|
||
}
|
||
if (key === "link") {
|
||
// 渲染器只读 url / target / is_external(is_external=on → target=_blank,与原站 link_attributes 一致)
|
||
return { url: v.url, target: v.target, is_external: v.is_external };
|
||
}
|
||
return val;
|
||
}
|
||
|
||
/** # 函数 递归瘦身单个 Elementor 元素
|
||
* 参数 el 原始元素(section/column/widget 通用结构)
|
||
* 返回值 仅含 id/elType/widgetType/isInner/白名单 settings/elements 的新对象
|
||
*/
|
||
function slimElement(el: unknown): unknown {
|
||
if (!el || typeof el !== "object") return el;
|
||
const src = el as Record<string, unknown>;
|
||
const out: Record<string, unknown> = {};
|
||
if (src.id != null) out.id = src.id;
|
||
if (src.elType != null) out.elType = src.elType;
|
||
if (src.widgetType != null) out.widgetType = src.widgetType;
|
||
if (src.isInner) out.isInner = true;
|
||
const st = src.settings;
|
||
if (st && typeof st === "object" && !Array.isArray(st)) {
|
||
const slim: Record<string, unknown> = {};
|
||
for (const key of Object.keys(st as Record<string, unknown>)) {
|
||
if (SETTING_KEYS.has(key)) slim[key] = slimSettingValue(key, (st as Record<string, unknown>)[key]);
|
||
}
|
||
if (Object.keys(slim).length > 0) out.settings = slim;
|
||
}
|
||
if (Array.isArray(src.elements) && src.elements.length > 0) {
|
||
out.elements = src.elements.map(slimElement);
|
||
}
|
||
return out;
|
||
}
|
||
|
||
/** # 函数 瘦身整份 Elementor 文档(数组或 {elements:[]} 包装均支持)
|
||
* 参数 doc 原始文档(来自 meta.elementor)
|
||
* 返回值 瘦身后的文档;非对象/数组输入原样返回
|
||
*/
|
||
export function slimElementorDoc(doc: unknown): unknown {
|
||
if (Array.isArray(doc)) return doc.map(slimElement);
|
||
if (doc && typeof doc === "object" && Array.isArray((doc as Record<string, unknown>).elements)) {
|
||
const wrapped = doc as Record<string, unknown>;
|
||
return { elements: (wrapped.elements as unknown[]).map(slimElement) };
|
||
}
|
||
return doc;
|
||
}
|
||
|
||
/** # 函数 瘦身内容 meta:替换 meta.elementor 为瘦身版(原 meta 其余字段保留)
|
||
* 参数 meta 原始 meta JSON(可能为 null)
|
||
* 返回值 elementor 已瘦身的 meta;无 elementor 时原样返回
|
||
*/
|
||
export function slimMetaElementor<T>(meta: T): T {
|
||
if (!meta || typeof meta !== "object") return meta;
|
||
const m = meta as Record<string, unknown>;
|
||
if (!m.elementor) return meta;
|
||
return { ...m, elementor: slimElementorDoc(m.elementor) } as T;
|
||
}
|