import SwiftUI import DSKit struct DocumentListView: View { @Environment(AppModel.self) private var model var body: some View { let selection = Binding( get: { model.selectedDocumentID }, set: { if let id = $0 { Task { await model.openDocument(id) } } } ) List(model.documentList, selection: selection) { doc in DocumentRow(doc: doc) } .listStyle(.inset) .background(Sage.surface) } } struct DocumentRow: View { let doc: DocumentResponse var body: some View { VStack(alignment: .leading, spacing: 4) { HStack(spacing: 6) { Chip(doc.displayFormat.uppercased(), Sage.formatColor(doc.displayFormat)) Text(doc.title ?? doc.downloadLabel) .font(.callout.weight(.medium)).foregroundStyle(Sage.ink).lineLimit(1) Spacer() if doc.pinned == true { Text("고정").font(.caption2).foregroundStyle(Sage.amber) } } HStack(spacing: 6) { if let d = doc.aiDomain { Chip(d, Sage.domainColor(d)) } if let r = doc.reviewStatus { Text(r).font(.caption2).foregroundStyle(Sage.reviewStatusColor(r)) } Spacer() Text(doc.updatedAtRaw.prefix(10)).font(.caption2.monospacedDigit()).foregroundStyle(Sage.muted) } } .padding(.vertical, 4) } } /// MD-first detail: render md_content when renderable, else extracted_text fallback + 'MD 변환 대기' /// badge + emphasized original-download button. (Download builds a real-shaped ?token= URL.) struct DocumentDetailView: View { @Environment(AppModel.self) private var model let detail: DocumentDetailResponse var body: some View { ScrollView { VStack(alignment: .leading, spacing: 14) { Text(detail.base.title ?? detail.base.downloadLabel) .font(.title2.weight(.bold)).foregroundStyle(Sage.ink) HStack(spacing: 8) { if let d = detail.base.aiDomain { Chip(d, Sage.domainColor(d)) } Chip(detail.base.displayFormat.uppercased(), Sage.formatColor(detail.base.displayFormat)) if let conf = detail.base.aiConfidence { Chip("AI \(String(format: "%.0f%%", conf * 100))", Sage.muted) } Spacer() if let url = model.downloadURL(for: detail.base) { Link(detail.base.downloadLabel, destination: url).font(.callout.weight(.semibold)) } } if let tags = detail.base.aiTags, !tags.isEmpty { HStack(spacing: 6) { ForEach(tags, id: \.self) { Chip($0, Sage.brand) } } } Divider() if detail.mdIsRenderable, let md = detail.mdContent { MarkdownView(md) } else { HStack { Chip("MD 변환 대기", Sage.amber); Spacer() } Text(detail.extractedText ?? "본문 없음") .font(.body).foregroundStyle(Sage.muted) .frame(maxWidth: .infinity, alignment: .leading) if let url = model.downloadURL(for: detail.base) { Link("원본 다운로드 — \(detail.base.downloadLabel)", destination: url) .font(.callout.weight(.semibold)) } } } .padding(20) } .background(Sage.surface) } }