refactor: 移除设置页键盘操作支持并升级 1.0.7
- SettingsView 删除方向键切换、roving tabindex、ARIA 单选组语义与焦点环,仅保留原生按钮点击切换 - README 清理过时描述并补充 v1.0.7 更新日志 - 版本升级 1.0.7
This commit is contained in:
@@ -128,6 +128,11 @@ pnpm build # 生产构建
|
|||||||
|
|
||||||
## 更新日志
|
## 更新日志
|
||||||
|
|
||||||
|
### v1.0.7
|
||||||
|
|
||||||
|
- 移除设置页方向键切换、roving tabindex 与 ARIA 单选组语义,保留原生按钮点击切换
|
||||||
|
- 同步清理 README 中已过时的「设置选项支持键盘操作」描述
|
||||||
|
|
||||||
### v1.0.6
|
### v1.0.6
|
||||||
|
|
||||||
- 深色模式设置入口移至 Halo 官方「外观」分组
|
- 深色模式设置入口移至 Halo 官方「外观」分组
|
||||||
@@ -138,7 +143,6 @@ pnpm build # 生产构建
|
|||||||
|
|
||||||
- 同步设置 `color-scheme`,缓解深色模式刷新闪烁
|
- 同步设置 `color-scheme`,缓解深色模式刷新闪烁
|
||||||
- 增加多标签页主题同步与 `useDarkMode` 单元测试
|
- 增加多标签页主题同步与 `useDarkMode` 单元测试
|
||||||
- 侧边栏切换按钮与设置选项支持键盘操作
|
|
||||||
- 后端日志改用 Lombok `@Slf4j`
|
- 后端日志改用 Lombok `@Slf4j`
|
||||||
- 裁剪 vendored `package.json`,新增 Dark Reader `SHA256SUMS` 完整性校验
|
- 裁剪 vendored `package.json`,新增 Dark Reader `SHA256SUMS` 完整性校验
|
||||||
- `plugin.yaml` 改为安装后由用户手动启用
|
- `plugin.yaml` 改为安装后由用户手动启用
|
||||||
|
|||||||
@@ -0,0 +1,70 @@
|
|||||||
|
# 修改交接单:移除设置页键盘操作支持(2026-08-08)
|
||||||
|
|
||||||
|
> 审查窗口产出,**交付开发窗口执行**;审查窗口不修改代码。
|
||||||
|
> 本文档为**唯一交接文件**(已合并 `recheck-entry-2026-08-08.md` 的内容)。
|
||||||
|
> 用户决定:设置页的键盘操作支持"没必要",要求移除。
|
||||||
|
|
||||||
|
## 零、背景:1.0.6 已落实项(复查结论,无需重复处理)
|
||||||
|
|
||||||
|
提交 `394613f fix: 深色模式入口移至外观分组并移除侧边栏按钮(1.0.6)` 已复查通过:
|
||||||
|
|
||||||
|
- 菜单移入「外观」分组(`ui/src/index.ts` `group: 'interface'`),线上已生效
|
||||||
|
- 侧边栏注入按钮已移除(`injector.ts`、`ThemeToggle.vue` 已删除),线上 `.theme-toggle` 数量 = 0
|
||||||
|
- `verify-toggle.py` 已改为 localStorage + `StorageEvent` 驱动 + color-scheme 断言
|
||||||
|
- 文档已同步、版本号 1.0.6
|
||||||
|
- 构建验证:vitest 8/8 · vue-tsc · prettier · vite build · `gradlew build` 全绿
|
||||||
|
|
||||||
|
唯一遗留的 R-1(README 过时描述)由本文档第四节第 4 点一并处理。
|
||||||
|
|
||||||
|
## 一、需求描述
|
||||||
|
|
||||||
|
移除 `SettingsView.vue`(深色模式设置页)中为"键盘操作"特意实现的逻辑:
|
||||||
|
方向键切换选项、roving tabindex、ARIA 单选组语义、焦点环等。保留最简交互:点击三个选项按钮切换主题模式。
|
||||||
|
|
||||||
|
## 二、决策依据(用户补充)
|
||||||
|
|
||||||
|
- Halo 后台的官方页面与第三方插件页面均**没有方向键特殊交互的惯例**:按方向键上下键只用于页面滚动/翻页,没有其他特殊功能。
|
||||||
|
- 设置页单独实现"方向键切换主题选项"(radiogroup 键盘导航)与平台整体交互习惯不一致,用户群体也不会预期方向键会改变主题选择,因此判定该键盘操作支持**不必要**,予以移除。
|
||||||
|
- 保留原生 `<button>` 即可:点击可用,浏览器内置的 Tab 聚焦 + Enter/空格 激活属基础可用性,非刻意实现,无需额外维护。
|
||||||
|
|
||||||
|
## 三、现状代码体现(`ui/src/views/SettingsView.vue`)
|
||||||
|
|
||||||
|
| 行号 | 内容 | 作用 |
|
||||||
|
| --- | --- | --- |
|
||||||
|
| L10 | `import { computed, nextTick, ref } from 'vue'` | `nextTick`/`ref` 仅键盘导航使用 |
|
||||||
|
| L27 | `const optionEls = ref<HTMLButtonElement[]>([])` | 选项按钮 DOM 引用数组 |
|
||||||
|
| L29-L35 | `activeIndex` computed | 当前选中索引 |
|
||||||
|
| L37-L39 | `setOptionRef(el, index)` | 收集按钮 ref |
|
||||||
|
| L41-L45 | `focusOption(index)` | 方向键切换选中并聚焦 |
|
||||||
|
| L47-L61 | `onKeydown(event)` | ArrowUp/Down/Left/Right/Home/End 处理 |
|
||||||
|
| L81-L84 | `role="radiogroup"` `aria-label` `@keydown="onKeydown"` | 单选组容器语义 |
|
||||||
|
| L88-L104 | button 上的 `role="radio"` / `:aria-checked` / `:tabindex`(roving)/ `:ref` / `@click` | 单选按钮语义与焦点管理 |
|
||||||
|
| L162-L165 | `.dark-mode-settings__option:focus-visible` | 键盘焦点环(可选移除) |
|
||||||
|
|
||||||
|
连带:`README.md` L141 `- 侧边栏切换按钮与设置选项支持键盘操作`(R-1,侧边栏按钮已删,"键盘操作"整体废弃)。
|
||||||
|
|
||||||
|
## 四、修改建议
|
||||||
|
|
||||||
|
### 方案 A(推荐):移除键盘导航实现,保留简单按钮
|
||||||
|
|
||||||
|
`ui/src/views/SettingsView.vue`:
|
||||||
|
1. **script**:
|
||||||
|
- 删除 L27 `optionEls`、L29-L35 `activeIndex`、L37-L39 `setOptionRef`、L41-L45 `focusOption`、L47-L61 `onKeydown`
|
||||||
|
- L10 import 精简为 `import { computed } from 'vue'`(`nextTick`、`ref` 不再使用)
|
||||||
|
2. **template**:
|
||||||
|
- L81-L84 删除 `role="radiogroup"`、`aria-label="主题模式"`、`@keydown="onKeydown"`
|
||||||
|
- L88-L104 按钮上删除 `role="radio"`、`:aria-checked`、`:tabindex`、`:ref`,仅保留 `type="button"`、`:class="{ 'is-active': ... }"`、`@click="setTheme(option.value)"`(`v-for` 中 `index` 不再需要,可去掉)
|
||||||
|
3. **style**(可选):删除 L162-L165 `:focus-visible` 规则;也可保留(无害,仅鼠标点击时不会触发)
|
||||||
|
4. **README.md**:删除 L141 整行(R-1 一并处理)
|
||||||
|
5. 构建回归:`cd ui && pnpm build`、`pnpm type-check`、`pnpm prettier`、`pnpm lint`、`pnpm test:unit`;根目录 `gradlew build`
|
||||||
|
|
||||||
|
### 方案 B(备选,不推荐):彻底无键盘语义
|
||||||
|
|
||||||
|
- 三个选项改回 `<div>` + `@click`(删除 button 全部键盘/语义属性)
|
||||||
|
- 需在样式补 `cursor: pointer`;可访问性退化,仅当明确要求时使用
|
||||||
|
|
||||||
|
## 五、注意
|
||||||
|
|
||||||
|
- 删除逻辑不涉及 `useDarkMode` / `darkreader-engine`,状态管理与主题切换不受影响。
|
||||||
|
- vitest 仅覆盖 `useDarkMode`,不受本次改动影响。
|
||||||
|
- 本次为前端 UI 变更,需重新构建部署后验证。
|
||||||
+1
-1
@@ -1,3 +1,3 @@
|
|||||||
version=1.0.6
|
version=1.0.7
|
||||||
org.gradle.jvmargs=-Xmx2g -Dfile.encoding=UTF-8
|
org.gradle.jvmargs=-Xmx2g -Dfile.encoding=UTF-8
|
||||||
org.gradle.java.home=D:/settings/Language/Java/jdk-25.0.1
|
org.gradle.java.home=D:/settings/Language/Java/jdk-25.0.1
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ import run.halo.app.plugin.PluginContext;
|
|||||||
* <p>Only one main class extending {@link BasePlugin} is allowed per plugin.</p>
|
* <p>Only one main class extending {@link BasePlugin} is allowed per plugin.</p>
|
||||||
*
|
*
|
||||||
* @author LHY
|
* @author LHY
|
||||||
* @since 1.0.6
|
* @since 1.0.7
|
||||||
*/
|
*/
|
||||||
@Slf4j
|
@Slf4j
|
||||||
@Component
|
@Component
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import type { ThemeMode } from '../composables/useDarkMode'
|
import type { ThemeMode } from '../composables/useDarkMode'
|
||||||
import { useDarkMode } from '../composables/useDarkMode'
|
import { useDarkMode } from '../composables/useDarkMode'
|
||||||
import { computed, nextTick, ref } from 'vue'
|
import { computed } from 'vue'
|
||||||
|
|
||||||
const { theme, isDark, setTheme } = useDarkMode()
|
const { theme, isDark, setTheme } = useDarkMode()
|
||||||
|
|
||||||
@@ -17,42 +17,6 @@ const modeOptions: { value: ThemeMode; label: string; description: string }[] =
|
|||||||
{ value: 'dark', label: '深色', description: '始终使用深色模式' },
|
{ value: 'dark', label: '深色', description: '始终使用深色模式' },
|
||||||
{ value: 'auto', label: '跟随系统', description: '根据系统设置自动切换' },
|
{ value: 'auto', label: '跟随系统', description: '根据系统设置自动切换' },
|
||||||
]
|
]
|
||||||
|
|
||||||
const optionEls = ref<HTMLButtonElement[]>([])
|
|
||||||
const activeIndex = computed(() =>
|
|
||||||
Math.max(
|
|
||||||
0,
|
|
||||||
modeOptions.findIndex((option) => option.value === theme.value),
|
|
||||||
),
|
|
||||||
)
|
|
||||||
|
|
||||||
function setOptionRef(el: unknown, index: number): void {
|
|
||||||
if (el) {
|
|
||||||
optionEls.value[index] = el as HTMLButtonElement
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function focusOption(index: number): void {
|
|
||||||
const next = (index + modeOptions.length) % modeOptions.length
|
|
||||||
setTheme(modeOptions[next].value)
|
|
||||||
void nextTick(() => optionEls.value[next]?.focus())
|
|
||||||
}
|
|
||||||
|
|
||||||
function onKeydown(event: KeyboardEvent): void {
|
|
||||||
if (event.key === 'ArrowDown' || event.key === 'ArrowRight') {
|
|
||||||
event.preventDefault()
|
|
||||||
focusOption(activeIndex.value + 1)
|
|
||||||
} else if (event.key === 'ArrowUp' || event.key === 'ArrowLeft') {
|
|
||||||
event.preventDefault()
|
|
||||||
focusOption(activeIndex.value - 1)
|
|
||||||
} else if (event.key === 'Home') {
|
|
||||||
event.preventDefault()
|
|
||||||
focusOption(0)
|
|
||||||
} else if (event.key === 'End') {
|
|
||||||
event.preventDefault()
|
|
||||||
focusOption(modeOptions.length - 1)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
@@ -67,26 +31,13 @@ function onKeydown(event: KeyboardEvent): void {
|
|||||||
当前生效:<strong>{{ currentEffectiveMode }}</strong>
|
当前生效:<strong>{{ currentEffectiveMode }}</strong>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div
|
<div class="dark-mode-settings__options">
|
||||||
class="dark-mode-settings__options"
|
|
||||||
role="radiogroup"
|
|
||||||
aria-label="主题模式"
|
|
||||||
@keydown="onKeydown"
|
|
||||||
>
|
|
||||||
<button
|
<button
|
||||||
v-for="(option, index) in modeOptions"
|
v-for="option in modeOptions"
|
||||||
:key="option.value"
|
:key="option.value"
|
||||||
type="button"
|
type="button"
|
||||||
role="radio"
|
|
||||||
class="dark-mode-settings__option"
|
class="dark-mode-settings__option"
|
||||||
:class="{ 'is-active': theme === option.value }"
|
:class="{ 'is-active': theme === option.value }"
|
||||||
:aria-checked="theme === option.value"
|
|
||||||
:tabindex="theme === option.value ? 0 : -1"
|
|
||||||
:ref="
|
|
||||||
(el: unknown) => {
|
|
||||||
setOptionRef(el, index)
|
|
||||||
}
|
|
||||||
"
|
|
||||||
@click="setTheme(option.value)"
|
@click="setTheme(option.value)"
|
||||||
>
|
>
|
||||||
<div class="dark-mode-settings__option-label">{{ option.label }}</div>
|
<div class="dark-mode-settings__option-label">{{ option.label }}</div>
|
||||||
@@ -163,11 +114,6 @@ function onKeydown(event: KeyboardEvent): void {
|
|||||||
background-color: var(--halo-bg-hover);
|
background-color: var(--halo-bg-hover);
|
||||||
}
|
}
|
||||||
|
|
||||||
.dark-mode-settings__option:focus-visible {
|
|
||||||
outline: 2px solid var(--halo-accent-primary);
|
|
||||||
outline-offset: 2px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.dark-mode-settings__option.is-active {
|
.dark-mode-settings__option.is-active {
|
||||||
background-color: var(--halo-menu-item-active);
|
background-color: var(--halo-menu-item-active);
|
||||||
border-color: var(--halo-accent-primary);
|
border-color: var(--halo-accent-primary);
|
||||||
|
|||||||
Reference in New Issue
Block a user