feat: 交互协调器拆分、附件提示、暗色图片适配、选区放大镜及文档清理

- 拆分 ContentDelegates/TextContentView 为独立协调器(InteractionCoordinator、LocationResolution、ExternalLinks、AttachmentTooltip)
- 新增 RDEPUBAttachmentTooltipView/OverlayView 附件气泡提示
- 新增 RDEPUBDarkImageAdjuster 暗色模式图片亮度适配
- 新增 RDEPUBSelectionLoupeView 选区放大镜
- 新增 MetadataParseWorker/CancellationController 元数据解析取消机制
- 重构 PresentationRuntime/PaginationCoordinator 精简职责
- 优化 ChapterLoader/WarmupOrchestrator 异步章节加载
- CFI 模块微调与 NoteModels 更新
- 清理冗余文档,更新架构/UML/业务逻辑文档

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
shenlei
2026-06-24 17:47:24 +08:00
co-authored by Claude
parent 7de661eb54
commit d15f20b097
59 changed files with 4522 additions and 7220 deletions
@@ -75,7 +75,7 @@ public enum RDEPUBCFIDOMPathBuilder {
match.numberOfRanges > 2 else {
return nil
}
return nsAttributes.substring(with: match.range(at: 2)).rd_cfiNilIfEmpty
return nsAttributes.substring(with: match.range(at: 2)).rd_nilIfEmpty
}
static func isIgnorableTag(_ tagName: String) -> Bool {
@@ -138,8 +138,8 @@ enum RDEPUBCFITextNodeMapBuilder {
markers: markers,
pathRanges: pathRanges,
recoveryMetadata: RDEPUBCFIRecoveryMetadata(
domFingerprint: rawHTML.sha256Hex,
normalizedTextChecksum: normalizedChapter.normalizedText.sha256Hex,
domFingerprint: rawHTML.rd_sha256Hex,
normalizedTextChecksum: normalizedChapter.normalizedText.rd_sha256Hex,
tokenIndex: makeTokenAnchors(
normalizedChapter: normalizedChapter,
markers: markers,
@@ -516,14 +516,6 @@ private struct RDEPUBNormalizedTextIndex {
}
}
extension String {
var rd_cfiNilIfEmpty: String? {
let trimmed = trimmingCharacters(in: .whitespacesAndNewlines)
return trimmed.isEmpty ? nil : trimmed
}
}
private extension String {
var fnv1a64: UInt64 {
let offsetBasis: UInt64 = 14695981039346656037
@@ -114,7 +114,7 @@ public enum RDEPUBCFIParser {
if let open = rawStep.firstIndex(of: "["),
let close = rawStep.lastIndex(of: "]"),
open < close {
idAssertion = String(rawStep[rawStep.index(after: open)..<close]).rd_cfiNilIfEmpty
idAssertion = String(rawStep[rawStep.index(after: open)..<close]).rd_nilIfEmpty
} else {
idAssertion = nil
}
@@ -34,6 +34,6 @@ public struct RDEPUBCFIStep: Codable, Equatable, Hashable {
public init(index: Int, idAssertion: String? = nil) {
self.index = index
self.idAssertion = idAssertion?.rd_cfiNilIfEmpty
self.idAssertion = idAssertion?.rd_nilIfEmpty
}
}
@@ -28,7 +28,7 @@ public enum RDEPUBCFIResolver {
if cfi.packagePath.steps.count >= 3 {
manifestStep = cfi.packagePath.steps[2]
} else {
manifestStep = cfi.packagePath.steps.last(where: { $0.idAssertion?.rd_cfiNilIfEmpty != nil })
manifestStep = cfi.packagePath.steps.last(where: { $0.idAssertion?.rd_nilIfEmpty != nil })
}
let href = manifestStep?.idAssertion
@@ -40,7 +40,7 @@ public enum RDEPUBCFIResolver {
fileIndex = nil
}
let fragmentID = cfi.contentPath.steps.last(where: { $0.idAssertion?.rd_cfiNilIfEmpty != nil })?.idAssertion
let fragmentID = cfi.contentPath.steps.last(where: { $0.idAssertion?.rd_nilIfEmpty != nil })?.idAssertion
return RDEPUBCFIResolverResult(
href: href,
fileIndex: fileIndex,