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:
@@ -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,
|
||||
|
||||
@@ -41,7 +41,7 @@ public struct RDEPUBNoteReference: Codable, Equatable {
|
||||
self.targetHref = targetHref
|
||||
self.targetFragment = targetFragment
|
||||
self.targetCFI = targetCFI
|
||||
self.label = label?.rdNoteNilIfEmpty
|
||||
self.label = label?.rd_nilIfEmpty
|
||||
self.kind = kind
|
||||
}
|
||||
}
|
||||
@@ -71,15 +71,8 @@ public struct RDEPUBResolvedNote: Equatable {
|
||||
self.reference = reference
|
||||
self.sourceLocation = sourceLocation
|
||||
self.targetLocation = targetLocation
|
||||
self.title = title?.rdNoteNilIfEmpty
|
||||
self.title = title?.rd_nilIfEmpty
|
||||
self.html = html
|
||||
self.plainText = plainText
|
||||
}
|
||||
}
|
||||
|
||||
private extension String {
|
||||
var rdNoteNilIfEmpty: String? {
|
||||
let trimmed = trimmingCharacters(in: .whitespacesAndNewlines)
|
||||
return trimmed.isEmpty ? nil : trimmed
|
||||
}
|
||||
}
|
||||
|
||||
@@ -261,7 +261,11 @@ public final class RDEPUBReadingSession {
|
||||
href: location.href,
|
||||
progression: location.progression,
|
||||
lastProgression: location.lastProgression,
|
||||
fragment: location.fragment
|
||||
fragment: location.fragment,
|
||||
rangeAnchor: location.rangeAnchor,
|
||||
cfi: location.cfi,
|
||||
lastCFI: location.lastCFI,
|
||||
rangeCFI: location.rangeCFI
|
||||
)
|
||||
|
||||
let resource = RDEPUBViewportResource(
|
||||
|
||||
@@ -96,15 +96,3 @@ extension RDEPUBWebView {
|
||||
currentLoadSignature = nil
|
||||
}
|
||||
}
|
||||
|
||||
extension UIColor {
|
||||
|
||||
var ss_hexString: String {
|
||||
var red: CGFloat = 0
|
||||
var green: CGFloat = 0
|
||||
var blue: CGFloat = 0
|
||||
var alpha: CGFloat = 0
|
||||
getRed(&red, green: &green, blue: &blue, alpha: &alpha)
|
||||
return String(format: "#%02X%02X%02X", Int(red * 255), Int(green * 255), Int(blue * 255))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,7 +16,7 @@ extension RDEPUBWebView {
|
||||
spread: spread,
|
||||
viewportSize: viewportSize,
|
||||
contentInset: contentInset,
|
||||
backgroundColorCSS: backgroundColor?.ss_hexString,
|
||||
backgroundColorCSS: backgroundColor?.rd_hexString,
|
||||
fit: .page,
|
||||
searchPresentation: nil
|
||||
)
|
||||
@@ -50,7 +50,7 @@ extension RDEPUBWebView {
|
||||
RDEPUBWebViewDebug.log(debugScope, message: "load fixed spread resources=\(request.spread.resources.map(\.href).joined(separator: ",")) fit=\(request.fit.rawValue)")
|
||||
webView.loadHTMLString(
|
||||
html,
|
||||
baseURL: URL(string: "\(RDEPUBResourceURLSchemeHandler.scheme):///")
|
||||
baseURL: URL(string: "\(RDEPUBResourceURLSchemeHandler.scheme)://\(RDEPUBResourceURLSchemeHandler.host)/")
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user