mirror of
https://github.com/wahyd4/links.git
synced 2026-08-09 05:06:16 +10:00
310 lines
9.4 KiB
Swift
310 lines
9.4 KiB
Swift
import Foundation
|
|
import CoreData
|
|
import Combine
|
|
import CoreSpotlight
|
|
|
|
class LinkViewModel: ObservableObject {
|
|
@Published var links: [Link] = []
|
|
@Published var filteredLinks: [Link] = []
|
|
@Published var searchText: String = "" {
|
|
didSet {
|
|
filterLinks()
|
|
}
|
|
}
|
|
@Published var selectedSortOption: SortOption = .alias {
|
|
didSet {
|
|
sortLinks()
|
|
}
|
|
}
|
|
@Published var sortAscending: Bool = true {
|
|
didSet { sortLinks() }
|
|
}
|
|
@Published var selectedTag: Tag? {
|
|
didSet {
|
|
filterLinks()
|
|
}
|
|
}
|
|
@Published var isLoading: Bool = false
|
|
@Published var errorMessage: String?
|
|
|
|
private var cancellables = Set<AnyCancellable>()
|
|
private let persistenceController = PersistenceController.shared
|
|
|
|
enum SortOption: String, CaseIterable {
|
|
case alias = "alias"
|
|
case type = "linkType"
|
|
case clicks = "clickCount"
|
|
case dateCreated = "createdAt"
|
|
case dateUpdated = "updatedAt"
|
|
|
|
var displayName: String {
|
|
switch self {
|
|
case .alias:
|
|
return NSLocalizedString("alias", comment: "Sort by alias")
|
|
case .type:
|
|
return NSLocalizedString("type", comment: "Sort by type")
|
|
case .clicks:
|
|
return NSLocalizedString("clicks", comment: "Sort by clicks")
|
|
case .dateCreated:
|
|
return NSLocalizedString("date_created", comment: "Sort by date created")
|
|
case .dateUpdated:
|
|
return NSLocalizedString("date_updated", comment: "Sort by date updated")
|
|
}
|
|
}
|
|
}
|
|
|
|
init() {
|
|
fetchLinks()
|
|
initializeSpotlightSearch()
|
|
}
|
|
|
|
private func initializeSpotlightSearch() {
|
|
// Initialize Spotlight search indexing when the app starts
|
|
DispatchQueue.global(qos: .background).async {
|
|
SpotlightSearchManager.shared.initializeSpotlightIndexing()
|
|
}
|
|
}
|
|
|
|
/// Manually refresh Spotlight search index
|
|
func refreshSpotlightIndex() {
|
|
DispatchQueue.global(qos: .background).async {
|
|
SpotlightSearchManager.shared.initializeSpotlightIndexing()
|
|
}
|
|
}
|
|
|
|
/// Check Spotlight search status for debugging
|
|
func checkSpotlightStatus(completion: @escaping (Int) -> Void) {
|
|
SpotlightSearchManager.shared.getIndexedItemsStatus(completion: completion)
|
|
}
|
|
|
|
func fetchLinks() {
|
|
isLoading = true
|
|
let context = persistenceController.container.viewContext
|
|
let request: NSFetchRequest<Link> = Link.fetchRequest()
|
|
request.sortDescriptors = [NSSortDescriptor(keyPath: \Link.alias, ascending: true)]
|
|
|
|
do {
|
|
links = try context.fetch(request)
|
|
filterLinks()
|
|
isLoading = false
|
|
} catch {
|
|
errorMessage = error.localizedDescription
|
|
isLoading = false
|
|
}
|
|
}
|
|
|
|
func createLink(alias: String, originalURL: String?, text: String?, linkType: LinkType, description: String?, tags: [Tag]) -> Bool {
|
|
let context = persistenceController.container.viewContext
|
|
|
|
// Check if alias already exists
|
|
if aliasExists(alias) {
|
|
errorMessage = NSLocalizedString("alias_already_exists", comment: "Alias already exists")
|
|
return false
|
|
}
|
|
|
|
let newLink = Link(context: context)
|
|
newLink.alias = alias
|
|
newLink.originalURL = originalURL
|
|
newLink.text = text
|
|
newLink.linkType = linkType.rawValue
|
|
newLink.linkDescription = description
|
|
newLink.clickCount = 0
|
|
newLink.createdAt = Date()
|
|
newLink.updatedAt = Date()
|
|
|
|
// Add tags
|
|
for tag in tags {
|
|
newLink.addToTags(tag)
|
|
}
|
|
|
|
// Create change log
|
|
let changeLog = ChangeLog(context: context)
|
|
changeLog.changeType = "CREATE"
|
|
changeLog.changeDescription = "Link created"
|
|
changeLog.changedAt = Date()
|
|
changeLog.link = newLink
|
|
|
|
do {
|
|
try context.save()
|
|
|
|
// Index the new link in Spotlight search
|
|
SpotlightSearchManager.shared.indexLink(newLink)
|
|
|
|
fetchLinks()
|
|
return true
|
|
} catch {
|
|
errorMessage = error.localizedDescription
|
|
return false
|
|
}
|
|
}
|
|
|
|
func updateLink(_ link: Link, alias: String, originalURL: String?, text: String?, linkType: LinkType, description: String?, tags: [Tag]) -> Bool {
|
|
let context = persistenceController.container.viewContext
|
|
|
|
// Check if alias already exists (excluding current link)
|
|
if alias != link.alias && aliasExists(alias) {
|
|
errorMessage = NSLocalizedString("alias_already_exists", comment: "Alias already exists")
|
|
return false
|
|
}
|
|
|
|
link.alias = alias
|
|
link.originalURL = originalURL
|
|
link.text = text
|
|
link.linkType = linkType.rawValue
|
|
link.linkDescription = description
|
|
link.updatedAt = Date()
|
|
|
|
// Update tags
|
|
link.removeFromTags(link.tags ?? NSSet())
|
|
for tag in tags {
|
|
link.addToTags(tag)
|
|
}
|
|
|
|
// Create change log
|
|
let changeLog = ChangeLog(context: context)
|
|
changeLog.changeType = "UPDATE"
|
|
changeLog.changeDescription = "Link updated"
|
|
changeLog.changedAt = Date()
|
|
changeLog.link = link
|
|
|
|
do {
|
|
try context.save()
|
|
|
|
// Re-index the updated link in Spotlight search
|
|
SpotlightSearchManager.shared.indexLink(link)
|
|
|
|
fetchLinks()
|
|
return true
|
|
} catch {
|
|
errorMessage = error.localizedDescription
|
|
return false
|
|
}
|
|
}
|
|
|
|
func deleteLink(_ link: Link) {
|
|
let context = persistenceController.container.viewContext
|
|
let aliasToRemove = link.alias
|
|
|
|
context.delete(link)
|
|
|
|
do {
|
|
try context.save()
|
|
|
|
// Remove the link from Spotlight search index
|
|
SpotlightSearchManager.shared.removeLink(withAlias: aliasToRemove)
|
|
|
|
fetchLinks()
|
|
} catch {
|
|
errorMessage = error.localizedDescription
|
|
}
|
|
}
|
|
|
|
func recordClick(for link: Link) {
|
|
let context = persistenceController.container.viewContext
|
|
|
|
// Create click log
|
|
let clickLog = ClickLog(context: context)
|
|
clickLog.clickedAt = Date()
|
|
clickLog.link = link
|
|
|
|
// Increment click count
|
|
link.clickCount += 1
|
|
|
|
do {
|
|
try context.save()
|
|
fetchLinks()
|
|
} catch {
|
|
errorMessage = error.localizedDescription
|
|
}
|
|
}
|
|
|
|
func aliasExists(_ alias: String) -> Bool {
|
|
let context = persistenceController.container.viewContext
|
|
let request: NSFetchRequest<Link> = Link.fetchRequest()
|
|
request.predicate = NSPredicate(format: "alias == %@", alias)
|
|
request.fetchLimit = 1
|
|
|
|
do {
|
|
let count = try context.count(for: request)
|
|
return count > 0
|
|
} catch {
|
|
return false
|
|
}
|
|
}
|
|
|
|
private func filterLinks() {
|
|
var filtered = links
|
|
|
|
// Filter by search text (alias only for Quick Go)
|
|
if !searchText.isEmpty {
|
|
filtered = filtered.filter { link in
|
|
link.alias.localizedCaseInsensitiveContains(searchText)
|
|
}
|
|
}
|
|
|
|
// Filter by selected tag
|
|
if let selectedTag = selectedTag {
|
|
filtered = filtered.filter { link in
|
|
link.sortedTags.contains(selectedTag)
|
|
}
|
|
}
|
|
|
|
filteredLinks = filtered
|
|
sortLinks()
|
|
}
|
|
|
|
private func sortLinks() {
|
|
let ascending = sortAscending
|
|
switch selectedSortOption {
|
|
case .alias:
|
|
filteredLinks.sort { ascending ? ($0.alias < $1.alias) : ($0.alias > $1.alias) }
|
|
case .type:
|
|
filteredLinks.sort { ascending ? ($0.linkType < $1.linkType) : ($0.linkType > $1.linkType) }
|
|
case .clicks:
|
|
filteredLinks.sort { ascending ? ($0.clickCount < $1.clickCount) : ($0.clickCount > $1.clickCount) }
|
|
case .dateCreated:
|
|
filteredLinks.sort { ascending ? ($0.createdAt < $1.createdAt) : ($0.createdAt > $1.createdAt) }
|
|
case .dateUpdated:
|
|
filteredLinks.sort { ascending ? ($0.updatedAt < $1.updatedAt) : ($0.updatedAt > $1.updatedAt) }
|
|
}
|
|
}
|
|
|
|
func openLink(_ link: Link, with parameters: [String: String] = [:]) {
|
|
recordClick(for: link)
|
|
|
|
guard let urlString = link.originalURL else { return }
|
|
|
|
var processedURL = urlString
|
|
|
|
// Process template parameters
|
|
if link.isTemplate {
|
|
processedURL = URLTemplateProcessor.processTemplate(urlString, with: parameters)
|
|
}
|
|
|
|
guard let url = URL(string: processedURL) else { return }
|
|
|
|
if UIApplication.shared.canOpenURL(url) {
|
|
UIApplication.shared.open(url)
|
|
}
|
|
}
|
|
}
|
|
|
|
// MARK: - Search functionality
|
|
extension LinkViewModel {
|
|
func performQuickSearch(_ query: String) {
|
|
searchText = query
|
|
|
|
// If user presses enter with search text and no results, open as URL
|
|
if filteredLinks.isEmpty && !query.isEmpty {
|
|
if let url = URL(string: query.hasPrefix("http") ? query : "https://\(query)") {
|
|
if UIApplication.shared.canOpenURL(url) {
|
|
UIApplication.shared.open(url)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Import UIKit for UIApplication
|
|
import UIKit
|