import SwiftUI struct AddBookmarkView: View { @Bindable var viewModel: BookmarksViewModel @Environment(\.dismiss) private var dismiss @State private var url = "" @State private var title = "" @State private var description = "" @State private var tagsText = "" @State private var importText = "" @State private var isSaving = false @State private var error: String? @State private var importMessage: String? var body: some View { NavigationStack { Form { Section { TextEditor(text: $importText) .paperField() .frame(minHeight: 96) .textInputAutocapitalization(.never) .autocorrectionDisabled() HStack { Button { extractImportText() } label: { Label("Extract Link", systemImage: "link.badge.plus") .font(PaperType.label) .foregroundStyle(Paper.accent) } .disabled(importText.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty) Spacer() if let importMessage { Text(importMessage) .font(PaperType.micro) .foregroundStyle(Paper.tertiary) } } } header: { Text("Import Text").font(PaperType.stamp).foregroundStyle(Paper.tertiary) } footer: { Text("Paste an email, newsletter, or message and Marks will pull out the first web link.").font(PaperType.micro).foregroundStyle(Paper.tertiary) } Section { TextField("https://", text: $url) .paperField() .keyboardType(.URL) .textInputAutocapitalization(.never) .autocorrectionDisabled() } header: { Text("URL").font(PaperType.stamp).foregroundStyle(Paper.tertiary) } Section { TextField("Optional", text: $title) .paperField() } header: { Text("Title").font(PaperType.stamp).foregroundStyle(Paper.tertiary) } Section { TextField("Optional", text: $description, axis: .vertical) .paperField() .lineLimit(2...5) } header: { Text("Notes").font(PaperType.stamp).foregroundStyle(Paper.tertiary) } Section { TextField("comma separated", text: $tagsText) .paperField() .textInputAutocapitalization(.never) .autocorrectionDisabled() } header: { Text("Tags").font(PaperType.stamp).foregroundStyle(Paper.tertiary) } footer: { Text("Separate tags with commas").font(PaperType.micro).foregroundStyle(Paper.tertiary) } if let error { Section { Text(error) .font(PaperType.meta) .foregroundStyle(Paper.alarm) } } } .listRowBackground(Paper.raised) .paperSurface() .navigationTitle("Add Bookmark") .navigationBarTitleDisplayMode(.inline) .toolbar { ToolbarItem(placement: .cancellationAction) { Button("Cancel") { dismiss() } } ToolbarItem(placement: .confirmationAction) { Button("Save") { save() } .font(PaperType.label) .tint(Paper.accent) .disabled(url.trimmingCharacters(in: .whitespaces).isEmpty || isSaving) .overlay { if isSaving { ProgressView().scaleEffect(0.7) } } } } } } private func extractImportText() { guard let payload = IngestPayloadParser.parse(item: importText, typeIdentifier: "public.plain-text") else { importMessage = "No link found" return } url = payload.url if let extractedTitle = payload.title, title.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty { title = extractedTitle } if description.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty { description = payload.notes } importMessage = "Link extracted" error = nil } private func save() { let urlString = url.normalizedURL guard URL(string: urlString) != nil else { error = "Invalid URL" return } let tags = tagsText.parsedTags isSaving = true error = nil Task { do { try await viewModel.addBookmark( url: urlString, title: title.trimmingCharacters(in: .whitespaces), description: description.trimmingCharacters(in: .whitespacesAndNewlines), tags: tags ) dismiss() } catch { self.error = error.localizedDescription isSaving = false } } } }