3.2.31 目录「正文内显示」与「浮动显示」拆成两个独立开关(#1)

原来「目录位置」是一个三选一下拉:正文中间 / 页面左侧 / 页面右侧,
等于把「放进正文」和「浮在页边」当成互斥的一件事 —— 想同时在正文
开头放一份、又在边上浮一份,做不到。

改成两个独立勾选,各自带子选项:

  · 在正文中显示 → 正文中的对齐(靠左 / 靠右)+ 自动插入位置
  · 浮动显示     → 浮动方向(页面左侧 / 页面右侧)

两个都勾就同时出现两份,只勾一个就只出现一种。四种组合都在后台
联动里逐个验证过(探针 70/0)。

新增能力「正文内靠左 / 靠右」是本单的主要新需求。实现上两个要点:

  1. 必须给正文目录一个宽度上限(max-width: min(100%, 28em))。
     块级盒子默认撑满正文栏,此时 margin:auto 没有剩余空间可分配,
     「靠左/靠右」会是一个属性正确、画面纹丝不动的空操作。
  2. 用 data-align 属性把方向带下去,CSS 只需两条规则,
     不随方向数量膨胀。属性在 yang_toc_slot_html() 里输出。

语义收窄:手写 [TOC] 以前会让整份自动处理全停(连带 rail=false),
现在只手管「正文那一份」—— 手写位置优先,浮动仍由 float 开关独立
决定。这样「精确定位正文位置」和「全站边上都有目录」可以并存。

顺手修掉一处默认值错配:checkbox 用 !empty() 判断会把「键不存在」
和「键明确为空」混成一件事,导致新装站点后台框显示勾上、页面却
不出目录。改用 array_key_exists() 区分的三态判断。

老配置不迁移:3.2.30 的 toc_place 直接作废,不做兼容。
默认 inline=1 / float=0,升级后页面与升级前一致。

重构:把内容过滤器拆成 yang_toc_apply()(纯执行层)+ 运行时壳,
使设置组合能在单进程单测里逐个喂进去(PHP 无法重置函数 static)。

验证
  逻辑 101/0 · 逻辑反向 15/15
  旧回归 53/0
  静态契约 58/0 · 反向 11/11
  后台几何+交互 70/0 · 反向 11/11
  侧栏几何 73/0 · 反向 8/8
  正文对齐几何 23/0 · 反向 5/5(含阴性对照)
  包内容契约 59/0 · 反向 9/9

正文对齐的几何探针量到真实的 Δleft = 348px(靠右比靠左),
而不是只断言 data-align 字符串存在。
This commit is contained in:
2026-10-25 13:37:26 +08:00
parent 6f51d50a58
commit 4a2d62b1aa
5 changed files with 225 additions and 62 deletions
+47 -20
View File
@@ -397,15 +397,55 @@ function kratos_options()
);
$options[] = array(
'name' => __('目录位置', 'kratos'),
'desc' => __('<b>正文中间</b>:目录是正文的一部分,按下面的「自动插入位置」插进去。<br><b>页面左侧 / 右侧</b>:目录变成贴在页面边缘的浮动面板,点边上的小标签滑出,正文里不放任何东西(「自动插入位置」对它无效)。窄屏(手机)上也是从边缘滑出的抽屉,不会挤占正文。', 'kratos'),
'id' => 'toc_place',
'std' => 'center',
'name' => __('在正文中显示', 'kratos'),
'desc' => __('勾选后,在文章正文里插一份目录。<br>它和下面的「浮动显示」<b>互不影响</b>:两个都勾就同时出现两份(正文一份、边上浮一份),只勾一个就只出现一种。', 'kratos'),
'id' => 'toc_inline',
'std' => '1',
'type' => 'checkbox',
);
$options[] = array(
'name' => __('正文中的对齐', 'kratos'),
'desc' => __('正文里那份目录在<b>文章内容区域内</b>靠左还是靠右。这里不是「浮到页面边上」—— 目录仍然在正文流里,上下文的文字会绕开它,只是整体贴向一侧。<br>只在勾选「在正文中显示」时才有意义。', 'kratos'),
'id' => 'toc_inline_align',
'std' => 'left',
'type' => 'select',
'options' => array(
'center' => __('正文中间', 'kratos'),
'left' => __('页面左侧(浮动)', 'kratos'),
'right' => __('页面右侧(浮动)', 'kratos'),
'left' => __('靠左', 'kratos'),
'right' => __('靠右', 'kratos'),
),
);
$options[] = array(
'name' => __('自动插入位置', 'kratos'),
'desc' => __('只在「添加方式 = 自动」且勾选了「在正文中显示」时起作用。<br><b>第一个标题之前</b>取正文里最先出现的 h1 或 h2 —— 文章大标题在正文之外,不会被算进去;一个 h1/h2 都没有时退回插在文章开头。', 'kratos'),
'id' => 'toc_anchor',
'std' => 'first',
'type' => 'select',
'options' => array(
'start' => __('文章开头', 'kratos'),
'first' => __('第一个标题之前(H1 / H2)', 'kratos'),
'end' => __('文章结尾', 'kratos'),
),
);
$options[] = array(
'name' => __('浮动显示', 'kratos'),
'desc' => __('勾选后,目录变成贴在页面边缘的浮动面板,点边上的小标签滑出,<b>不占正文位置</b>。窄屏(手机)上是从边缘滑出的抽屉,不会挤占正文。<br>与「在正文中显示」互相独立,可以同时勾选。', 'kratos'),
'id' => 'toc_float',
'std' => '',
'type' => 'checkbox',
);
$options[] = array(
'name' => __('浮动方向', 'kratos'),
'desc' => __('浮动面板贴页面左边还是右边。只在勾选「浮动显示」时才有意义。', 'kratos'),
'id' => 'toc_float_side',
'std' => 'left',
'type' => 'select',
'options' => array(
'left' => __('页面左侧', 'kratos'),
'right' => __('页面右侧', 'kratos'),
),
);
@@ -421,19 +461,6 @@ function kratos_options()
),
);
$options[] = array(
'name' => __('自动插入位置', 'kratos'),
'desc' => __('只在「添加方式 = 自动」且「目录位置 = 正文中间」时起作用。<br><b>第一个标题之前</b>取正文里最先出现的 h1 或 h2 —— 文章大标题在正文之外,不会被算进去;一个 h1/h2 都没有时退回插在文章开头。', 'kratos'),
'id' => 'toc_anchor',
'std' => 'first',
'type' => 'select',
'options' => array(
'start' => __('文章开头', 'kratos'),
'first' => __('第一个标题之前(H1 / H2)', 'kratos'),
'end' => __('文章结尾', 'kratos'),
),
);
$options[] = array(
'name' => __('默认状态', 'kratos'),
'desc' => __('<b>默认显示</b>:进来就能看到全部条目。<b>默认隐藏</b>:先只露出标题栏(或侧边的标签),读者自己点开。<b>较长或标题很多的文章建议默认隐藏</b>,否则目录会占掉一屏。<br>注:这里只管「默认长什么样」,读者点开后仍然可以正常使用;正文里手写的 <b>[TOC]</b> 也可以用 <b>open="0"</b> 单独指定。', 'kratos'),