3.2.24: 评论富文本工具条(B/I/颜色/表情移入)+ kses 放行 span+style + 后台设置页标签左列竖排重排

This commit is contained in:
2026-10-24 21:59:06 +08:00
parent 78fce2d87d
commit fd27b4d218
6 changed files with 441 additions and 26 deletions
+145 -18
View File
@@ -925,11 +925,147 @@
background: none;
}
/* 工具条 */
/* 工具条:加粗 / 斜体 / 字体颜色 / 表情。
*
* 位于输入框上方,与 textarea 同在 .comment-textarea 这个带边框的
* 容器里 —— 工具条铺满容器顶部,输入框一聚焦整框(含工具条)
* 一起亮主色描边,视觉上是"一个编辑器",而不是两条孤立的横条。
* 面板(色板 / 表情)从按钮**下方**弹出:按钮在容器顶部,
* 向上弹会飘到表单外面盖住上文,向下弹只盖住自己的输入区。 */
.comment-toolbar {
display: flex;
align-items: center;
gap: 2px;
padding: 5px 9px;
border-bottom: 1px solid #edf0f4;
border-radius: var(--yq-radius, 4px) var(--yq-radius, 4px) 0 0;
background-color: #fafbfc;
}
/* 面板的定位父级:每个带面板的按钮各包一层 */
.comment-tool-group {
position: relative;
display: flex;
align-items: center;
}
/* 按钮组之间的细分隔线 */
.comment-toolbar-sep {
align-self: center;
width: 1px;
height: 16px;
margin: 0 5px;
background-color: #e2e7ee;
}
.comment-tool-btn {
display: inline-flex;
align-items: center;
justify-content: center;
min-width: 30px;
height: 30px;
padding: 0 7px;
border: 0;
border-radius: var(--yq-radius, 4px);
background: none;
color: var(--yq-text-2, #4b5563);
font-size: 14px;
line-height: 1;
cursor: pointer;
transition: color 0.16s var(--yq-ease), background-color 0.16s var(--yq-ease);
}
.comment-tool-btn:hover {
background-color: var(--yq-primary-soft, #eaf6ff);
color: var(--yq-primary-dark, #0089db);
}
.comment-tool-btn:focus-visible {
outline: 2px solid var(--yq-primary, #00a2ff);
outline-offset: 1px;
}
/* 字形:B 用粗体、I 用衬线斜体,一眼能认出对应排版效果 */
.comment-tool-glyph {
display: inline-block;
font-family: inherit;
font-size: 14.5px;
line-height: 1;
}
.comment-tool-glyph-bold {
font-weight: 700;
}
.comment-tool-glyph-italic {
font-family: Georgia, "Times New Roman", serif;
font-style: italic;
font-weight: 600;
}
/* 颜色按钮:字母 A 底下压一条渐变色条,点开是色板 */
.comment-tool-glyph-color {
position: relative;
font-weight: 600;
}
.comment-tool-glyph-color::after {
content: "";
position: absolute;
left: 1px;
right: 1px;
bottom: -3px;
height: 3px;
border-radius: 2px;
background: linear-gradient(90deg, #e74c3c 0%, #f1c40f 30%, #2ecc71 55%, #00a2ff 80%, #9b59b6 100%);
}
/* 色板:8 个圆点,从按钮下方弹出 */
.comment-color-panel {
position: absolute;
top: calc(100% + 6px);
left: 0;
z-index: 21;
display: flex;
flex-wrap: wrap;
gap: 6px;
width: 164px;
padding: 10px;
border: 1px solid #e2e7ee;
border-radius: var(--yq-radius-lg, 6px);
background-color: #fff;
box-shadow: 0 6px 20px rgba(31, 35, 40, 0.12);
}
.comment-color-panel[hidden] {
display: none;
}
.comment-color-swatch {
width: 20px;
height: 20px;
padding: 0;
border: 1px solid rgba(0, 0, 0, 0.08);
border-radius: 50%;
cursor: pointer;
transition: transform 0.12s var(--yq-ease), box-shadow 0.12s var(--yq-ease);
}
.comment-color-swatch:hover {
transform: scale(1.18);
box-shadow: 0 0 0 2px #fff, 0 0 0 3.5px var(--yq-primary, #00a2ff);
}
.comment-color-swatch:focus-visible {
outline: 2px solid var(--yq-primary, #00a2ff);
outline-offset: 1px;
}
/* 底栏:表情移进工具条后只剩状态提示 + 提交按钮 */
.comment-bar {
display: flex;
align-items: center;
justify-content: space-between;
justify-content: flex-end;
gap: 10px;
padding: 8px 11px;
border-top: 1px solid #edf0f4;
@@ -937,27 +1073,18 @@
background-color: #fafbfc;
}
.comment-bar-left,
.comment-bar-right {
display: flex;
align-items: center;
gap: 8px;
}
.comment-bar-left {
position: relative;
}
/* 表情按钮:并入工具条后尺寸与其它工具按钮一致(原 4px 8px 小药丸) */
.comment-smile-btn {
padding: 4px 8px;
border: 0;
border-radius: var(--yq-radius, 4px);
background: none;
color: var(--yq-text-3, #6b7280);
font-size: 17px;
line-height: 1;
cursor: pointer;
transition: color 0.16s var(--yq-ease), background-color 0.16s var(--yq-ease);
min-width: 30px;
height: 30px;
padding: 0 7px;
font-size: 16px;
}
.comment-smile-btn:hover {
@@ -965,10 +1092,10 @@
color: var(--yq-primary-dark, #0089db);
}
/* 表情面板:3 行 8 列 */
/* 表情面板:按钮在容器顶部,改为**向下**弹出(原 bottom 定位作废) */
.comment-smile-panel {
position: absolute;
bottom: calc(100% + 8px);
top: calc(100% + 6px);
left: 0;
z-index: 20;
display: flex;
+70
View File
@@ -176,6 +176,76 @@
}
})
/* ── 富文本工具条(3.2.24)─────────────────────────────────
* 评论内容仍是纯文本存储;工具条只在光标/选区处插入受控标签:
* B → <strong>…</strong> I → <em>…</em>
* 颜色 → <span style="color:#xxx">…</span>
* 后端由 theme-comments.php 的 kses 白名单放行这三种标签
* (style 属性再经 WP 核心 safecss_filter_attr 消毒,只留安全属性)。
*
* 选区为空时插入占位文本并**选中**它 —— 用户直接打字就覆盖占位,
* 这是各家编辑器工具条的通行做法;不选中的话占位文本会留在正文里。
*/
function wrapSelection (before, after, placeholder) {
if (!textarea) return
textarea.focus()
var s = textarea.selectionStart || 0
var e = textarea.selectionEnd || 0
var v = textarea.value
var sel = v.slice(s, e)
var usedPlaceholder = false
if (sel === '') {
sel = placeholder || ''
usedPlaceholder = true
}
textarea.value = v.slice(0, s) + before + sel + after + v.slice(e)
// 选中被包裹的内容(或占位文本),光标落回包裹体内
var a = s + before.length
var b = a + sel.length
try { textarea.setSelectionRange(a, b) } catch (ex) { /* 老 IE 忽略 */ }
return usedPlaceholder
}
// B / I:按钮上带 data-wrap-before / data-wrap-after,加新按钮不用改 JS
$$('.comment-tool-btn[data-wrap-before]', form).forEach(function (btn) {
btn.addEventListener('click', function () {
wrapSelection(
btn.getAttribute('data-wrap-before') || '',
btn.getAttribute('data-wrap-after') || '',
I18N.boldPlaceholder || '加粗文字'
)
})
})
/* 字体颜色面板:开合 + 色板点击包裹选区。
* 与表情面板同款交互:点按钮开/关,点外面关,选中后关。 */
var colorBtn = $('#yang-tool-color', form)
var colorPanel = $('#yang-color-panel', form)
if (colorBtn && colorPanel) {
colorBtn.addEventListener('click', function (ev) {
ev.stopPropagation()
var open = !colorPanel.hidden
colorPanel.hidden = open
colorBtn.setAttribute('aria-expanded', open ? 'false' : 'true')
})
document.addEventListener('click', function (ev) {
if (colorPanel.hidden) return
if (colorPanel.contains(ev.target) || colorBtn.contains(ev.target)) return
colorPanel.hidden = true
colorBtn.setAttribute('aria-expanded', 'false')
})
colorPanel.addEventListener('click', function (ev) {
var sw = ev.target.closest ? ev.target.closest('.comment-color-swatch') : null
if (!sw) return
var c = sw.getAttribute('data-color') || '#000000'
// 色值只允许 #rrggbb,面板里写死的,这里再挡一道
if (!/^#[0-9a-fA-F]{6}$/.test(c)) return
wrapSelection('<span style="color:' + c + '">', '</span>', I18N.colorPlaceholder || '彩色文字')
colorPanel.hidden = true
colorBtn.setAttribute('aria-expanded', 'false')
})
}
/* ── 表情面板 ──────────────────────────────────────────────
* 点表情按钮 → 把对应代码插到光标处(不是简单追加到末尾)。
* 旧实现是 `myField.value += tag`,光标本在中间也会跑到最后,
+78 -7
View File
@@ -222,21 +222,92 @@ if ($yang_per_page <= 0) {
<div class="comment-textarea">
<label for="comment" class="sr-only"><?php esc_html_e('评论内容', 'kratos'); ?></label>
<textarea class="comment-input" id="comment" name="comment" rows="6"
placeholder="<?php esc_attr_e('说点什么…(支持 :smile: 这类表情代码)', 'kratos'); ?>"
required="required" maxlength="65525"></textarea>
<div class="comment-bar">
<div class="comment-bar-left">
<button type="button" class="comment-smile-btn" id="yang-smile-btn"
<?php
/* ── 3.2.24 富文本工具条 ──────────────────────────────
* 在输入框上方一排小按钮:加粗 / 斜体 / 字体颜色 / 表情。
* 不是 contenteditable 富文本 —— 评论内容仍是纯文本存储,
* 工具条只是在光标处插入受控的少量 HTML 标签
* (<strong>/<em>/<span style="color:…">),后端由
* theme-comments.php 里的 kses 白名单放行这三种标签,
* style 属性由 WP 核心的 safecss_filter_attr 二次消毒,
* 只放行 color 这类安全属性。改动面最小、无 XSS 面。
*
* 表情按钮原来在底栏左侧,3.2.24 起并入
* 工具条;id(yang-smile-btn / yang-smile-panel)不变,
* comments.js 的面板开合逻辑不用改。
*/
?>
<div class="comment-toolbar" role="toolbar"
aria-label="<?php esc_attr_e('评论格式工具条', 'kratos'); ?>">
<button type="button" class="comment-tool-btn" id="yang-tool-bold"
data-wrap-before="&lt;strong&gt;" data-wrap-after="&lt;/strong&gt;"
title="<?php esc_attr_e('加粗', 'kratos'); ?>"
aria-label="<?php esc_attr_e('加粗', 'kratos'); ?>">
<span class="comment-tool-glyph comment-tool-glyph-bold" aria-hidden="true">B</span>
</button>
<button type="button" class="comment-tool-btn" id="yang-tool-italic"
data-wrap-before="&lt;em&gt;" data-wrap-after="&lt;/em&gt;"
title="<?php esc_attr_e('斜体', 'kratos'); ?>"
aria-label="<?php esc_attr_e('斜体', 'kratos'); ?>">
<span class="comment-tool-glyph comment-tool-glyph-italic" aria-hidden="true">I</span>
</button>
<span class="comment-toolbar-sep" aria-hidden="true"></span>
<div class="comment-tool-group">
<button type="button" class="comment-tool-btn comment-tool-color" id="yang-tool-color"
aria-expanded="false" aria-controls="yang-color-panel"
title="<?php esc_attr_e('字体颜色', 'kratos'); ?>"
aria-label="<?php esc_attr_e('字体颜色', 'kratos'); ?>">
<span class="comment-tool-glyph comment-tool-glyph-color" aria-hidden="true">A</span>
</button>
<div class="comment-color-panel" id="yang-color-panel" hidden>
<?php
/* 预设色板:常用 8 色。data-color 由 JS 读去包选区,
不用 <input type=color> —— 原生取色器弹窗体验割裂,
而且访客只是想快速标个重点色。 */
$yang_tool_colors = array(
'#000000' => '黑色',
'#7f8c8d' => '灰色',
'#e74c3c' => '红色',
'#e67e22' => '橙色',
'#f1c40f' => '黄色',
'#2ecc71' => '绿色',
'#00a2ff' => '蓝色',
'#9b59b6' => '紫色',
);
foreach ($yang_tool_colors as $yang_c => $yang_name) :
?>
<button type="button" class="comment-color-swatch"
data-color="<?php echo esc_attr($yang_c); ?>"
style="background-color: <?php echo esc_attr($yang_c); ?>;"
title="<?php echo esc_attr(sprintf(__('字体颜色:%s', 'kratos'), $yang_name)); ?>"
aria-label="<?php echo esc_attr(sprintf(__('字体颜色:%s', 'kratos'), $yang_name)); ?>"></button>
<?php endforeach; ?>
</div>
</div>
<span class="comment-toolbar-sep" aria-hidden="true"></span>
<div class="comment-tool-group comment-tool-group-smile">
<button type="button" class="comment-tool-btn comment-smile-btn" id="yang-smile-btn"
aria-expanded="false" aria-controls="yang-smile-panel"
title="<?php esc_attr_e('插入表情', 'kratos'); ?>">
title="<?php esc_attr_e('插入表情', 'kratos'); ?>"
aria-label="<?php esc_attr_e('插入表情', 'kratos'); ?>">
<i class="kicon i-face" aria-hidden="true"></i>
</button>
<div class="comment-smile-panel" id="yang-smile-panel" hidden>
<?php echo $smilies; ?>
</div>
</div>
</div>
<textarea class="comment-input" id="comment" name="comment" rows="6"
placeholder="<?php esc_attr_e('说点什么…(支持 :smile: 这类表情代码)', 'kratos'); ?>"
required="required" maxlength="65525"></textarea>
<div class="comment-bar">
<div class="comment-bar-right">
<span class="comment-status" id="yang-comment-status" role="status" aria-live="polite"></span>
<button type="submit" id="submit" class="comment-submit">
@@ -359,3 +359,112 @@
line-height: 1.5;
margin-top: 3px;
}
/* ══════════════════════════════════════════════════════════════
3.2.24 设置页布局重排
用户反馈:"主题设置的样式不好看,把标签页按钮放左侧、上下排列,
功能项放在右侧,重新设置好看一点。"
实现:≥960px 时把 nav-tab-wrapper 浮到左侧成一列竖排导航,
内容区(含保存提示条)整体右移;窄屏回退 WP 默认的横向标签,
不在手机后台做特殊布局。切标签的 JS(options-custom.js)不动,
它只认 .nav-tab / .nav-tab-active / .group 这几个类。
══════════════════════════════════════════════════════════════ */
@media screen and (min-width: 960px) {
#optionsframework-wrap {
max-width: 1180px;
}
/* 浮动布局的收尾:否则 wrap 高度塌到标签列或面板较短的那个 */
#optionsframework-wrap::after {
display: table;
clear: both;
content: "";
}
/* ── 左侧标签列 ── */
#optionsframework-wrap h2.nav-tab-wrapper {
float: left;
box-sizing: border-box;
width: 190px;
margin: 0 20px 20px 0;
padding: 8px;
border: 1px solid #dcdcde;
border-radius: 6px;
background: #fff;
box-shadow: 0 1px 1px rgba(0, 0, 0, 0.04);
}
#optionsframework-wrap .nav-tab-wrapper .nav-tab {
float: none;
display: block;
width: 100%;
box-sizing: border-box;
margin: 0 0 2px;
padding: 9px 12px;
border: 0;
border-left: 3px solid transparent;
border-radius: 4px;
background: none;
color: #50575e;
line-height: 1.5;
cursor: pointer;
}
#optionsframework-wrap .nav-tab-wrapper .nav-tab:last-child {
margin-bottom: 0;
}
#optionsframework-wrap .nav-tab-wrapper .nav-tab:hover {
border-left-color: transparent;
background: #f0f0f1;
color: #1d2327;
}
#optionsframework-wrap .nav-tab-wrapper .nav-tab-active,
#optionsframework-wrap .nav-tab-wrapper .nav-tab-active:hover {
border-left-color: #00a2ff;
background: #e8f5ff;
color: #0089db;
font-weight: 600;
}
/* ── 右侧内容区:面板 + 保存提示条都要让开左侧一列 ── */
#optionsframework-wrap > #optionsframework-metabox,
#optionsframework-wrap > .notice,
#optionsframework-wrap > .updated,
#optionsframework-wrap > .error {
margin-left: 210px;
}
#optionsframework-metabox #optionsframework {
border-radius: 6px;
}
}
/* ── 区块内部(宽窄屏都生效)────────────────────────────────
1. 分组标题:原来 `#optionsframework h3` 是给面板顶部那条用的
灰底横条,选择器太宽把每个分组标题也罩了进去,整页一排灰条。
这里把分组标题单独重排:去灰底、加重字重、压淡下边线。 */
#optionsframework .group > h3 {
margin: 0;
padding: 15px 18px 13px;
border-bottom: 1px solid #f0f0f1;
background: none;
font-size: 15px;
font-weight: 600;
color: #1d2327;
}
/* 2. 区块间距与分隔:原来 10px 的内边距太挤,项与项之间没有分界 */
#optionsframework .section {
padding: 16px 18px 8px;
border-bottom: 1px solid #f6f7f7;
}
/* 3. 提交栏:与分组标题同一套留白 */
#optionsframework-submit {
padding: 12px 18px;
}
+38
View File
@@ -43,6 +43,42 @@ if (!defined('ABSPATH')) {
exit;
}
/**
* 3.2.24 富文本工具条:给访客评论放行 <span style="color:…">
*
* 工具条插入的三种标签里,<strong>/<em> 本来就在 WP 评论的
* kses 白名单($allowedtags)里,不用管;<span style="color:…">
* 不在 —— 访客提交时会被 wp_filter_kses 整个剥掉,颜色功能就白做了。
*
* 这里把 span(带 style / class 属性)追加进 kses 白名单。安全性:
* 1. kses 对 style 属性的内容会再过一遍 safecss_filter_attr(),
* 那是一份固定白名单(color、font-weight、text-align…),
* expression()/url()/behavior() 这些危险属性一律剥掉;
* 2. 工具条前端只生成 color 一个声明,白名单只是兜底;
* 3. 有 unfiltered_html 权限的用户(管理员)本来就不走 kses,不受影响。
*
* ⚠️ 不按 context 区分:wp_filter_kses 实际传进来的 context 串
* 在 WP 各版本间不稳定,写死 'pre_comment_user' 之类的会静默失效
* (失效 = 标签被剥 = 功能没了但没有任何报错)。span+style 放行到
* 任何 kses 场景都是安全的(style 内容有 safecss_filter_attr 挡着),
* 所以对除 'strip' 外的所有 context 一律追加。
*/
function yang_kses_allow_span($tags, $context)
{
if ($context === 'strip') {
return $tags;
}
if (!is_array($tags)) {
$tags = array();
}
$tags['span'] = array(
'style' => true,
'class' => true,
);
return $tags;
}
add_filter('wp_kses_allowed_html', 'yang_kses_allow_span', 10, 2);
/**
* wpDiscuz 是否在接管评论
*
@@ -652,6 +688,8 @@ function yang_comment_scripts()
'tooLong' => __('评论内容太长了。', 'kratos'),
'loading' => __('加载中…', 'kratos'),
'noMore' => __('没有更多评论了', 'kratos'),
'boldPlaceholder' => __('加粗文字', 'kratos'),
'colorPlaceholder' => __('彩色文字', 'kratos'),
),
));
}
+1 -1
View File
@@ -1,7 +1,7 @@
/*
Theme Name: Kratos_Yang
Text Domain: kratos_Yang
Version: 3.2.23
Version: 3.2.24
Requires PHP: 7.0
Description: 这是一个Kratos主题的分支版本,用来增加一些定制化功能。
Tags: 博客, 双栏, 响应式, 自定义背景, 自定义颜色, 自定义图标, 自定义菜单, 特色图像, 收录优化, 邮件中心, 主题选项, 小工具, 文章置顶, 无障碍友好