feat(share): dup-aware interactive save card with AI tag suggestions
All checks were successful
CI / build-and-deploy (push) Successful in 20s
All checks were successful
CI / build-and-deploy (push) Successful in 20s
Replace the fire-and-forget share toast with an interactive SwiftUI card. On open it checks linkding's /api/bookmarks/check/ so an already-saved URL routes to an Update (no more silent duplicates) and a new URL shows a save form with title, tags, notes, and a read-later toggle. Tag suggestions are hybrid: fetch the user's existing tag vocabulary (/api/tags/) and AI tag ideas (/v1/marks/enrich via anonymous device auth), auto-fill known-vocabulary matches into the field and surface net-new ideas as tappable chips. Best-effort and non-blocking — failures never block save. Also: - Refresh bookmarks when the app returns to the foreground (scenePhase). - Render bookmark dates as a static relative label instead of the live ticking RelativeDateTime timer. - Share LinkdingAPI/Bookmark/ServerConfig/MarksAuth into the ShareExtension target via project.yml (durable across xcodegen regen) and regenerate. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -78,6 +78,28 @@ actor LinkdingAPI {
|
||||
return try decoder.decode(BookmarkResponse.self, from: data)
|
||||
}
|
||||
|
||||
/// Checks whether `url` is already bookmarked. Returns the existing bookmark
|
||||
/// (or nil), scraped metadata, and suggested tags for a fresh save.
|
||||
func checkBookmark(url urlString: String) async throws -> BookmarkCheck {
|
||||
let url = try makeUrl(path: "/api/bookmarks/check/", queryItems: [.init(name: "url", value: urlString)])
|
||||
let (data, response) = try await session.data(for: authorizedRequest(url: url))
|
||||
guard (response as? HTTPURLResponse)?.statusCode == 200 else {
|
||||
throw APIError.badStatus((response as? HTTPURLResponse)?.statusCode ?? 0)
|
||||
}
|
||||
return try decoder.decode(BookmarkCheck.self, from: data)
|
||||
}
|
||||
|
||||
/// All tag names the user already uses — the existing vocabulary we bias
|
||||
/// AI tag suggestions toward.
|
||||
func fetchTags(limit: Int = 1000) async throws -> [String] {
|
||||
let url = try makeUrl(path: "/api/tags/", queryItems: [.init(name: "limit", value: "\(limit)")])
|
||||
let (data, response) = try await session.data(for: authorizedRequest(url: url))
|
||||
guard (response as? HTTPURLResponse)?.statusCode == 200 else {
|
||||
throw APIError.badStatus((response as? HTTPURLResponse)?.statusCode ?? 0)
|
||||
}
|
||||
return try decoder.decode(TagResponse.self, from: data).results.map(\.name)
|
||||
}
|
||||
|
||||
func createBookmark(_ create: BookmarkCreate) async throws -> Bookmark {
|
||||
let body = try JSONEncoder().encode(create)
|
||||
let url = try makeUrl(path: "/api/bookmarks/")
|
||||
|
||||
Reference in New Issue
Block a user