[开发]Xenforo 主题对齐与改造报告(2026-09-28)

👑Lv.11 元老 🌞 骨干会员
2026-10-26 01:59:06

本文由 FlintHub 开发文档归档发布。原始文档日期见标题前缀,来源:WWW/docs/Xenforo主题对齐与改造报告_2026-09-28.md。

Xenforo 主题对齐与改造报告(2026-09-28)

范围:assets/themes/Xenforo/ 全部改动,共 7 轮(功能对齐 → 视觉改造 → 回归修复 → 细节打磨)。

当前 sha256:xenforo.css = b0353dd9…82ba8bca、thread/show.php = 43c575b8…513cab45


0. 概览

项数据
起因assets/themes/Xenforo 是独立覆盖副本(不是继承),核心后来加的收敛层/组件它全错过了 → 功能脱节
改动的模板30 个里 12 个(+149 行 / −110 行)
改动的样式xenforo.css(第 2~6 轮主战场)
新增文件_components/attachment_card.php(主题版附件卡)
刻意未改判定为「设计差异」的 4 项(见 §3)

一句话:前 4 轮解决「功能脱节」(云存储失效、附件/灯箱缺组件、htmx 契约不成立、hx-select 继承导致点赞/楼中楼全废),后 3 轮解决「视觉与落位」(用户栏、等级块、楼中楼位置与底色)。


1. 先讲机制(决定改法的三条)

  1. TemplateCompiler::resolveTemplate() 对 $this->include() 同样生效 → 主题内放同名 _components/xxx.php 即可「复用核心调用 + 保持主题视觉」,不必改调用点。本次即用此法做 xen 版 attachment_card。
  2. 覆盖层目录名白名单 ^[A-Za-z0-9_-]+$;后台请求(/admin)不走覆盖层。
  3. 主题找不到的模板自动回落 app/Views → 无视觉差异的组件(如 image_lightbox)别另造,直接 include 走核心回落。

2. 按方面说明

A. 功能对齐(第 1 轮)

A1. 上传 URL 收敛 —— 本次最致命的一项

  • 25 处 \UPLOAD_URL . $x → $this->uploadUrl($x)(涉及 11 个文件)。
  • 根因:Upload::url() 内含 upload_url 钩子(云存储/CDN 改写),直拼会绕过钩子 → bitiful_storage(缤纷云存储)在 Xenforo 主题下静默失效;同时丢掉 http(s) 直通 / null 归一 / 反斜杠归一。
  • 另有 2 处 style="…url('<?php echo $this->e(…) ?>')" → cssValue($this->uploadUrl(…))(CSS 上下文不能用 HTML 转义)。

A2. 附件组件化 + 灯箱

  • 新建 assets/themes/Xenforo/_components/attachment_card.php(xen 风格):Upload::isImageAttachment() 判图 + ?s=thumb 缩略图 + data-fullurl + data-action="view-image" + 下载按钮/次数 + 图片文件分组。
  • thread/show.php:188 / 193 / 317 / 322 改组件调用;:438 灯箱改 include('_components/image_lightbox')。

A3. htmx 容器 / 空态 / 钩子

  • 补 #replies htmx 容器(thread/show.php:241)、thread.no_replies 空态、thread.reply_all 标题。
  • 补 thread_show_top 插件钩子(:57)—— 「跳转后一次性提示」的唯一正解(核心没有 flash 机制)。
  • 引用按钮门禁改 $hasReplyForm && $threadQuotable(主帖)/ $post['_quotable'](楼层);data-quote-content 由 content 改 _content_raw。

A4. 其它功能补齐

  • tags/index.php:补分页(原本超一页看不到)+ tagMax 改全站基准(否则同标签跨页字号不一致)。
  • blog/edit.php:补封面移除(remove_cover + blog.cover_remove)。
  • layouts/main.php:补 nav.language(语言按钮 title)+ 中栏接入 _components/mobile_sidebar。

B. 帖子详情页「布局 + 配色」改造(第 2 轮,14 处,仅 xenforo.css)

目标:对齐 XenForo 2 默认风格(白卡 + 浅灰用户栏 + 归属行无底色)。

  • 配色:--mn-primary #1f4e79 → #185886(XenForo paletteColor1)、--mn-bg-body #eef0f5 → #f5f5f5、--mn-text-muted #8a94a6 → #8c8c8c、新增 --xen-user-bg。
  • 布局:用户栏白底 → 浅灰、去 border-right、加指向三角 ::after;归属行去底色去下边框;date order:1 + margin-right:auto 靠左,anchor order:2,actions order:3。
  • ⚠️ 关键坑(头部里的插件注入元素):post_operation_after(楼中楼按钮、屏蔽按钮)输出的是无 order、无 auto margin 的直接子元素 → 只给 anchor 加 margin-left:auto 不够,插件元素会留在最左。正解 = **.xen-message-header > * { order:2 } 兜底归组**。
  • ⚠️ 且只能有一个 auto margin:anchor 与 actions 同时带会平分剩余空间,把日期挤到中间。
  • ⚠️ --mn-bg-nav 仍是 #1f4e79,未在方案内,待定。

C. 回复贴右侧对齐核心 + 楼中楼按钮归位(第 3 轮)

用户诉求:用户栏 120 → 200px;回复贴右侧改成跟默认模板一样;底栏靠底;楼中楼按钮挪进底栏;签名档改 UI。

★★★ 核心根因:floor_reply 的 DOM 契约在主题里不成立

plugins/floor_reply/assets/script.js::init() 依赖核心类名 + 核心嵌套:

var bar   = anchor.closest('.thread-post-user-bar');
var floor = bar.parentElement;                    // ← 必须正好是楼层容器
var voteActions = floor.querySelector('.thread-vote-actions');
quote.insertAdjacentElement('afterend', btn);                              // 主路径
voteActions.insertBefore(btn, voteActions.querySelector('.mn-ml-auto'));   // 兜底
floor.appendChild(anchor);                                                 // 锚点 → 楼层末尾

主题用 .xen-message-* → closest() 返 null → if (!floor) return; 整楼早退(按钮不挪、锚点不挪、自动展开失效)。

修法(主题侧补契约,不动插件)

主题元素追加的标记类作用
.xen-message-innerthread-post-user-bar令 parentElement = article.xen-message(楼层容器)
.xen-message-footerthread-vote-actions插件插入目标
.xen-message-reportmn-ml-auto兜底分支的插入锚点

  • ⚠️⚠️ 最容易漏:.mn-ml-auto 必须是 .thread-vote-actions 的直接子元素,否则 insertBefore 抛 NotFoundError(DOMException)→ 整个 init() 中断,连后续楼层一起废。
  • 底栏「靠不到底」真因:回复贴 .xen-message-footer 嵌在 .xen-message-body 内部,而 flex:1 撑高的是 body 本身 → footer 浮在中间。提到 .xen-message-content 直接子级(与主帖一致)。
  • ⚠️ 补了核心类就要提权抵消 modern.css 的同名规则:.thread-post-user-bar{padding:12px 20px} / .thread-vote-actions{margin-top:10px}。

D. 三个回归修复(第 4 轮)

症状:① 点赞后按钮不见 ② 楼中楼点不开 ③ 回复贴之间没了间隔。

①+② 同一个真因:#replies 的 hx-select="#replies" 被后代继承

htmx 里 hx-select 是继承属性 → 楼层内所有请求都去响应体里找 #replies:点赞 hx-post="/api/vote"(响应是 <span class="vote-buttons">…</span>)、插件 htmx.ajax({source: btn}) → 都找不到。htmx 的 no-match 分支 return [be] 返回空占位节点 → swap 把目标整个换掉 → 按钮/锚点从 DOM 消失(HTTP 200、控制台无报错)。

→ 正解 = 抄核心,不是自己发明:核心 app/Views/thread/show.php:269 每个楼层容器上就有 hx-disinherit="*",主题抄结构时漏了这一层。落到 thread/show.php:257。

  • ⚠️ hx-disinherit 必须挂在触发元素的祖先上(htmx 走查跳过元素自身)。
  • ⚠️ 别只挂 .xen-message-footer:只覆盖主分支,插件兜底分支会漏。

③ 真因:换了父容器,丢了父容器的 gap

第 1 轮把回复贴从 .xen-messageList(gap:10px)挪进新容器 #replies.xen-replyList(原本只有 margin-top)→ 楼层变普通块相邻。→ 补 display:flex; flex-direction:column; gap:10px(xenforo.css:456)。

E. 楼中楼只落右侧 + 等级块改 2 行(第 5 轮)

  • 楼中楼:插件把锚点 append 到楼层容器,锚点是块级元素 → 从卡片左缘起排、横跨整卡 → 压在 200px 用户栏下面。→ 主题推到内容列内缘(xenforo.css:404-405,margin-left: calc(var(--xen-user-w) + 16px) = 216px)。
  • ⚠️ 必须提权到 (0,2,0):插件那条是 (0,1,0) 且用 margin 简写,同权重会连 margin-left 一起抢走。
  • 顺手把用户栏宽度抽成 --xen-user-w(:86),以后改宽度一处生效。
  • 等级块:「级别徽章 + 信任等级徽章」原本挤在同一行 → .xen-message-badges 改 flex-direction: column(:377),各占一行。该块主帖(show.php:122)+ 楼层(:278)共用,一处改动两处生效。
  • 窄屏(≤900px,用户栏折成满宽顶栏)还原:margin-left: 16px、徽章回 row。

F. 楼中楼间距 + 用户栏底色贯通(第 6 轮)

  • 间距:插件给的是 margin-top: calc(-1 * var(--mn-pad-flow, 20px) + 12px / 8px) = -8px / -12px —— 这是为核心 .mn-p-20 的 20px 底部内边距做的抵消。主题的底栏只有 10px 内边距 → 负值把楼中楼往上压进底栏按钮行约 2px。

→ 主题接管 margin-top: 6px(净间距 = 10 + 6 = 16px,与 .xen-signature 同节奏)。

  • 底色:.xen-message-inner { align-items: stretch } 只把用户栏撑到内容列高度;楼中楼块在 inner 之外 → 那一横条里左侧 200px 露的是卡片白底,与上方 #f5f5f5 断层。→ 给「含楼中楼的卡片」铺左列渐变(xenforo.css:412-416)。
  • ⚠️ 必须用 :has() 精确限定,不能写成「所有 .xen-message」:博客评论同样是 .xen-message(blog/detail.php:76),但桌面端用户栏只有 120px,渐变会铺错宽度。
  • ✅ 已确认博客评论没有楼中楼(走 blog_comment_operation_after,不是 post_operation_after)→ :has() 天然不命中,零误伤。
  • :has() 不是新依赖(modern-spectrum.css:570 已在用);不支持时整条规则被丢弃 → 只是没这条底色,布局不受影响(优雅降级)。
  • ⚠️ 窄屏必须在媒体查询里 background-image: none,否则左半边整条变灰。

3. 刻意未改(判定为「设计差异」)

两栏 vs 核心三栏、无 left_sidebar(分类在移动抽屉)、forum 浮动栏(面包屑已可返回)、forum.pinned 用 common.pinned 替代。

理由:用户要求「界面不要有太大改动」,为了「对齐 i18n 键」去改文案 = 无意义的界面变动。


4. 验证方式(本次最有价值的部分)

  1. 主题冒烟必须带 ?preview_theme=Xenforo:匿名访客走 settings.default_theme,不带参数会渲染出别的主题却返回 200 → 「绿了但根本没测到目标主题」的静默假通过。
  2. jsdom 跑真实插件脚本:runScripts:'outside-only' + 强制 readyState='complete'(否则 IIFE 挂在 DOMContentLoaded 上永不执行)+ window.eval(真实 script.js)。插件 DOM 契约类问题静态扫描必漏(PHP 语法对、页面 200,脚本运行时早退或抛异常)。
  3. 喂真实渲染页(curl '…?preview_theme=Xenforo')+ 真实 assets/lib/fhajax.js;XHR 桩按 URL 精确分派,不认识的一律回空注释。
  4. A/B 反证:把修复剥离再跑一遍对比 PASS 数(第 4 轮实测 32/32 vs 16/32),否则无法证明修的是真因。
  5. 两条分支都要测:游客(无引用按钮 → 兜底分支)、登录(有引用按钮 → 主分支)。登录态用临时 sessions 行,测完按 id 精确 DELETE。
  6. CSS 用花括号深度解析(不是 count('{')==count('}'))。
  7. 每轮收尾核对:protected/error.log 83192 字节无新增、临时会话行数还原、临时产物按精确文件名清理。
  8. ⚠️ jsdom 的 getComputedStyle 不能当级联证据:解析不了 var()/calc()(含 calc() 的 margin 简写会整条丢掉);普通 px 值可信。

5. 遗留待定

项现状
.xen-comment .xen-message-user(博客评论)仍固定 120px,未跟随 200px
--mn-bg-nav仍 #1f4e79,是否改 #185886 待定
.fr-box 下外边距仍是插件给的 4px(灰盒几乎贴卡片下边框)
.xen-message--thread 主帖未挂核心标记类(主帖无 post_operation_after,插件用不到)


6. 备份与回滚

备份目录内容改动前 sha256(完整)
.workbuddy-ai/tmp/xen_bak_20260928_121134/第 1 轮前:30 个模板 + sha256_before.txt见目录内 sha256_before.txt(逐文件基线)
.workbuddy-ai/tmp/xen_css_bak_20260928_123228/第 2 轮前:xenforo.cssa3b9007d8540d6e152435046d28e1b1db25066a61f388bb723d69e1d32059bed
.workbuddy-ai/tmp/xen_bak2_20260928_124438/第 3 轮前:thread/show.php + xenforo.css51d0b79e15db5da610431138b03069f155d7df3c721440536e62955f4f26b28c / 36a4615e778359505c4290e4e282d393682535da214266029f3d327f9467d93e
.workbuddy-ai/tmp/xen_bak4_20260928_162649/第 5 轮前:xenforo.css2e893564e397fcdcd1f22bca1676d338b38cdf43bcf433ee9655d30fde11b710
.workbuddy-ai/tmp/xen_bak5_20260928_163753/第 6 轮前:xenforo.css24b6a3abcc65a9db251807b1a9ab432436b34aaac6a3840f89eee745e0030562

当前值(改动后):xenforo.css = b0353dd93d397e6ec2a1a9405bd6ee6143de7e724c33edbedb251aac82ba8bca;thread/show.php = 43c575b81ad1db50107d28d9547fad7e0faa0698fb98c80165991c2e513cab45

回滚方式:按目录覆盖对应文件(模板按相对路径回填到 assets/themes/Xenforo/)。

⚠️ 若改动已进发布包,config.php 不在包内,无需同步;主题文件属纯代码,直接覆盖即可。

轻量级、高性能、零 MySQL 依赖的PHP社区系统。
| 瀏覽 6 次 | 回覆 0 次

全部回覆 (0)

暫無回覆

請 登入