Add local source ingest and podcasts
This commit is contained in:
@@ -5,6 +5,8 @@ import SwiftUI
|
||||
/// read-later flag before saving (new) or updating (existing) — never a duplicate.
|
||||
struct ShareView: View {
|
||||
let url: String
|
||||
let initialTitle: String?
|
||||
let initialNotes: String
|
||||
/// Called when the extension should dismiss. `saved` is true after a successful
|
||||
/// write, false on cancel.
|
||||
let onClose: (_ saved: Bool) -> Void
|
||||
@@ -26,10 +28,14 @@ struct ShareView: View {
|
||||
|
||||
private let api: LinkdingAPI?
|
||||
|
||||
init(url: String, onClose: @escaping (_ saved: Bool) -> Void) {
|
||||
init(url: String, initialTitle: String? = nil, initialNotes: String = "", onClose: @escaping (_ saved: Bool) -> Void) {
|
||||
self.url = url
|
||||
self.initialTitle = initialTitle
|
||||
self.initialNotes = initialNotes
|
||||
self.onClose = onClose
|
||||
self.api = ServerConfig.loadShared().map(LinkdingAPI.init)
|
||||
_title = State(initialValue: initialTitle ?? "")
|
||||
_notes = State(initialValue: initialNotes)
|
||||
}
|
||||
|
||||
private enum Phase { case loading, editing, saving, done, failed }
|
||||
@@ -225,6 +231,14 @@ struct ShareView: View {
|
||||
// MARK: - Networking
|
||||
|
||||
private func check() async {
|
||||
guard let parsed = URL(string: url),
|
||||
let scheme = parsed.scheme?.lowercased(),
|
||||
scheme == "http" || scheme == "https",
|
||||
parsed.host?.isEmpty == false else {
|
||||
errorMessage = "No bookmarkable link found"
|
||||
phase = .failed
|
||||
return
|
||||
}
|
||||
guard let api else {
|
||||
errorMessage = "Open Marks first"
|
||||
phase = .failed
|
||||
@@ -239,8 +253,11 @@ struct ShareView: View {
|
||||
notes = bookmark.description
|
||||
readLater = bookmark.unread
|
||||
} else {
|
||||
title = result.metadata?.title ?? ""
|
||||
title = initialTitle ?? result.metadata?.title ?? ""
|
||||
suggestedTags = result.autoTags ?? []
|
||||
if notes.isEmpty {
|
||||
notes = result.metadata?.description ?? initialNotes
|
||||
}
|
||||
}
|
||||
// Pre-enable the toggle when the current tags match the auto-rule.
|
||||
createPodcast = PodcastRequests.matchesAutoTag(parsedTags)
|
||||
@@ -295,6 +312,7 @@ struct ShareView: View {
|
||||
let create = BookmarkCreate(
|
||||
url: url,
|
||||
title: "",
|
||||
description: notes,
|
||||
tagNames: parsedTags,
|
||||
isArchived: false,
|
||||
unread: readLater,
|
||||
|
||||
@@ -4,6 +4,13 @@ import UniformTypeIdentifiers
|
||||
|
||||
@objc(ShareViewController)
|
||||
class ShareViewController: UIViewController {
|
||||
private let supportedTypeIdentifiers = [
|
||||
UTType.url.identifier,
|
||||
UTType.html.identifier,
|
||||
UTType.rtf.identifier,
|
||||
UTType.plainText.identifier,
|
||||
UTType.text.identifier
|
||||
]
|
||||
|
||||
override func viewDidLoad() {
|
||||
super.viewDidLoad()
|
||||
@@ -11,37 +18,49 @@ class ShareViewController: UIViewController {
|
||||
blur.frame = view.bounds
|
||||
blur.autoresizingMask = [.flexibleWidth, .flexibleHeight]
|
||||
view.addSubview(blur)
|
||||
extractURL()
|
||||
extractPayload()
|
||||
}
|
||||
|
||||
private func extractURL() {
|
||||
private func extractPayload() {
|
||||
guard let item = extensionContext?.inputItems.first as? NSExtensionItem,
|
||||
let attachments = item.attachments else {
|
||||
present(url: nil)
|
||||
present(payload: nil)
|
||||
return
|
||||
}
|
||||
|
||||
func load(typeId: String) -> Bool {
|
||||
guard let attachment = attachments.first(where: { $0.hasItemConformingToTypeIdentifier(typeId) }) else {
|
||||
return false
|
||||
}
|
||||
attachment.loadItem(forTypeIdentifier: typeId, options: nil) { [weak self] item, _ in
|
||||
let urlString = typeId == UTType.url.identifier
|
||||
? ((item as? URL)?.absoluteString ?? (item as? String))
|
||||
: (item as? String)
|
||||
DispatchQueue.main.async { self?.present(url: urlString) }
|
||||
}
|
||||
return true
|
||||
let candidates = attachments.flatMap { attachment in
|
||||
supportedTypeIdentifiers
|
||||
.filter { attachment.hasItemConformingToTypeIdentifier($0) }
|
||||
.map { (attachment, $0) }
|
||||
}
|
||||
|
||||
if !load(typeId: UTType.url.identifier) && !load(typeId: UTType.plainText.identifier) {
|
||||
present(url: nil)
|
||||
loadNext(candidates, index: 0)
|
||||
}
|
||||
|
||||
private func loadNext(_ candidates: [(NSItemProvider, String)], index: Int) {
|
||||
guard index < candidates.count else {
|
||||
present(payload: nil)
|
||||
return
|
||||
}
|
||||
|
||||
let (attachment, typeIdentifier) = candidates[index]
|
||||
attachment.loadItem(forTypeIdentifier: typeIdentifier, options: nil) { [weak self] item, _ in
|
||||
guard let self else { return }
|
||||
if let payload = IngestPayloadParser.parse(item: item, typeIdentifier: typeIdentifier) {
|
||||
DispatchQueue.main.async { self.present(payload: payload) }
|
||||
} else {
|
||||
DispatchQueue.main.async { self.loadNext(candidates, index: index + 1) }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Hosts the SwiftUI save card. A nil URL surfaces an error state in the card.
|
||||
private func present(url: String?) {
|
||||
let root = ShareView(url: url ?? "") { [weak self] _ in
|
||||
private func present(payload: IngestPayload?) {
|
||||
let root = ShareView(
|
||||
url: payload?.url ?? "",
|
||||
initialTitle: payload?.title,
|
||||
initialNotes: payload?.notes ?? ""
|
||||
) { [weak self] _ in
|
||||
self?.close()
|
||||
}
|
||||
let host = UIHostingController(rootView: root)
|
||||
@@ -52,7 +71,7 @@ class ShareViewController: UIViewController {
|
||||
view.addSubview(host.view)
|
||||
host.didMove(toParent: self)
|
||||
|
||||
if url == nil {
|
||||
if payload == nil {
|
||||
// No URL to work with — let the card render its failure state briefly.
|
||||
DispatchQueue.main.asyncAfter(deadline: .now() + 1.6) { [weak self] in self?.close() }
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user