feat: 支持 EPUB 阅读并改善规则兼容与阅读稳定性

支持 EPUB 2/3 导入、目录、封面和章节插图,保留本地书管理与阅读进度。
修复 JSON 模板、动态替换与请求参数兼容,以及 RSS 翻页和连续滚动定位。
更新编辑器工具栏和回归测试,发布版本为 0.1.12(build 18)。
This commit is contained in:
Aminark
2026-10-24 00:58:11 +08:00
parent 3322fd71db
commit 67bc212ffd
58 changed files with 2331 additions and 244 deletions
+10 -4
View File
@@ -115,9 +115,9 @@ public struct ContentSanitizer: Sendable {
var diagnostics: [RuleDiagnostic] = []
for rule in ordered where rule.scopeTitle && applies(rule, to: book) {
let result = apply(rule, to: title)
let result = apply(rule, to: title, book: book, chapter: chapter)
diagnostics += result.diagnostics
if result.value != title {
if result.value != title, !result.value.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty {
title = result.value
effective.append(rule.id)
}
@@ -146,7 +146,7 @@ public struct ContentSanitizer: Sendable {
}
for rule in ordered where rule.scopeContent && applies(rule, to: book) {
let result = apply(rule, to: body)
let result = apply(rule, to: body, book: book, chapter: chapter)
diagnostics += result.diagnostics
if result.value != body {
body = result.value
@@ -172,10 +172,16 @@ public struct ContentSanitizer: Sendable {
return (!book.name.isEmpty && scope.contains(book.name)) || (!book.origin.isEmpty && scope.contains(book.origin))
}
private func apply(_ rule: ReplaceRule, to input: String) -> (value: String, diagnostics: [RuleDiagnostic]) {
private func apply(_ rule: ReplaceRule, to input: String, book: Book, chapter: BookChapter) -> (value: String, diagnostics: [RuleDiagnostic]) {
guard !rule.pattern.isEmpty else { return (input, []) }
do {
if rule.isRegex {
if rule.replacement.hasPrefix("@js:") {
let evaluate = JavaScriptRuleEvaluator().replacementEvaluator(
script: String(rule.replacement.dropFirst(4)), book: book, chapter: chapter)
return (try regex.replacingStrict(pattern: rule.pattern, in: input,
timeoutMilliseconds: rule.timeoutMillisecond, replacement: evaluate), [])
}
return (try regex.replacingStrict(pattern: rule.pattern, in: input, with: rule.replacement, timeoutMilliseconds: rule.timeoutMillisecond), [])
}
return (input.replacingOccurrences(of: rule.pattern, with: rule.replacement), [])