Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion plugins/hushreader/.gitignore
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
node_modules
dist
**/*.zip
test
test
功能说明文档.md
docs/
28 changes: 28 additions & 0 deletions plugins/hushreader/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,34 @@
# Changelog

All notable changes to this project will be documented in this file.

## [1.4.0](https://github.com/me1dlinger/hushreader/releases/tag/v1.4.0) - 2026-06-23

### Added
- **已读完标记**:读完最后一章时自动标记为"已读完",书籍卡片封面右上角显示"已读完"角标
- **标记为未读完**:右键菜单新增"标记为未读完"选项,可清除已读完状态(仅已读完书籍显示)
- **书架统计栏**:书架顶部新增统计栏,显示总书籍数、已读数、累计阅读时长
- **书签功能**:隐阅窗口右键菜单新增"添加书签"选项,自动提取当前页首句作为书签内容
- **书签快捷键**:隐阅窗口支持可配置的添加书签快捷键(默认 Shift+F)
- **书签列表**:书架书籍右键菜单新增"书签列表"选项,显示日期、阅读进度和内容预览
- **书签跳转**:双击书签可打开隐阅窗口并跳转到书签位置
- **书签删除**:书签列表中每条书签支持直接删除,无需确认
- **销毁隐阅窗口**:新增可配置的销毁快捷键(默认 Shift+D),快速关闭隐阅窗口
- **全文搜索跳转**:书籍右键菜单新增"搜索跳转"选项,可搜索全书内容,按句子展示结果,支持分页浏览、关键词高亮、双击跳转到对应位置
- **批量设置分类**:多选模式下新增"批量设置分类"按钮,可统一为选中书籍设置分类
- **卡片/列表视图切换**:书架头部新增视图切换按钮,支持卡片视图与列表视图一键切换,移除设置界面"其他设置"中的"列表书架模式"开关

### Changed
- **多选按钮图标**:多选按钮默认图标改为勾选框样式,与视图切换按钮做视觉区分
- **列表模式已读完标识**:列表模式下已读完标识从封面移至文字信息行,避免挤占小封面空间

### Fixed
- **阅读百分比无法达到100%**:修复翻到最后一页时百分比达不到100%的问题,最后一页使用页末字符偏移计算进度
- **恢复封面未删除自定义封面**:修复 TXT 书籍恢复封面时自定义封面未从 IndexedDB 删除,导致重启后仍加载自定义封面的问题
- **多选模式已读完角标重叠**:修复多选模式下已读完角标与勾选框重叠的问题
- **搜索 XSS 隐患**:修复全文搜索结果使用 v-html 渲染未转义内容导致的 XSS 安全隐患
- **搜索性能问题**:修复全文搜索循环中每次迭代重复调用 toLowerCase() 导致大文件 OOM 和卡死的问题,将小写转换提取到循环外并限制句子边界查找窗口

## [1.3.3](https://github.com/me1dlinger/hushreader/releases/tag/v1.3.3) - 2026-06-19

### Added
Expand Down
54 changes: 38 additions & 16 deletions plugins/hushreader/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,25 +16,45 @@

## 功能一览

| 阅读体验 | 个性化 | 书架管理 |
| :------------------ | :--------------- | :---------------- |
| 沉浸式悬浮窗口,可置于任意位置 | 背景透明度与整体透明度分离控制 | 支持按添加时间、书名、作者、最近阅读排序 |
| 滚轮翻页 / 快捷键翻页 / 自动翻页 | 自定义字体,支持添加系统字体 | 右键编辑元数据(标题、作者、分类) |
| 只显示完整行,文字不残缺 | 十六进制颜色输入 + 颜色选择器 | 阅读进度持久化,关闭再开继续读 |
| 文本预处理:压缩空行、清理空白 | 设置实时预览,取消可还原 | 拖拽导入 / 快捷文件导入(`导入书籍`) |
| 鼠标移出隐藏三模式(关闭/仅进度/全隐藏) | 亮色 / 暗色主题切换 | 书籍信息窗口(封面、简介、分类、阅读统计) |
| 鼠标移入显示延迟可调 | 列表书架模式 | 多选模式 + 批量重载/删除 |
| 百分比进度编辑跳转 | 窗口大小锁定 | 重载元数据 / 恢复封面 |
| 章节列表高亮当前进度 | — | 书籍分类筛选栏 |
### 隐阅窗口
- 悬浮窗口,可置于任意位置
- 翻页控制:滚轮翻页 / 快捷键翻页 / 自动翻页
- 进度管理:百分比进度编辑跳转、章节列表高亮当前进度、书签功能(添加/列表/跳转/删除)
- 全文搜索:搜索全书内容,按句子展示结果,支持分页浏览、关键词高亮、双击跳转
- 窗口行为:鼠标移出隐藏三模式(不隐藏/仅进度/全隐藏)、移入显示延迟可调、窗口大小锁定
- 快捷操作:添加书签快捷键(Shift+F)、销毁窗口快捷键(Shift+D)

### 书架管理
- 书籍导入:拖拽导入 / 快捷文件导入(`导入书籍`)
- 信息管理:书籍信息窗口(封面、简介、分类、阅读统计)、右键编辑元数据(标题、作者、分类)
- 批量操作:多选模式 + 批量重载/删除/设置分类、重载元数据 / 恢复封面
- 阅读状态:已读完标记 + 标记为未读完、书架统计栏(总书籍/已读/阅读时长)
- 筛选排序:书籍分类筛选栏、按添加时间/书名/作者/最近阅读排序

### 个性化设置
- 外观控制:背景透明度与整体透明度分离控制、亮色/暗色主题切换、列表书架模式
- 字体样式:自定义字体(支持添加系统字体)、十六进制颜色输入 + 颜色选择器
- 配置管理:设置实时预览、取消可还原、多配置方案切换

### 技术特性
- 只显示完整行,文字不残缺
- 文本预处理:压缩空行、清理空白
- 阅读进度持久化,关闭再开继续读

## 截图

![书架](https://files.seeusercontent.com/2026/06/17/qvX8/image_86.png)
![暗色模式](https://files.seeusercontent.com/2026/06/17/lu4H/image_85.png)
![书籍信息](https://files.seeusercontent.com/2026/06/18/D9di/image_90.png)
![隐阅窗口设置](https://files.seeusercontent.com/2026/06/17/eCu4/image_89.png)
![功能设置](https://files.seeusercontent.com/2026/06/17/0bqW/image_88.png)
![其他设置](https://files.seeusercontent.com/2026/06/17/y3uD/image_87.png)
![书架](https://files.seeusercontent.com/2026/06/24/Pqs5/image_105.png)
![列表书架](https://files.seeusercontent.com/2026/06/24/1xDq/image_110.png)
![暗色模式](https://files.seeusercontent.com/2026/06/24/iR3q/image_106.png)
![右键菜单](https://files.seeusercontent.com/2026/06/24/Mb0u/image_107.png)
![书籍信息](https://files.seeusercontent.com/2026/06/24/6oZd/image_109.png)
![章节列表](https://files.seeusercontent.com/2026/06/24/zc1T/image_111.png)
![书签列表](https://files.seeusercontent.com/2026/06/24/Sur5/image_112.png)
![搜索跳转](https://files.seeusercontent.com/2026/06/24/5Nhl/image_113.png)
![书籍分类](https://files.seeusercontent.com/2026/06/24/7xXx/image_114.png)
![隐阅窗口设置](https://files.seeusercontent.com/2026/06/24/Dl4e/image_115.png)
![功能设置](https://files.seeusercontent.com/2026/06/24/9Nle/image_117.png)
![其他设置](https://files.seeusercontent.com/2026/06/24/Wta6/image_118.png)
![隐阅效果](https://files.seeusercontent.com/2026/06/18/h1Jb/show.gif)


Expand Down Expand Up @@ -87,6 +107,8 @@ npm run build # 构建生产版本
| `categories` | 分类数组 |
| `customCoverImage` | 自定义封面 |
| `fileModifiedAt` | 文件修改时间 |
| `isFinished` | 是否已读完 |
| `bookmarks` | 书签列表(章节索引、字符偏移、内容预览、创建时间) |

</details>

Expand Down
2 changes: 1 addition & 1 deletion plugins/hushreader/package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "hushreader",
"private": true,
"version": "1.3.3",
"version": "1.4.0",
"type": "module",
"scripts": {
"dev": "vite",
Expand Down
37 changes: 24 additions & 13 deletions plugins/hushreader/public/hushreader.html
Original file line number Diff line number Diff line change
Expand Up @@ -1015,13 +1015,21 @@
const binding = getKeyboardEventBinding(event)
const prevPageKey = normalizeKeyBinding(latestSettings.prevPageKey, "ArrowLeft")
const nextPageKey = normalizeKeyBinding(latestSettings.nextPageKey, "ArrowRight")
const addBookmarkKey = normalizeKeyBinding(latestSettings.addBookmarkKey, "Shift+F")
const destroyKey = normalizeKeyBinding(latestSettings.destroyKey, "Shift+D")

if (binding && binding === nextPageKey) {
event.preventDefault()
sendCommand("next")
} else if (binding && binding === prevPageKey) {
event.preventDefault()
sendCommand("prev")
} else if (binding && binding === addBookmarkKey) {
event.preventDefault()
sendCommand("add-bookmark")
} else if (binding && binding === destroyKey) {
event.preventDefault()
sendCommand("destroy")
}
})

Expand Down Expand Up @@ -1161,23 +1169,26 @@

document.addEventListener("contextmenu", (event) => {
event.preventDefault()
if (!latestSettings.showHushreaderMeta) return

const metaRect = metaNode.getBoundingClientRect()
const inMetaArea = event.clientX >= metaRect.left - 4 && event.clientX <= metaRect.right + 4
&& event.clientY >= metaRect.top - 4 && event.clientY <= metaRect.bottom + 4

if (!inMetaArea) return

const items = []
items.push({ label: "关闭隐阅窗口", command: "close-reader" })
items.push({ label: "显示主窗口", command: "show-main" })
if (isAutoPaging) {
items.push({ label: "停止自动翻页", command: "stop-auto" })
} else {
items.push({ label: "开启自动翻页", command: "start-auto" })
if (latestSettings.showHushreaderMeta) {
const metaRect = metaNode.getBoundingClientRect()
const inMetaArea = event.clientX >= metaRect.left - 4 && event.clientX <= metaRect.right + 4
&& event.clientY >= metaRect.top - 4 && event.clientY <= metaRect.bottom + 4
if (inMetaArea) {
items.push({ label: "关闭隐阅窗口", command: "close-reader" })
items.push({ label: "添加书签", command: "add-bookmark" })
items.push({ label: "显示主窗口", command: "show-main" })
if (isAutoPaging) {
items.push({ label: "停止自动翻页", command: "stop-auto" })
} else {
items.push({ label: "开启自动翻页", command: "start-auto" })
}
}
}

if (items.length === 0) return

contextMenuNode.innerHTML = ""
items.forEach((item) => {
const btn = document.createElement("button")
Expand Down
2 changes: 1 addition & 1 deletion plugins/hushreader/public/plugin.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
"title": "隐阅盒",
"description": "ZTools自己的摸鱼阅读,支持TXT/EPUB/MOBI格式,沉浸式阅读",
"author": "meidlinger",
"version": "1.3.3",
"version": "1.4.0",
"main": "index.html",
"preload": "preload/services.js",
"logo": "logo.png",
Expand Down
46 changes: 45 additions & 1 deletion plugins/hushreader/src/App.vue
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { computed, nextTick, onBeforeUnmount, onMounted, provide, ref, watch } f
import Bookshelf from './components/Bookshelf/index.vue'
import Toast from './components/Bookshelf/Toast.vue'
import { useReaderStore } from './stores/reader'
import { useBookStore } from './stores/books'
import { useBookStore, type Bookmark } from './stores/books'
import { useConfigStore } from './stores/config'
import { parseTxt } from './utils/txtParser'
import { parseEpub } from './utils/epubParser'
Expand Down Expand Up @@ -208,6 +208,8 @@ function getHushreaderPayload(bounds = getHushreaderWindowBounds()) {
bgOpacity: hushreaderCfg.value.bgOpacity,
prevPageKey: hushreaderCfg.value.prevPageKey,
nextPageKey: hushreaderCfg.value.nextPageKey,
addBookmarkKey: hushreaderCfg.value.addBookmarkKey,
destroyKey: hushreaderCfg.value.destroyKey,
showHushreaderMeta: hushreaderCfg.value.showHushreaderMeta,
progressMode: hushreaderCfg.value.progressMode,
hideOnMouseLeave: hushreaderCfg.value.hideOnMouseLeave,
Expand Down Expand Up @@ -466,6 +468,10 @@ function saveReadingProgress() {
updates.lastSaveReadChars = Math.round(totalChars * (readerStore.readingPercent / 100))
}

if (readerStore.readingPercent >= 100 && !book.finishedAt) {
updates.finishedAt = now
}

(window as any).__hushreaderSessionLastActive = now
bookStore.updateBook(book.id, updates)
}
Expand Down Expand Up @@ -629,9 +635,45 @@ function handleHushreaderCommand(command: HushreaderCommand) {
else if (command === 'close') closePlugin()
else if (command === 'auto') toggleAutoPaging()
else if (command === 'close-reader') { isReaderHidden.value = true; blurHushreaderKeyboard() }
else if (command === 'destroy') { saveReadingProgress(); hushreaderActivated.value = false; stopReadingTimer(); hushreaderWindow?.close?.(); hushreaderWindow = null }
else if (command === 'show-main') { (window as any).ztools?.showMainWindow?.() }
else if (command === 'stop-auto') { isAutoPaging.value = false; hushreaderCfg.value.autoFlipEnabled = false }
else if (command === 'start-auto') { if (currentBook.value) { isAutoPaging.value = true; hushreaderCfg.value.autoFlipEnabled = true } }
else if (command === 'add-bookmark') {
const book = currentBook.value
if (book) {
const pageText = hushreaderLines.value.join('')
const boundaryRe = /[。!?…!?\.\」\』\)\】\」]/
let start = 0
const boundary = boundaryRe.exec(pageText)
if (boundary) {
start = boundary.index + boundary[0].length
while (start < pageText.length && /[\s\u3000]/.test(pageText[start])) start++
}
if (start >= pageText.length) start = 0
const rest = pageText.slice(start)
const sentenceEndRe = /[。!?…!?\.]/
let text = ''
const endMatch = sentenceEndRe.exec(rest)
if (endMatch) {
text = rest.slice(0, endMatch.index + endMatch[0].length).trim()
} else {
text = rest.trim().slice(0, 50)
}
if (text.length > 50) text = text.slice(0, 50) + '...'
const bookmark: Bookmark = {
id: `bm_${Date.now()}_${Math.random().toString(36).slice(2)}`,
chapterIndex: readerStore.currentChapterIndex,
charIndex: readerStore.currentPageSlice.startIndex,
readingPercent: readerStore.readingPercent,
text,
createdAt: Date.now()
}
const existing = book.bookmarks ?? []
bookStore.updateBook(book.id, { bookmarks: [...existing, bookmark] })
toast('书签已添加', 'success')
}
}
else if (command === 'notification-close') { pendingNotificationCallback?.(); pendingNotificationCallback = null }
}

Expand Down Expand Up @@ -708,6 +750,8 @@ watch(
hushreaderCfg.value.bgOpacity,
hushreaderCfg.value.prevPageKey,
hushreaderCfg.value.nextPageKey,
hushreaderCfg.value.addBookmarkKey,
hushreaderCfg.value.destroyKey,
hushreaderCfg.value.showHushreaderMeta,
hushreaderCfg.value.progressMode,
hushreaderCfg.value.hideOnMouseLeave,
Expand Down
Loading
Loading