Update plugin 隐阅盒 v1.3.0#266
Conversation
- feat: 初始化ZTools插件项目 - feat: 实现隐阅盒阅读器插件 - chore(gitignore): add ignore rules - chore: 相关标识重命名 - chore: add changelog - fix: 修复书架导入书籍异常未捕获、完善本地存储数据兼容逻辑 - docs: 更新README - docs: 重写README - feat: 发布1.1.0版本,新增多项功能并修复多个bug - fix(窗口拖动): 修复阅读窗口拖动拉伸卡顿问题 - chore: bump plugin version to 1.1.1 - chore: 重命名部分称呼 - feat(setting): add plain cover option, remove epub cache storage - feat: 新增MOBI电子书格式支持 - feat: 迁移封面和章节缓存到ztools.db - feat: 新增主题模式切换功能,优化深色主题样式 - chore: release v1.3.0, update plugin info and changelog
There was a problem hiding this comment.
Code Review
This pull request introduces support for the MOBI ebook format, including PalmDOC decompression, DRM detection, and cover extraction. It also migrates cover images and chapter content caching to a database (ztools.db) to improve performance and prevent storage overflow, and adds a theme toggle component for dark mode support. Feedback on these changes highlights several issues: MOBI covers are not saved during import, the theme toggle does not watch for configuration changes, and toggling the plain-text cover option accidentally deletes chapter caches. Additionally, the MOBI parser lacks boundary checks on record offsets, does not handle cases where firstImageIndex is 0xffffffff, and uses String.fromCharCode instead of String.fromCodePoint for HTML entity decoding.
Important
The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.
| title = result.title || title | ||
| author = result.author || '' |
There was a problem hiding this comment.
| <script setup lang="ts"> | ||
| import { computed } from 'vue' | ||
| import { useConfigStore } from '../../stores/config' | ||
|
|
||
| const configStore = useConfigStore() | ||
| const isDark = computed(() => configStore.config.other.theme === 'dark') | ||
|
|
||
| function toggle() { | ||
| configStore.config.other.theme = isDark.value ? 'light' : 'dark' | ||
| applyTheme() | ||
| configStore.save() | ||
| } | ||
|
|
||
| function applyTheme() { | ||
| document.documentElement.setAttribute('data-theme', configStore.config.other.theme) | ||
| } | ||
|
|
||
| applyTheme() |
There was a problem hiding this comment.
在组件初始化时,applyTheme() 会立即执行,但此时 configStore.load() 尚未在父组件中完成调用,因此获取到的是默认的 light 主题。此外,由于没有对 configStore.config.other.theme 进行监听,当配置从数据库加载完成后,主题也无法自动应用。建议引入 watch 并开启 immediate: true,这样既能保证初始应用,也能在配置加载或切换时自动更新 DOM 的 data-theme 属性。
<script setup lang="ts">
import { computed, watch } from 'vue'
import { useConfigStore } from '../../stores/config'
const configStore = useConfigStore()
const isDark = computed(() => configStore.config.other.theme === 'dark')
function toggle() {
configStore.config.other.theme = isDark.value ? 'light' : 'dark'
configStore.save()
}
watch(
() => configStore.config.other.theme,
(newTheme) => {
if (newTheme) {
document.documentElement.setAttribute('data-theme', newTheme)
}
},
{ immediate: true }
)
| b.customCoverImage = undefined | ||
| }) | ||
| } | ||
| await Promise.allSettled(bookStore.books.map(b => removeBookData(b.id))) |
| const firstRecordOffset = recordOffsets[0] | ||
| const palmDocHeader = parsePalmDocHeader(data, firstRecordOffset) | ||
| const mobiHeader = parseMobiHeader(data, firstRecordOffset) |
There was a problem hiding this comment.
在解析 MOBI 文件时,直接将 recordOffsets[0] 作为 firstRecordOffset 传入 parsePalmDocHeader,但未校验该偏移量加上头部大小后是否超出了文件总长度。如果文件损坏或偏移量异常,可能会导致越界读取。建议在解析前添加边界检查,确保文件长度足够。
| const firstRecordOffset = recordOffsets[0] | |
| const palmDocHeader = parsePalmDocHeader(data, firstRecordOffset) | |
| const mobiHeader = parseMobiHeader(data, firstRecordOffset) | |
| const firstRecordOffset = recordOffsets[0] | |
| if (firstRecordOffset + 16 > data.length) { | |
| return { title: '', author: '', chapters: [], error: 'MOBI文件格式损坏或记录偏移越界' } | |
| } | |
| const palmDocHeader = parsePalmDocHeader(data, firstRecordOffset) | |
| const mobiHeader = parseMobiHeader(data, firstRecordOffset) |
| const coverRecord = findExthRecord(exthRecords, 201) | ||
| if (!coverRecord) return undefined | ||
|
|
||
| const coverRecordIndex = firstImageIndex + exthRawToUint(coverRecord.raw) |
There was a problem hiding this comment.
如果 MOBI 文件中没有图片,firstImageIndex 的值通常为 0xffffffff(即 -1)。此时直接进行加法运算 firstImageIndex + exthRawToUint(...) 会导致计算出错误的索引。建议在提取封面时,先判断 firstImageIndex 是否为 0xffffffff,若是则直接返回 undefined。
| const coverRecord = findExthRecord(exthRecords, 201) | |
| if (!coverRecord) return undefined | |
| const coverRecordIndex = firstImageIndex + exthRawToUint(coverRecord.raw) | |
| const coverRecord = findExthRecord(exthRecords, 201) | |
| if (!coverRecord) return undefined | |
| if (firstImageIndex === 0xffffffff) return undefined | |
| const coverRecordIndex = firstImageIndex + exthRawToUint(coverRecord.raw) |
| .replace(/&#(\d+);/gi, (_, code) => String.fromCharCode(parseInt(code))) | ||
| .replace(/&#x([0-9a-fA-F]+);/gi, (_, hex) => String.fromCharCode(parseInt(hex, 16))) |
There was a problem hiding this comment.
在解码 HTML 实体字符时,使用 String.fromCharCode 无法正确处理 Unicode 码点大于 0xFFFF 的字符(例如部分 Emoji 或生僻字),会导致字符截断或乱码。建议使用 ES6 的 String.fromCodePoint 代替 String.fromCharCode。
| .replace(/&#(\d+);/gi, (_, code) => String.fromCharCode(parseInt(code))) | |
| .replace(/&#x([0-9a-fA-F]+);/gi, (_, hex) => String.fromCharCode(parseInt(hex, 16))) | |
| .replace(/&#(\\d+);/gi, (_, code) => String.fromCodePoint(parseInt(code, 10))) | |
| .replace(/&#x([0-9a-fA-F]+);/gi, (_, hex) => String.fromCodePoint(parseInt(hex, 16))) |
- feat: 初始化ZTools插件项目 - feat: 实现隐阅盒阅读器插件 - chore(gitignore): add ignore rules - chore: 相关标识重命名 - chore: add changelog - fix: 修复书架导入书籍异常未捕获、完善本地存储数据兼容逻辑 - docs: 更新README - docs: 重写README - feat: 发布1.1.0版本,新增多项功能并修复多个bug - fix(窗口拖动): 修复阅读窗口拖动拉伸卡顿问题 - chore: bump plugin version to 1.1.1 - chore: 重命名部分称呼 - feat(setting): add plain cover option, remove epub cache storage - feat: 新增MOBI电子书格式支持 - feat: 迁移封面和章节缓存到ztools.db - feat: 新增主题模式切换功能,优化深色主题样式 - chore: release v1.3.0, update plugin info and changelog - style: 优化窗口进度显示 - chore: 发布v1.3.1版本,更新功能与修复问题 - chore: bump plugin version to 1.3.1
- feat: 初始化ZTools插件项目 - feat: 实现隐阅盒阅读器插件 - chore(gitignore): add ignore rules - chore: 相关标识重命名 - chore: add changelog - fix: 修复书架导入书籍异常未捕获、完善本地存储数据兼容逻辑 - docs: 更新README - docs: 重写README - feat: 发布1.1.0版本,新增多项功能并修复多个bug - fix(窗口拖动): 修复阅读窗口拖动拉伸卡顿问题 - chore: bump plugin version to 1.1.1 - chore: 重命名部分称呼 - feat(setting): add plain cover option, remove epub cache storage - feat: 新增MOBI电子书格式支持 - feat: 迁移封面和章节缓存到ztools.db - feat: 新增主题模式切换功能,优化深色主题样式 - chore: release v1.3.0, update plugin info and changelog - style: 优化窗口进度显示 - chore: 发布v1.3.1版本,更新功能与修复问题 - chore: bump plugin version to 1.3.1 - fix: 修复多个已知问题并优化部分功能
- feat: 初始化ZTools插件项目 - feat: 实现隐阅盒阅读器插件 - chore(gitignore): add ignore rules - chore: 相关标识重命名 - chore: add changelog - fix: 修复书架导入书籍异常未捕获、完善本地存储数据兼容逻辑 - docs: 更新README - docs: 重写README - feat: 发布1.1.0版本,新增多项功能并修复多个bug - fix(窗口拖动): 修复阅读窗口拖动拉伸卡顿问题 - chore: bump plugin version to 1.1.1 - chore: 重命名部分称呼 - feat(setting): add plain cover option, remove epub cache storage - feat: 新增MOBI电子书格式支持 - feat: 迁移封面和章节缓存到ztools.db - feat: 新增主题模式切换功能,优化深色主题样式 - chore: release v1.3.0, update plugin info and changelog - style: 优化窗口进度显示 - chore: 发布v1.3.1版本,更新功能与修复问题 - chore: bump plugin version to 1.3.1 - fix: 修复多个已知问题并优化部分功能 - style(settings): 优化设置项的封面提示文案 - fix(bookshelf): 修复纯色封面关闭后MOBI封面未恢复的问题
插件信息
本次变更
1.3.1 - 2026-06-17
Added
Fixed
watch+immediate: true替代直接调用applyTheme(),确保配置异步加载后主题也能正确应用firstRecordOffset + 16 > data.length检查,防止格式损坏文件导致越界&#xxx;和&#xHH;解码改用String.fromCodePoint,parseInt 增加 radix 参数,支持 BMP 外字符resolveMobiCovers()函数,关闭纯色封面时同步恢复 MOBI 书籍封面(此前仅恢复 EPUB 封面)Removed
1.3.0 - 2026-06-17
Added
Changed
cover_{bookId}/custom_cover_{bookId},书架加载时从数据库恢复chapters_{bookId},打开书籍时优先从数据库加载,文件修改后自动重新解析并更新缓存截图 / 演示
自检清单
plugins/hushreader/目录此 PR 由 ztools-plugin-cli 自动管理:每次
ztools publish在分支上追加一个 commit,PR 链接保持不变。