Spiegel von @mana/shared-schemas/pageta@0.1.21 (Web = SOT): - ArticleMediaArchivedPayload + Reducer-Case (non-destruktiv, if-let wie warning); ArticleEnriched clobbert imageUrl nicht mehr - ArticleState.htmlContent; stateToArticle mappt es (war nil) - ArticleExtractPreview dekodiert imageUrl (og:image-Hotlink) - archiveMedia(): POST /articles/archive-media, fire-and-forget nach Save/Enrich; NUR die archivierte media.mana.how-URL wird emittiert, nie der Hotlink; Delete-Race-Guard wie im Web-Client - MediaURL.variant (thumb/medium/large, Spiegel von media-url.ts) - Lese-Liste: 56pt-Thumbnail; Reader-Header: Lead-Image (medium, 200pt) - 5 neue Reducer-/MediaURL-Tests (54/54 grün, iPhone-17-Simulator) Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
115 lines
3 KiB
Swift
115 lines
3 KiB
Swift
import Foundation
|
|
|
|
// Projected state types — Swift-Spiegel der TypeScript-State-Shapes in
|
|
// `apps/web/src/lib/event-sync-api.ts` und der Reducer in
|
|
// `mana/packages/shared-schemas/src/pageta/events/*.ts`.
|
|
//
|
|
// Diese Typen sind das Ergebnis des Reducer-Replays über alle Events
|
|
// eines Aggregates. Die UI sieht den Wire-Type `Article`/`Highlight`
|
|
// aus `Core/Domain/`; `EventSyncCoordinator` mappt State → Wire.
|
|
|
|
// MARK: - Article
|
|
|
|
/// Lese-Listen-Artikel als event-sourced State.
|
|
///
|
|
/// `content` enthält den Mozilla-Readability-Plain-Text aus dem
|
|
/// HTTP-`extract/preview`-Schritt, der weiterhin pageta-api lebt. Alle
|
|
/// User-Mutationen (Status/Progress/Favorite/Note/Delete) sind Events.
|
|
///
|
|
/// `urlHash` wird vom Native-Client als SHA-256-Hex der `originalUrl`
|
|
/// berechnet — matched dem Web-`urlHash`, damit Duplicate-Check über
|
|
/// alle Clients konsistent ist.
|
|
struct ArticleState: Codable, Equatable {
|
|
var id: String
|
|
var userId: String
|
|
var originalUrl: String
|
|
var urlHash: String
|
|
var title: String
|
|
var excerpt: String?
|
|
var content: String
|
|
/// Archiviertes Readability-HTML (img-srcs → media.mana.how) aus
|
|
/// `ArticleMediaArchived`. Native emittiert selbst nie HTML (der
|
|
/// Reader ist Plain-Text), spiegelt aber Web-Archive im State.
|
|
var htmlContent: String?
|
|
var author: String?
|
|
var siteName: String?
|
|
var imageUrl: String?
|
|
var wordCount: Int?
|
|
var readingTimeMinutes: Int?
|
|
var publishedAt: String?
|
|
var status: ArticleStatus
|
|
var readingProgress: Double
|
|
var isFavorite: Bool
|
|
var userNote: String?
|
|
var tags: [String]
|
|
var savedAt: String
|
|
var readAt: String?
|
|
var deletedAt: String?
|
|
/// Vom Server-Extract gesetzt (`"probable_consent_wall"` etc.).
|
|
/// Bleibt im State, damit das UI nach Replay den Banner zeigt.
|
|
var warning: String?
|
|
|
|
static func initial() -> ArticleState {
|
|
ArticleState(
|
|
id: "",
|
|
userId: "",
|
|
originalUrl: "",
|
|
urlHash: "",
|
|
title: "",
|
|
excerpt: nil,
|
|
content: "",
|
|
htmlContent: nil,
|
|
author: nil,
|
|
siteName: nil,
|
|
imageUrl: nil,
|
|
wordCount: nil,
|
|
readingTimeMinutes: nil,
|
|
publishedAt: nil,
|
|
status: .unread,
|
|
readingProgress: 0,
|
|
isFavorite: false,
|
|
userNote: nil,
|
|
tags: [],
|
|
savedAt: "",
|
|
readAt: nil,
|
|
deletedAt: nil,
|
|
warning: nil
|
|
)
|
|
}
|
|
}
|
|
|
|
// MARK: - Highlight
|
|
|
|
/// Highlight-State. Web-Client setzt echte Offsets, Native setzt 0/0
|
|
/// (Paragraph-Granularität). `text` ist führend für Anzeige + Lookup.
|
|
struct HighlightEventState: Codable, Equatable {
|
|
var id: String
|
|
var userId: String
|
|
var articleId: String
|
|
var text: String
|
|
var note: String?
|
|
var color: HighlightColor
|
|
var startOffset: Int
|
|
var endOffset: Int
|
|
var contextBefore: String?
|
|
var contextAfter: String?
|
|
var createdAt: String
|
|
var deletedAt: String?
|
|
|
|
static func initial() -> HighlightEventState {
|
|
HighlightEventState(
|
|
id: "",
|
|
userId: "",
|
|
articleId: "",
|
|
text: "",
|
|
note: nil,
|
|
color: .yellow,
|
|
startOffset: 0,
|
|
endOffset: 0,
|
|
contextBefore: nil,
|
|
contextAfter: nil,
|
|
createdAt: "",
|
|
deletedAt: nil
|
|
)
|
|
}
|
|
}
|