Files
linkding-ios/Marks/Intents/IntentSupport.swift
2026-07-02 01:11:03 -05:00

67 lines
2.4 KiB
Swift

import Foundation
import AppIntents
/// Which top-level tab the app is showing. Used so an intent can switch tabs.
enum AppTab: Hashable {
case bookmarks, tags, sources, podcasts, search
}
/// Bridges App Intents (which run in the main app process, since there is no
/// separate AppIntents extension target) to the live SwiftUI scene.
///
/// Intents mutate this shared, observable singleton; `MainContainer` and
/// `SearchView` observe it and react (present a browser, switch to the search
/// tab, etc.). This is the standard "intent in-app navigation" pattern when
/// perform() runs in-process.
@MainActor
@Observable
final class IntentRouter {
static let shared = IntentRouter()
private init() {}
/// A bookmark URL an intent asked to open in the in-app browser.
var openBookmarkURL: String?
/// A query an intent asked the app to search for.
var searchRequest: String?
func openBookmark(url: String) { openBookmarkURL = url }
func search(_ query: String) { searchRequest = query }
}
/// Errors surfaced to Siri / Shortcuts when an intent can't run.
enum MarksIntentError: Error, CustomLocalizedStringResourceConvertible {
case notConfigured
case invalidURL
var localizedStringResource: LocalizedStringResource {
switch self {
case .notConfigured: "Open Marks and connect to your Linkding server first."
case .invalidURL: "That doesn't look like a valid link."
}
}
}
/// Shared plumbing for the intents: build an API client from saved config and
/// keep the widgets in sync after mutations.
enum MarksIntent {
/// Builds a `LinkdingAPI` from the saved server config, or throws a
/// user-facing error if the app has never been connected.
static func api() throws -> LinkdingAPI {
guard let config = ServerConfig.load() else { throw MarksIntentError.notConfigured }
return LinkdingAPI(config: config)
}
/// Prepend a freshly-saved bookmark into the widget cache so the Recent
/// widget reflects it immediately (mirrors `BookmarksViewModel.load`).
static func refreshWidgets(adding b: Bookmark) {
var items = WidgetDataStore.loadBookmarks()
items.removeAll { $0.url == b.url }
items.insert(
WidgetBookmark(url: b.url, title: b.displayTitle, domain: b.domain, faviconUrl: b.faviconUrl),
at: 0
)
WidgetDataStore.saveBookmarks(Array(items.prefix(20)))
}
}