├── .DS_Store ├── images ├── OG.png ├── .DS_Store └── Download on the App Store Badge.png ├── Linkeeper ├── .DS_Store ├── CoreData │ ├── .DS_Store │ ├── Folder+CoreDataClass.swift │ ├── Bookmark+CoreDataClass.swift │ ├── StoreURL Extension.swift │ ├── Linkeeper.xcdatamodeld │ │ └── Linkeeper.xcdatamodel │ │ │ └── contents │ ├── Bookmark+CoreDataProperties.swift │ ├── Folder+CoreDataProperties.swift │ ├── BookmarksManager.swift │ └── FoldersManager.swift ├── Helpers │ ├── .DS_Store │ ├── SharedUserDefaults.swift │ ├── Extensions │ │ ├── Notifications.swift │ │ ├── Bool.swift │ │ ├── Shape.swift │ │ ├── URL.swift │ │ ├── String.swift │ │ ├── Color.swift │ │ ├── TextEditor.swift │ │ ├── Bookmark.swift │ │ └── View.swift │ ├── Global │ │ ├── isMac.swift │ │ ├── isVisionOS.swift │ │ └── reloadAllWidgets.swift │ ├── Enums │ │ ├── SortMethod.swift │ │ ├── EditState.swift │ │ ├── SortDirection.swift │ │ ├── ViewOption.swift │ │ └── ColorOption.swift │ ├── Drag & Dropping │ │ ├── FolderDropItem.swift │ │ ├── DraggableFolder.swift │ │ ├── BookmarkDropItem.swift │ │ ├── DraggableBookmark.swift │ │ └── DragExtension.swift │ ├── AlertView.swift │ ├── macOS Bridge.swift │ ├── AppConfiguration.swift │ ├── CommandKeyObserver.swift │ ├── QuickActionsHandler.swift │ ├── Functions │ │ └── Metadata Fetcher.swift │ ├── ModernLabel.swift │ ├── ShareButton.swift │ ├── Flashcard.swift │ ├── Shapes │ │ └── YouTube.swift │ ├── FolderItemView.swift │ ├── FolderPickerView.swift │ └── ListItem.swift ├── Assets.xcassets │ ├── .DS_Store │ ├── Contents.json │ ├── Om.imageset │ │ ├── Om.jpg │ │ ├── .DS_Store │ │ └── Contents.json │ ├── AppIcon.appiconset │ │ ├── 40.png │ │ ├── 58.png │ │ ├── 60.png │ │ ├── 76.png │ │ ├── 80.png │ │ ├── 87.png │ │ ├── 1024.png │ │ ├── 114 1.png │ │ ├── 114 2.png │ │ ├── 114.png │ │ ├── 120 1.png │ │ ├── 120 2.png │ │ ├── 120 3.png │ │ ├── 120 4.png │ │ ├── 120 5.png │ │ ├── 120.png │ │ ├── 128 1.png │ │ ├── 128.png │ │ ├── 136 1.png │ │ ├── 136.png │ │ ├── 152 1.png │ │ ├── 152.png │ │ ├── 167 1.png │ │ ├── 167.png │ │ ├── 180 1.png │ │ ├── 180 2.png │ │ ├── 180.png │ │ ├── 192 1.png │ │ ├── 192.png │ │ ├── 40 1.png │ │ ├── 40 2.png │ │ ├── 58 1.png │ │ ├── 58 2.png │ │ ├── 60 1.png │ │ ├── 60 2.png │ │ ├── 76 1.png │ │ ├── 80 1.png │ │ ├── 80 2.png │ │ ├── 87 1.png │ │ ├── 87 2.png │ │ ├── macOS 1024.png │ │ ├── macOS 128.png │ │ ├── macOS 16.png │ │ ├── macOS 256.png │ │ ├── macOS 257.png │ │ ├── macOS 32.png │ │ ├── macOS 33.png │ │ ├── macOS 512.png │ │ ├── macOS 513.png │ │ ├── macOS 65.png │ │ ├── Frame 3-8 76.png │ │ ├── Frame 3-8 128.png │ │ ├── Frame 3-8 136.png │ │ ├── Frame 3-8 152.png │ │ ├── Frame 3-8 167.png │ │ ├── Frame 3-8 192.png │ │ ├── Icon-Dark-1024x1024.png │ │ └── Icon-Tinted-1024x1024.png │ ├── DarkIcon.appiconset │ │ ├── 1024.png │ │ ├── 114.png │ │ ├── 120.png │ │ ├── 180.png │ │ ├── 40.png │ │ ├── 58.png │ │ ├── 60.png │ │ ├── 80.png │ │ ├── 87.png │ │ ├── 120 1.png │ │ ├── Frame 3-7 128.png │ │ ├── Frame 3-7 136.png │ │ ├── Frame 3-7 152.png │ │ ├── Frame 3-7 167.png │ │ ├── Frame 3-7 192.png │ │ ├── Frame 3-7 76.png │ │ └── Contents.json │ ├── ClassicIcon.appiconset │ │ ├── 114.png │ │ ├── 120.png │ │ ├── 128.png │ │ ├── 136.png │ │ ├── 152.png │ │ ├── 167.png │ │ ├── 180.png │ │ ├── 192.png │ │ ├── 40.png │ │ ├── 58.png │ │ ├── 60.png │ │ ├── 76.png │ │ ├── 80.png │ │ ├── 87.png │ │ ├── 1024.png │ │ ├── 114 1.png │ │ ├── 114 2.png │ │ ├── 120 1.png │ │ ├── 120 2.png │ │ ├── 120 3.png │ │ ├── 120 4.png │ │ ├── 120 5.png │ │ ├── 128 1.png │ │ ├── 136 1.png │ │ ├── 152 1.png │ │ ├── 167 1.png │ │ ├── 180 1.png │ │ ├── 180 2.png │ │ ├── 192 1.png │ │ ├── 40 1.png │ │ ├── 40 2.png │ │ ├── 58 1.png │ │ ├── 58 2.png │ │ ├── 60 1.png │ │ ├── 60 2.png │ │ ├── 76 1.png │ │ ├── 80 1.png │ │ ├── 80 2.png │ │ ├── 87 1.png │ │ ├── 87 2.png │ │ ├── macOS 16.png │ │ ├── macOS 32.png │ │ ├── macOS 33.png │ │ ├── macOS 65.png │ │ ├── macOS 1024.png │ │ ├── macOS 128.png │ │ ├── macOS 256.png │ │ ├── macOS 257.png │ │ ├── macOS 512.png │ │ ├── macOS 513.png │ │ ├── Frame 3-8 128.png │ │ ├── Frame 3-8 136.png │ │ ├── Frame 3-8 152.png │ │ ├── Frame 3-8 167.png │ │ ├── Frame 3-8 192.png │ │ ├── Frame 3-8 76.png │ │ ├── Icon-Dark-1024x1024.png │ │ └── Icon-Tinted-1024x1024.png │ ├── ClassicIconImage.imageset │ │ ├── 512.png │ │ ├── 1024.png │ │ └── Contents.json │ ├── AppIcon.solidimagestack │ │ ├── Back.solidimagestacklayer │ │ │ ├── Contents.json │ │ │ └── Content.imageset │ │ │ │ ├── Frame 3.png │ │ │ │ └── Contents.json │ │ ├── Front.solidimagestacklayer │ │ │ ├── Contents.json │ │ │ └── Content.imageset │ │ │ │ ├── Center.png │ │ │ │ └── Contents.json │ │ ├── Middle.solidimagestacklayer │ │ │ ├── Contents.json │ │ │ └── Content.imageset │ │ │ │ └── Contents.json │ │ └── Contents.json │ ├── DarkIcon.solidimagestack │ │ ├── Back.solidimagestacklayer │ │ │ ├── Contents.json │ │ │ └── Content.imageset │ │ │ │ ├── Frame 3-17.jpg │ │ │ │ └── Contents.json │ │ ├── Front.solidimagestacklayer │ │ │ ├── Contents.json │ │ │ └── Content.imageset │ │ │ │ ├── Frame 3-13.png │ │ │ │ └── Contents.json │ │ ├── Middle.solidimagestacklayer │ │ │ ├── Contents.json │ │ │ └── Content.imageset │ │ │ │ └── Contents.json │ │ └── Contents.json │ ├── ClassicIcon.solidimagestack │ │ ├── Back.solidimagestacklayer │ │ │ ├── Contents.json │ │ │ └── Content.imageset │ │ │ │ ├── Frame 3.png │ │ │ │ └── Contents.json │ │ ├── Front.solidimagestacklayer │ │ │ ├── Contents.json │ │ │ └── Content.imageset │ │ │ │ ├── Center.png │ │ │ │ └── Contents.json │ │ ├── Middle.solidimagestacklayer │ │ │ ├── Contents.json │ │ │ └── Content.imageset │ │ │ │ └── Contents.json │ │ └── Contents.json │ ├── DarkIconImage.imageset │ │ ├── Frame 3-7 1.png │ │ ├── Frame 3-7.png │ │ └── Contents.json │ ├── AccentColor.colorset │ │ └── Contents.json │ ├── ShortcutIconForeground.colorset │ │ └── Contents.json │ ├── primaryInverted.colorset │ │ └── Contents.json │ ├── AllBookmarksColor.colorset │ │ └── Contents.json │ ├── DetailsEditBarColor.colorset │ │ └── Contents.json │ ├── GridItemBackground.colorset │ │ └── Contents.json │ ├── ShortcutBackground1.colorset │ │ └── Contents.json │ └── ShortcutBackground2.colorset │ │ └── Contents.json ├── Preview Content │ └── Preview Assets.xcassets │ │ └── Contents.json ├── StoreKit │ ├── Tips.plist │ ├── Local Tips.storekit │ └── Synced Tips.storekit ├── App Intents │ ├── Apple Intelligence Domains │ │ ├── DeleteBookmarks.swift │ │ └── CreateBookmark.swift │ ├── AppShortcuts Provider.swift │ ├── Folders │ │ └── DeleteFolder.swift │ └── Bookmarks │ │ └── FavoriteToggle.swift ├── Linkeeper.entitlements ├── AppDelegate (Mac).swift ├── AppDelegate (iOS, visionOS).swift ├── Bookmarks View │ ├── Table View │ │ ├── Column.swift │ │ └── BookmarkTableNameView.swift │ ├── Grid View │ │ └── FolderGridItem.swift │ └── List View │ │ └── BookmarkListItem.swift ├── Info.plist ├── BookmarkInfoViews │ └── ChangeIconsView.swift ├── LinkeeperApp.swift └── TipRequestView.swift ├── BookmarksWidget ├── .DS_Store ├── Assets.xcassets │ ├── Contents.json │ ├── AccentColor.colorset │ │ └── Contents.json │ ├── WidgetBackground.colorset │ │ └── Contents.json │ └── AppIcon.appiconset │ │ └── Contents.json ├── BookmarksWidgetBundle.swift ├── BookmarksEntry.swift ├── Info.plist ├── ConfigurationAppIntent.swift ├── BookmarksWidgetExtension.entitlements ├── WidgetProvider.swift ├── BookmarksWidgetView.swift ├── BookmarkItemView.swift ├── MediumOrLargeWidgetView.swift └── SmallWidgetView.swift ├── Share Extension Mac ├── .DS_Store ├── icon.icns ├── Share_Extension_Mac.entitlements └── Info.plist ├── Linkeeper.xcodeproj ├── project.xcworkspace │ ├── contents.xcworkspacedata │ ├── xcuserdata │ │ └── omchachad.xcuserdatad │ │ │ ├── UserInterfaceState.xcuserstate │ │ │ ├── IDEFindNavigatorScopes.plist │ │ │ └── WorkspaceSettings.xcsettings │ └── xcshareddata │ │ ├── WorkspaceSettings.xcsettings │ │ ├── IDEWorkspaceChecks.plist │ │ └── swiftpm │ │ └── Package.resolved ├── xcuserdata │ └── omchachad.xcuserdatad │ │ ├── xcdebugger │ │ └── Breakpoints_v2.xcbkptlist │ │ └── xcschemes │ │ └── xcschememanagement.plist └── xcshareddata │ └── xcschemes │ ├── Debug.xcscheme │ ├── Linkeeper.xcscheme │ ├── Share Extension.xcscheme │ ├── Share Extension Mac.xcscheme │ └── Share Extension Vision.xcscheme ├── PrivacyInfo.xcprivacy ├── Share Extension Vision ├── Share Extension Vision.entitlements └── Info.plist └── Share Extension ├── ShareExtension.entitlements └── Info.plist /.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OmChachad/Linkeeper/HEAD/.DS_Store -------------------------------------------------------------------------------- /images/OG.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OmChachad/Linkeeper/HEAD/images/OG.png -------------------------------------------------------------------------------- /images/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OmChachad/Linkeeper/HEAD/images/.DS_Store -------------------------------------------------------------------------------- /Linkeeper/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OmChachad/Linkeeper/HEAD/Linkeeper/.DS_Store -------------------------------------------------------------------------------- /BookmarksWidget/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OmChachad/Linkeeper/HEAD/BookmarksWidget/.DS_Store -------------------------------------------------------------------------------- /Linkeeper/CoreData/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OmChachad/Linkeeper/HEAD/Linkeeper/CoreData/.DS_Store -------------------------------------------------------------------------------- /Linkeeper/Helpers/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OmChachad/Linkeeper/HEAD/Linkeeper/Helpers/.DS_Store -------------------------------------------------------------------------------- /Share Extension Mac/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OmChachad/Linkeeper/HEAD/Share Extension Mac/.DS_Store -------------------------------------------------------------------------------- /Share Extension Mac/icon.icns: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OmChachad/Linkeeper/HEAD/Share Extension Mac/icon.icns -------------------------------------------------------------------------------- /Linkeeper/Assets.xcassets/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OmChachad/Linkeeper/HEAD/Linkeeper/Assets.xcassets/.DS_Store -------------------------------------------------------------------------------- /Linkeeper/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /BookmarksWidget/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /Linkeeper/Assets.xcassets/Om.imageset/Om.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OmChachad/Linkeeper/HEAD/Linkeeper/Assets.xcassets/Om.imageset/Om.jpg -------------------------------------------------------------------------------- /images/Download on the App Store Badge.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OmChachad/Linkeeper/HEAD/images/Download on the App Store Badge.png -------------------------------------------------------------------------------- /Linkeeper/Assets.xcassets/Om.imageset/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OmChachad/Linkeeper/HEAD/Linkeeper/Assets.xcassets/Om.imageset/.DS_Store -------------------------------------------------------------------------------- /Linkeeper/Preview Content/Preview Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /Linkeeper/Assets.xcassets/AppIcon.appiconset/40.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OmChachad/Linkeeper/HEAD/Linkeeper/Assets.xcassets/AppIcon.appiconset/40.png -------------------------------------------------------------------------------- /Linkeeper/Assets.xcassets/AppIcon.appiconset/58.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OmChachad/Linkeeper/HEAD/Linkeeper/Assets.xcassets/AppIcon.appiconset/58.png -------------------------------------------------------------------------------- /Linkeeper/Assets.xcassets/AppIcon.appiconset/60.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OmChachad/Linkeeper/HEAD/Linkeeper/Assets.xcassets/AppIcon.appiconset/60.png -------------------------------------------------------------------------------- /Linkeeper/Assets.xcassets/AppIcon.appiconset/76.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OmChachad/Linkeeper/HEAD/Linkeeper/Assets.xcassets/AppIcon.appiconset/76.png -------------------------------------------------------------------------------- /Linkeeper/Assets.xcassets/AppIcon.appiconset/80.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OmChachad/Linkeeper/HEAD/Linkeeper/Assets.xcassets/AppIcon.appiconset/80.png -------------------------------------------------------------------------------- /Linkeeper/Assets.xcassets/AppIcon.appiconset/87.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OmChachad/Linkeeper/HEAD/Linkeeper/Assets.xcassets/AppIcon.appiconset/87.png -------------------------------------------------------------------------------- /Linkeeper/Assets.xcassets/AppIcon.appiconset/1024.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OmChachad/Linkeeper/HEAD/Linkeeper/Assets.xcassets/AppIcon.appiconset/1024.png -------------------------------------------------------------------------------- /Linkeeper/Assets.xcassets/AppIcon.appiconset/114 1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OmChachad/Linkeeper/HEAD/Linkeeper/Assets.xcassets/AppIcon.appiconset/114 1.png -------------------------------------------------------------------------------- /Linkeeper/Assets.xcassets/AppIcon.appiconset/114 2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OmChachad/Linkeeper/HEAD/Linkeeper/Assets.xcassets/AppIcon.appiconset/114 2.png -------------------------------------------------------------------------------- /Linkeeper/Assets.xcassets/AppIcon.appiconset/114.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OmChachad/Linkeeper/HEAD/Linkeeper/Assets.xcassets/AppIcon.appiconset/114.png -------------------------------------------------------------------------------- /Linkeeper/Assets.xcassets/AppIcon.appiconset/120 1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OmChachad/Linkeeper/HEAD/Linkeeper/Assets.xcassets/AppIcon.appiconset/120 1.png -------------------------------------------------------------------------------- /Linkeeper/Assets.xcassets/AppIcon.appiconset/120 2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OmChachad/Linkeeper/HEAD/Linkeeper/Assets.xcassets/AppIcon.appiconset/120 2.png -------------------------------------------------------------------------------- /Linkeeper/Assets.xcassets/AppIcon.appiconset/120 3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OmChachad/Linkeeper/HEAD/Linkeeper/Assets.xcassets/AppIcon.appiconset/120 3.png -------------------------------------------------------------------------------- /Linkeeper/Assets.xcassets/AppIcon.appiconset/120 4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OmChachad/Linkeeper/HEAD/Linkeeper/Assets.xcassets/AppIcon.appiconset/120 4.png -------------------------------------------------------------------------------- /Linkeeper/Assets.xcassets/AppIcon.appiconset/120 5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OmChachad/Linkeeper/HEAD/Linkeeper/Assets.xcassets/AppIcon.appiconset/120 5.png -------------------------------------------------------------------------------- /Linkeeper/Assets.xcassets/AppIcon.appiconset/120.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OmChachad/Linkeeper/HEAD/Linkeeper/Assets.xcassets/AppIcon.appiconset/120.png -------------------------------------------------------------------------------- /Linkeeper/Assets.xcassets/AppIcon.appiconset/128 1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OmChachad/Linkeeper/HEAD/Linkeeper/Assets.xcassets/AppIcon.appiconset/128 1.png -------------------------------------------------------------------------------- /Linkeeper/Assets.xcassets/AppIcon.appiconset/128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OmChachad/Linkeeper/HEAD/Linkeeper/Assets.xcassets/AppIcon.appiconset/128.png -------------------------------------------------------------------------------- /Linkeeper/Assets.xcassets/AppIcon.appiconset/136 1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OmChachad/Linkeeper/HEAD/Linkeeper/Assets.xcassets/AppIcon.appiconset/136 1.png -------------------------------------------------------------------------------- /Linkeeper/Assets.xcassets/AppIcon.appiconset/136.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OmChachad/Linkeeper/HEAD/Linkeeper/Assets.xcassets/AppIcon.appiconset/136.png -------------------------------------------------------------------------------- /Linkeeper/Assets.xcassets/AppIcon.appiconset/152 1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OmChachad/Linkeeper/HEAD/Linkeeper/Assets.xcassets/AppIcon.appiconset/152 1.png -------------------------------------------------------------------------------- /Linkeeper/Assets.xcassets/AppIcon.appiconset/152.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OmChachad/Linkeeper/HEAD/Linkeeper/Assets.xcassets/AppIcon.appiconset/152.png -------------------------------------------------------------------------------- /Linkeeper/Assets.xcassets/AppIcon.appiconset/167 1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OmChachad/Linkeeper/HEAD/Linkeeper/Assets.xcassets/AppIcon.appiconset/167 1.png -------------------------------------------------------------------------------- /Linkeeper/Assets.xcassets/AppIcon.appiconset/167.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OmChachad/Linkeeper/HEAD/Linkeeper/Assets.xcassets/AppIcon.appiconset/167.png -------------------------------------------------------------------------------- /Linkeeper/Assets.xcassets/AppIcon.appiconset/180 1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OmChachad/Linkeeper/HEAD/Linkeeper/Assets.xcassets/AppIcon.appiconset/180 1.png -------------------------------------------------------------------------------- /Linkeeper/Assets.xcassets/AppIcon.appiconset/180 2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OmChachad/Linkeeper/HEAD/Linkeeper/Assets.xcassets/AppIcon.appiconset/180 2.png -------------------------------------------------------------------------------- /Linkeeper/Assets.xcassets/AppIcon.appiconset/180.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OmChachad/Linkeeper/HEAD/Linkeeper/Assets.xcassets/AppIcon.appiconset/180.png -------------------------------------------------------------------------------- /Linkeeper/Assets.xcassets/AppIcon.appiconset/192 1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OmChachad/Linkeeper/HEAD/Linkeeper/Assets.xcassets/AppIcon.appiconset/192 1.png -------------------------------------------------------------------------------- /Linkeeper/Assets.xcassets/AppIcon.appiconset/192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OmChachad/Linkeeper/HEAD/Linkeeper/Assets.xcassets/AppIcon.appiconset/192.png -------------------------------------------------------------------------------- /Linkeeper/Assets.xcassets/AppIcon.appiconset/40 1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OmChachad/Linkeeper/HEAD/Linkeeper/Assets.xcassets/AppIcon.appiconset/40 1.png -------------------------------------------------------------------------------- /Linkeeper/Assets.xcassets/AppIcon.appiconset/40 2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OmChachad/Linkeeper/HEAD/Linkeeper/Assets.xcassets/AppIcon.appiconset/40 2.png -------------------------------------------------------------------------------- /Linkeeper/Assets.xcassets/AppIcon.appiconset/58 1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OmChachad/Linkeeper/HEAD/Linkeeper/Assets.xcassets/AppIcon.appiconset/58 1.png -------------------------------------------------------------------------------- /Linkeeper/Assets.xcassets/AppIcon.appiconset/58 2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OmChachad/Linkeeper/HEAD/Linkeeper/Assets.xcassets/AppIcon.appiconset/58 2.png -------------------------------------------------------------------------------- /Linkeeper/Assets.xcassets/AppIcon.appiconset/60 1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OmChachad/Linkeeper/HEAD/Linkeeper/Assets.xcassets/AppIcon.appiconset/60 1.png -------------------------------------------------------------------------------- /Linkeeper/Assets.xcassets/AppIcon.appiconset/60 2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OmChachad/Linkeeper/HEAD/Linkeeper/Assets.xcassets/AppIcon.appiconset/60 2.png -------------------------------------------------------------------------------- /Linkeeper/Assets.xcassets/AppIcon.appiconset/76 1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OmChachad/Linkeeper/HEAD/Linkeeper/Assets.xcassets/AppIcon.appiconset/76 1.png -------------------------------------------------------------------------------- /Linkeeper/Assets.xcassets/AppIcon.appiconset/80 1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OmChachad/Linkeeper/HEAD/Linkeeper/Assets.xcassets/AppIcon.appiconset/80 1.png -------------------------------------------------------------------------------- /Linkeeper/Assets.xcassets/AppIcon.appiconset/80 2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OmChachad/Linkeeper/HEAD/Linkeeper/Assets.xcassets/AppIcon.appiconset/80 2.png -------------------------------------------------------------------------------- /Linkeeper/Assets.xcassets/AppIcon.appiconset/87 1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OmChachad/Linkeeper/HEAD/Linkeeper/Assets.xcassets/AppIcon.appiconset/87 1.png -------------------------------------------------------------------------------- /Linkeeper/Assets.xcassets/AppIcon.appiconset/87 2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OmChachad/Linkeeper/HEAD/Linkeeper/Assets.xcassets/AppIcon.appiconset/87 2.png -------------------------------------------------------------------------------- /Linkeeper/Assets.xcassets/DarkIcon.appiconset/1024.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OmChachad/Linkeeper/HEAD/Linkeeper/Assets.xcassets/DarkIcon.appiconset/1024.png -------------------------------------------------------------------------------- /Linkeeper/Assets.xcassets/DarkIcon.appiconset/114.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OmChachad/Linkeeper/HEAD/Linkeeper/Assets.xcassets/DarkIcon.appiconset/114.png -------------------------------------------------------------------------------- /Linkeeper/Assets.xcassets/DarkIcon.appiconset/120.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OmChachad/Linkeeper/HEAD/Linkeeper/Assets.xcassets/DarkIcon.appiconset/120.png -------------------------------------------------------------------------------- /Linkeeper/Assets.xcassets/DarkIcon.appiconset/180.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OmChachad/Linkeeper/HEAD/Linkeeper/Assets.xcassets/DarkIcon.appiconset/180.png -------------------------------------------------------------------------------- /Linkeeper/Assets.xcassets/DarkIcon.appiconset/40.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OmChachad/Linkeeper/HEAD/Linkeeper/Assets.xcassets/DarkIcon.appiconset/40.png -------------------------------------------------------------------------------- /Linkeeper/Assets.xcassets/DarkIcon.appiconset/58.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OmChachad/Linkeeper/HEAD/Linkeeper/Assets.xcassets/DarkIcon.appiconset/58.png -------------------------------------------------------------------------------- /Linkeeper/Assets.xcassets/DarkIcon.appiconset/60.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OmChachad/Linkeeper/HEAD/Linkeeper/Assets.xcassets/DarkIcon.appiconset/60.png -------------------------------------------------------------------------------- /Linkeeper/Assets.xcassets/DarkIcon.appiconset/80.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OmChachad/Linkeeper/HEAD/Linkeeper/Assets.xcassets/DarkIcon.appiconset/80.png -------------------------------------------------------------------------------- /Linkeeper/Assets.xcassets/DarkIcon.appiconset/87.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OmChachad/Linkeeper/HEAD/Linkeeper/Assets.xcassets/DarkIcon.appiconset/87.png -------------------------------------------------------------------------------- /Linkeeper/Assets.xcassets/ClassicIcon.appiconset/114.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OmChachad/Linkeeper/HEAD/Linkeeper/Assets.xcassets/ClassicIcon.appiconset/114.png -------------------------------------------------------------------------------- /Linkeeper/Assets.xcassets/ClassicIcon.appiconset/120.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OmChachad/Linkeeper/HEAD/Linkeeper/Assets.xcassets/ClassicIcon.appiconset/120.png -------------------------------------------------------------------------------- /Linkeeper/Assets.xcassets/ClassicIcon.appiconset/128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OmChachad/Linkeeper/HEAD/Linkeeper/Assets.xcassets/ClassicIcon.appiconset/128.png -------------------------------------------------------------------------------- /Linkeeper/Assets.xcassets/ClassicIcon.appiconset/136.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OmChachad/Linkeeper/HEAD/Linkeeper/Assets.xcassets/ClassicIcon.appiconset/136.png -------------------------------------------------------------------------------- /Linkeeper/Assets.xcassets/ClassicIcon.appiconset/152.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OmChachad/Linkeeper/HEAD/Linkeeper/Assets.xcassets/ClassicIcon.appiconset/152.png -------------------------------------------------------------------------------- /Linkeeper/Assets.xcassets/ClassicIcon.appiconset/167.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OmChachad/Linkeeper/HEAD/Linkeeper/Assets.xcassets/ClassicIcon.appiconset/167.png -------------------------------------------------------------------------------- /Linkeeper/Assets.xcassets/ClassicIcon.appiconset/180.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OmChachad/Linkeeper/HEAD/Linkeeper/Assets.xcassets/ClassicIcon.appiconset/180.png -------------------------------------------------------------------------------- /Linkeeper/Assets.xcassets/ClassicIcon.appiconset/192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OmChachad/Linkeeper/HEAD/Linkeeper/Assets.xcassets/ClassicIcon.appiconset/192.png -------------------------------------------------------------------------------- /Linkeeper/Assets.xcassets/ClassicIcon.appiconset/40.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OmChachad/Linkeeper/HEAD/Linkeeper/Assets.xcassets/ClassicIcon.appiconset/40.png -------------------------------------------------------------------------------- /Linkeeper/Assets.xcassets/ClassicIcon.appiconset/58.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OmChachad/Linkeeper/HEAD/Linkeeper/Assets.xcassets/ClassicIcon.appiconset/58.png -------------------------------------------------------------------------------- /Linkeeper/Assets.xcassets/ClassicIcon.appiconset/60.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OmChachad/Linkeeper/HEAD/Linkeeper/Assets.xcassets/ClassicIcon.appiconset/60.png -------------------------------------------------------------------------------- /Linkeeper/Assets.xcassets/ClassicIcon.appiconset/76.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OmChachad/Linkeeper/HEAD/Linkeeper/Assets.xcassets/ClassicIcon.appiconset/76.png -------------------------------------------------------------------------------- /Linkeeper/Assets.xcassets/ClassicIcon.appiconset/80.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OmChachad/Linkeeper/HEAD/Linkeeper/Assets.xcassets/ClassicIcon.appiconset/80.png -------------------------------------------------------------------------------- /Linkeeper/Assets.xcassets/ClassicIcon.appiconset/87.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OmChachad/Linkeeper/HEAD/Linkeeper/Assets.xcassets/ClassicIcon.appiconset/87.png -------------------------------------------------------------------------------- /Linkeeper/Assets.xcassets/DarkIcon.appiconset/120 1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OmChachad/Linkeeper/HEAD/Linkeeper/Assets.xcassets/DarkIcon.appiconset/120 1.png -------------------------------------------------------------------------------- /Linkeeper/Assets.xcassets/AppIcon.appiconset/macOS 1024.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OmChachad/Linkeeper/HEAD/Linkeeper/Assets.xcassets/AppIcon.appiconset/macOS 1024.png -------------------------------------------------------------------------------- /Linkeeper/Assets.xcassets/AppIcon.appiconset/macOS 128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OmChachad/Linkeeper/HEAD/Linkeeper/Assets.xcassets/AppIcon.appiconset/macOS 128.png -------------------------------------------------------------------------------- /Linkeeper/Assets.xcassets/AppIcon.appiconset/macOS 16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OmChachad/Linkeeper/HEAD/Linkeeper/Assets.xcassets/AppIcon.appiconset/macOS 16.png -------------------------------------------------------------------------------- /Linkeeper/Assets.xcassets/AppIcon.appiconset/macOS 256.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OmChachad/Linkeeper/HEAD/Linkeeper/Assets.xcassets/AppIcon.appiconset/macOS 256.png -------------------------------------------------------------------------------- /Linkeeper/Assets.xcassets/AppIcon.appiconset/macOS 257.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OmChachad/Linkeeper/HEAD/Linkeeper/Assets.xcassets/AppIcon.appiconset/macOS 257.png -------------------------------------------------------------------------------- /Linkeeper/Assets.xcassets/AppIcon.appiconset/macOS 32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OmChachad/Linkeeper/HEAD/Linkeeper/Assets.xcassets/AppIcon.appiconset/macOS 32.png -------------------------------------------------------------------------------- /Linkeeper/Assets.xcassets/AppIcon.appiconset/macOS 33.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OmChachad/Linkeeper/HEAD/Linkeeper/Assets.xcassets/AppIcon.appiconset/macOS 33.png -------------------------------------------------------------------------------- /Linkeeper/Assets.xcassets/AppIcon.appiconset/macOS 512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OmChachad/Linkeeper/HEAD/Linkeeper/Assets.xcassets/AppIcon.appiconset/macOS 512.png -------------------------------------------------------------------------------- /Linkeeper/Assets.xcassets/AppIcon.appiconset/macOS 513.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OmChachad/Linkeeper/HEAD/Linkeeper/Assets.xcassets/AppIcon.appiconset/macOS 513.png -------------------------------------------------------------------------------- /Linkeeper/Assets.xcassets/AppIcon.appiconset/macOS 65.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OmChachad/Linkeeper/HEAD/Linkeeper/Assets.xcassets/AppIcon.appiconset/macOS 65.png -------------------------------------------------------------------------------- /Linkeeper/Assets.xcassets/ClassicIcon.appiconset/1024.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OmChachad/Linkeeper/HEAD/Linkeeper/Assets.xcassets/ClassicIcon.appiconset/1024.png -------------------------------------------------------------------------------- /Linkeeper/Assets.xcassets/ClassicIcon.appiconset/114 1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OmChachad/Linkeeper/HEAD/Linkeeper/Assets.xcassets/ClassicIcon.appiconset/114 1.png -------------------------------------------------------------------------------- /Linkeeper/Assets.xcassets/ClassicIcon.appiconset/114 2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OmChachad/Linkeeper/HEAD/Linkeeper/Assets.xcassets/ClassicIcon.appiconset/114 2.png -------------------------------------------------------------------------------- /Linkeeper/Assets.xcassets/ClassicIcon.appiconset/120 1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OmChachad/Linkeeper/HEAD/Linkeeper/Assets.xcassets/ClassicIcon.appiconset/120 1.png -------------------------------------------------------------------------------- /Linkeeper/Assets.xcassets/ClassicIcon.appiconset/120 2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OmChachad/Linkeeper/HEAD/Linkeeper/Assets.xcassets/ClassicIcon.appiconset/120 2.png -------------------------------------------------------------------------------- /Linkeeper/Assets.xcassets/ClassicIcon.appiconset/120 3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OmChachad/Linkeeper/HEAD/Linkeeper/Assets.xcassets/ClassicIcon.appiconset/120 3.png -------------------------------------------------------------------------------- /Linkeeper/Assets.xcassets/ClassicIcon.appiconset/120 4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OmChachad/Linkeeper/HEAD/Linkeeper/Assets.xcassets/ClassicIcon.appiconset/120 4.png -------------------------------------------------------------------------------- /Linkeeper/Assets.xcassets/ClassicIcon.appiconset/120 5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OmChachad/Linkeeper/HEAD/Linkeeper/Assets.xcassets/ClassicIcon.appiconset/120 5.png -------------------------------------------------------------------------------- /Linkeeper/Assets.xcassets/ClassicIcon.appiconset/128 1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OmChachad/Linkeeper/HEAD/Linkeeper/Assets.xcassets/ClassicIcon.appiconset/128 1.png -------------------------------------------------------------------------------- /Linkeeper/Assets.xcassets/ClassicIcon.appiconset/136 1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OmChachad/Linkeeper/HEAD/Linkeeper/Assets.xcassets/ClassicIcon.appiconset/136 1.png -------------------------------------------------------------------------------- /Linkeeper/Assets.xcassets/ClassicIcon.appiconset/152 1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OmChachad/Linkeeper/HEAD/Linkeeper/Assets.xcassets/ClassicIcon.appiconset/152 1.png -------------------------------------------------------------------------------- /Linkeeper/Assets.xcassets/ClassicIcon.appiconset/167 1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OmChachad/Linkeeper/HEAD/Linkeeper/Assets.xcassets/ClassicIcon.appiconset/167 1.png -------------------------------------------------------------------------------- /Linkeeper/Assets.xcassets/ClassicIcon.appiconset/180 1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OmChachad/Linkeeper/HEAD/Linkeeper/Assets.xcassets/ClassicIcon.appiconset/180 1.png -------------------------------------------------------------------------------- /Linkeeper/Assets.xcassets/ClassicIcon.appiconset/180 2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OmChachad/Linkeeper/HEAD/Linkeeper/Assets.xcassets/ClassicIcon.appiconset/180 2.png -------------------------------------------------------------------------------- /Linkeeper/Assets.xcassets/ClassicIcon.appiconset/192 1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OmChachad/Linkeeper/HEAD/Linkeeper/Assets.xcassets/ClassicIcon.appiconset/192 1.png -------------------------------------------------------------------------------- /Linkeeper/Assets.xcassets/ClassicIcon.appiconset/40 1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OmChachad/Linkeeper/HEAD/Linkeeper/Assets.xcassets/ClassicIcon.appiconset/40 1.png -------------------------------------------------------------------------------- /Linkeeper/Assets.xcassets/ClassicIcon.appiconset/40 2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OmChachad/Linkeeper/HEAD/Linkeeper/Assets.xcassets/ClassicIcon.appiconset/40 2.png -------------------------------------------------------------------------------- /Linkeeper/Assets.xcassets/ClassicIcon.appiconset/58 1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OmChachad/Linkeeper/HEAD/Linkeeper/Assets.xcassets/ClassicIcon.appiconset/58 1.png -------------------------------------------------------------------------------- /Linkeeper/Assets.xcassets/ClassicIcon.appiconset/58 2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OmChachad/Linkeeper/HEAD/Linkeeper/Assets.xcassets/ClassicIcon.appiconset/58 2.png -------------------------------------------------------------------------------- /Linkeeper/Assets.xcassets/ClassicIcon.appiconset/60 1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OmChachad/Linkeeper/HEAD/Linkeeper/Assets.xcassets/ClassicIcon.appiconset/60 1.png -------------------------------------------------------------------------------- /Linkeeper/Assets.xcassets/ClassicIcon.appiconset/60 2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OmChachad/Linkeeper/HEAD/Linkeeper/Assets.xcassets/ClassicIcon.appiconset/60 2.png -------------------------------------------------------------------------------- /Linkeeper/Assets.xcassets/ClassicIcon.appiconset/76 1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OmChachad/Linkeeper/HEAD/Linkeeper/Assets.xcassets/ClassicIcon.appiconset/76 1.png -------------------------------------------------------------------------------- /Linkeeper/Assets.xcassets/ClassicIcon.appiconset/80 1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OmChachad/Linkeeper/HEAD/Linkeeper/Assets.xcassets/ClassicIcon.appiconset/80 1.png -------------------------------------------------------------------------------- /Linkeeper/Assets.xcassets/ClassicIcon.appiconset/80 2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OmChachad/Linkeeper/HEAD/Linkeeper/Assets.xcassets/ClassicIcon.appiconset/80 2.png -------------------------------------------------------------------------------- /Linkeeper/Assets.xcassets/ClassicIcon.appiconset/87 1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OmChachad/Linkeeper/HEAD/Linkeeper/Assets.xcassets/ClassicIcon.appiconset/87 1.png -------------------------------------------------------------------------------- /Linkeeper/Assets.xcassets/ClassicIcon.appiconset/87 2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OmChachad/Linkeeper/HEAD/Linkeeper/Assets.xcassets/ClassicIcon.appiconset/87 2.png -------------------------------------------------------------------------------- /Linkeeper/Assets.xcassets/ClassicIconImage.imageset/512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OmChachad/Linkeeper/HEAD/Linkeeper/Assets.xcassets/ClassicIconImage.imageset/512.png -------------------------------------------------------------------------------- /Linkeeper/Assets.xcassets/AppIcon.appiconset/Frame 3-8 76.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OmChachad/Linkeeper/HEAD/Linkeeper/Assets.xcassets/AppIcon.appiconset/Frame 3-8 76.png -------------------------------------------------------------------------------- /Linkeeper/Assets.xcassets/AppIcon.solidimagestack/Back.solidimagestacklayer/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /Linkeeper/Assets.xcassets/AppIcon.solidimagestack/Front.solidimagestacklayer/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /Linkeeper/Assets.xcassets/AppIcon.solidimagestack/Middle.solidimagestacklayer/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /Linkeeper/Assets.xcassets/ClassicIcon.appiconset/macOS 16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OmChachad/Linkeeper/HEAD/Linkeeper/Assets.xcassets/ClassicIcon.appiconset/macOS 16.png -------------------------------------------------------------------------------- /Linkeeper/Assets.xcassets/ClassicIcon.appiconset/macOS 32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OmChachad/Linkeeper/HEAD/Linkeeper/Assets.xcassets/ClassicIcon.appiconset/macOS 32.png -------------------------------------------------------------------------------- /Linkeeper/Assets.xcassets/ClassicIcon.appiconset/macOS 33.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OmChachad/Linkeeper/HEAD/Linkeeper/Assets.xcassets/ClassicIcon.appiconset/macOS 33.png -------------------------------------------------------------------------------- /Linkeeper/Assets.xcassets/ClassicIcon.appiconset/macOS 65.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OmChachad/Linkeeper/HEAD/Linkeeper/Assets.xcassets/ClassicIcon.appiconset/macOS 65.png -------------------------------------------------------------------------------- /Linkeeper/Assets.xcassets/ClassicIconImage.imageset/1024.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OmChachad/Linkeeper/HEAD/Linkeeper/Assets.xcassets/ClassicIconImage.imageset/1024.png -------------------------------------------------------------------------------- /Linkeeper/Assets.xcassets/DarkIcon.solidimagestack/Back.solidimagestacklayer/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /Linkeeper/Assets.xcassets/DarkIcon.solidimagestack/Front.solidimagestacklayer/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /Linkeeper/Assets.xcassets/AppIcon.appiconset/Frame 3-8 128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OmChachad/Linkeeper/HEAD/Linkeeper/Assets.xcassets/AppIcon.appiconset/Frame 3-8 128.png -------------------------------------------------------------------------------- /Linkeeper/Assets.xcassets/AppIcon.appiconset/Frame 3-8 136.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OmChachad/Linkeeper/HEAD/Linkeeper/Assets.xcassets/AppIcon.appiconset/Frame 3-8 136.png -------------------------------------------------------------------------------- /Linkeeper/Assets.xcassets/AppIcon.appiconset/Frame 3-8 152.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OmChachad/Linkeeper/HEAD/Linkeeper/Assets.xcassets/AppIcon.appiconset/Frame 3-8 152.png -------------------------------------------------------------------------------- /Linkeeper/Assets.xcassets/AppIcon.appiconset/Frame 3-8 167.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OmChachad/Linkeeper/HEAD/Linkeeper/Assets.xcassets/AppIcon.appiconset/Frame 3-8 167.png -------------------------------------------------------------------------------- /Linkeeper/Assets.xcassets/AppIcon.appiconset/Frame 3-8 192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OmChachad/Linkeeper/HEAD/Linkeeper/Assets.xcassets/AppIcon.appiconset/Frame 3-8 192.png -------------------------------------------------------------------------------- /Linkeeper/Assets.xcassets/ClassicIcon.appiconset/macOS 1024.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OmChachad/Linkeeper/HEAD/Linkeeper/Assets.xcassets/ClassicIcon.appiconset/macOS 1024.png -------------------------------------------------------------------------------- /Linkeeper/Assets.xcassets/ClassicIcon.appiconset/macOS 128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OmChachad/Linkeeper/HEAD/Linkeeper/Assets.xcassets/ClassicIcon.appiconset/macOS 128.png -------------------------------------------------------------------------------- /Linkeeper/Assets.xcassets/ClassicIcon.appiconset/macOS 256.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OmChachad/Linkeeper/HEAD/Linkeeper/Assets.xcassets/ClassicIcon.appiconset/macOS 256.png -------------------------------------------------------------------------------- /Linkeeper/Assets.xcassets/ClassicIcon.appiconset/macOS 257.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OmChachad/Linkeeper/HEAD/Linkeeper/Assets.xcassets/ClassicIcon.appiconset/macOS 257.png -------------------------------------------------------------------------------- /Linkeeper/Assets.xcassets/ClassicIcon.appiconset/macOS 512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OmChachad/Linkeeper/HEAD/Linkeeper/Assets.xcassets/ClassicIcon.appiconset/macOS 512.png -------------------------------------------------------------------------------- /Linkeeper/Assets.xcassets/ClassicIcon.appiconset/macOS 513.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OmChachad/Linkeeper/HEAD/Linkeeper/Assets.xcassets/ClassicIcon.appiconset/macOS 513.png -------------------------------------------------------------------------------- /Linkeeper/Assets.xcassets/ClassicIcon.solidimagestack/Back.solidimagestacklayer/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /Linkeeper/Assets.xcassets/ClassicIcon.solidimagestack/Front.solidimagestacklayer/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /Linkeeper/Assets.xcassets/ClassicIcon.solidimagestack/Middle.solidimagestacklayer/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /Linkeeper/Assets.xcassets/DarkIcon.appiconset/Frame 3-7 128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OmChachad/Linkeeper/HEAD/Linkeeper/Assets.xcassets/DarkIcon.appiconset/Frame 3-7 128.png -------------------------------------------------------------------------------- /Linkeeper/Assets.xcassets/DarkIcon.appiconset/Frame 3-7 136.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OmChachad/Linkeeper/HEAD/Linkeeper/Assets.xcassets/DarkIcon.appiconset/Frame 3-7 136.png -------------------------------------------------------------------------------- /Linkeeper/Assets.xcassets/DarkIcon.appiconset/Frame 3-7 152.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OmChachad/Linkeeper/HEAD/Linkeeper/Assets.xcassets/DarkIcon.appiconset/Frame 3-7 152.png -------------------------------------------------------------------------------- /Linkeeper/Assets.xcassets/DarkIcon.appiconset/Frame 3-7 167.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OmChachad/Linkeeper/HEAD/Linkeeper/Assets.xcassets/DarkIcon.appiconset/Frame 3-7 167.png -------------------------------------------------------------------------------- /Linkeeper/Assets.xcassets/DarkIcon.appiconset/Frame 3-7 192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OmChachad/Linkeeper/HEAD/Linkeeper/Assets.xcassets/DarkIcon.appiconset/Frame 3-7 192.png -------------------------------------------------------------------------------- /Linkeeper/Assets.xcassets/DarkIcon.appiconset/Frame 3-7 76.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OmChachad/Linkeeper/HEAD/Linkeeper/Assets.xcassets/DarkIcon.appiconset/Frame 3-7 76.png -------------------------------------------------------------------------------- /Linkeeper/Assets.xcassets/DarkIcon.solidimagestack/Middle.solidimagestacklayer/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /Linkeeper/Assets.xcassets/DarkIconImage.imageset/Frame 3-7 1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OmChachad/Linkeeper/HEAD/Linkeeper/Assets.xcassets/DarkIconImage.imageset/Frame 3-7 1.png -------------------------------------------------------------------------------- /Linkeeper/Assets.xcassets/DarkIconImage.imageset/Frame 3-7.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OmChachad/Linkeeper/HEAD/Linkeeper/Assets.xcassets/DarkIconImage.imageset/Frame 3-7.png -------------------------------------------------------------------------------- /Linkeeper/Assets.xcassets/ClassicIcon.appiconset/Frame 3-8 128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OmChachad/Linkeeper/HEAD/Linkeeper/Assets.xcassets/ClassicIcon.appiconset/Frame 3-8 128.png -------------------------------------------------------------------------------- /Linkeeper/Assets.xcassets/ClassicIcon.appiconset/Frame 3-8 136.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OmChachad/Linkeeper/HEAD/Linkeeper/Assets.xcassets/ClassicIcon.appiconset/Frame 3-8 136.png -------------------------------------------------------------------------------- /Linkeeper/Assets.xcassets/ClassicIcon.appiconset/Frame 3-8 152.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OmChachad/Linkeeper/HEAD/Linkeeper/Assets.xcassets/ClassicIcon.appiconset/Frame 3-8 152.png -------------------------------------------------------------------------------- /Linkeeper/Assets.xcassets/ClassicIcon.appiconset/Frame 3-8 167.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OmChachad/Linkeeper/HEAD/Linkeeper/Assets.xcassets/ClassicIcon.appiconset/Frame 3-8 167.png -------------------------------------------------------------------------------- /Linkeeper/Assets.xcassets/ClassicIcon.appiconset/Frame 3-8 192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OmChachad/Linkeeper/HEAD/Linkeeper/Assets.xcassets/ClassicIcon.appiconset/Frame 3-8 192.png -------------------------------------------------------------------------------- /Linkeeper/Assets.xcassets/ClassicIcon.appiconset/Frame 3-8 76.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OmChachad/Linkeeper/HEAD/Linkeeper/Assets.xcassets/ClassicIcon.appiconset/Frame 3-8 76.png -------------------------------------------------------------------------------- /Linkeeper/Assets.xcassets/AppIcon.appiconset/Icon-Dark-1024x1024.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OmChachad/Linkeeper/HEAD/Linkeeper/Assets.xcassets/AppIcon.appiconset/Icon-Dark-1024x1024.png -------------------------------------------------------------------------------- /Linkeeper/Assets.xcassets/AppIcon.appiconset/Icon-Tinted-1024x1024.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OmChachad/Linkeeper/HEAD/Linkeeper/Assets.xcassets/AppIcon.appiconset/Icon-Tinted-1024x1024.png -------------------------------------------------------------------------------- /Linkeeper/Assets.xcassets/ClassicIcon.appiconset/Icon-Dark-1024x1024.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OmChachad/Linkeeper/HEAD/Linkeeper/Assets.xcassets/ClassicIcon.appiconset/Icon-Dark-1024x1024.png -------------------------------------------------------------------------------- /Linkeeper/Assets.xcassets/ClassicIcon.appiconset/Icon-Tinted-1024x1024.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OmChachad/Linkeeper/HEAD/Linkeeper/Assets.xcassets/ClassicIcon.appiconset/Icon-Tinted-1024x1024.png -------------------------------------------------------------------------------- /Linkeeper.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /Linkeeper/Assets.xcassets/AccentColor.colorset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "colors" : [ 3 | { 4 | "idiom" : "universal" 5 | } 6 | ], 7 | "info" : { 8 | "author" : "xcode", 9 | "version" : 1 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /BookmarksWidget/Assets.xcassets/AccentColor.colorset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "colors" : [ 3 | { 4 | "idiom" : "universal" 5 | } 6 | ], 7 | "info" : { 8 | "author" : "xcode", 9 | "version" : 1 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /Linkeeper.xcodeproj/xcuserdata/omchachad.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | -------------------------------------------------------------------------------- /Linkeeper.xcodeproj/project.xcworkspace/xcuserdata/omchachad.xcuserdatad/UserInterfaceState.xcuserstate: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OmChachad/Linkeeper/HEAD/Linkeeper.xcodeproj/project.xcworkspace/xcuserdata/omchachad.xcuserdatad/UserInterfaceState.xcuserstate -------------------------------------------------------------------------------- /Linkeeper/Assets.xcassets/AppIcon.solidimagestack/Back.solidimagestacklayer/Content.imageset/Frame 3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OmChachad/Linkeeper/HEAD/Linkeeper/Assets.xcassets/AppIcon.solidimagestack/Back.solidimagestacklayer/Content.imageset/Frame 3.png -------------------------------------------------------------------------------- /Linkeeper/Assets.xcassets/AppIcon.solidimagestack/Front.solidimagestacklayer/Content.imageset/Center.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OmChachad/Linkeeper/HEAD/Linkeeper/Assets.xcassets/AppIcon.solidimagestack/Front.solidimagestacklayer/Content.imageset/Center.png -------------------------------------------------------------------------------- /Linkeeper/Assets.xcassets/ClassicIcon.solidimagestack/Back.solidimagestacklayer/Content.imageset/Frame 3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OmChachad/Linkeeper/HEAD/Linkeeper/Assets.xcassets/ClassicIcon.solidimagestack/Back.solidimagestacklayer/Content.imageset/Frame 3.png -------------------------------------------------------------------------------- /Linkeeper/Assets.xcassets/ClassicIcon.solidimagestack/Front.solidimagestacklayer/Content.imageset/Center.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OmChachad/Linkeeper/HEAD/Linkeeper/Assets.xcassets/ClassicIcon.solidimagestack/Front.solidimagestacklayer/Content.imageset/Center.png -------------------------------------------------------------------------------- /Linkeeper/Assets.xcassets/DarkIcon.solidimagestack/Back.solidimagestacklayer/Content.imageset/Frame 3-17.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OmChachad/Linkeeper/HEAD/Linkeeper/Assets.xcassets/DarkIcon.solidimagestack/Back.solidimagestacklayer/Content.imageset/Frame 3-17.jpg -------------------------------------------------------------------------------- /Linkeeper/Assets.xcassets/DarkIcon.solidimagestack/Front.solidimagestacklayer/Content.imageset/Frame 3-13.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OmChachad/Linkeeper/HEAD/Linkeeper/Assets.xcassets/DarkIcon.solidimagestack/Front.solidimagestacklayer/Content.imageset/Frame 3-13.png -------------------------------------------------------------------------------- /Linkeeper/Helpers/SharedUserDefaults.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SharedUserDefaults.swift 3 | // Linkeeper 4 | // 5 | // Created by Om Chachad on 7/14/25. 6 | // 7 | 8 | import Foundation 9 | 10 | let SharedUserDefaults = UserDefaults(suiteName: "group.starlightapps.Linkeeper")! 11 | -------------------------------------------------------------------------------- /Linkeeper.xcodeproj/project.xcworkspace/xcuserdata/omchachad.xcuserdatad/IDEFindNavigatorScopes.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /Linkeeper/Helpers/Extensions/Notifications.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Notifications.swift 3 | // Linkeeper 4 | // 5 | // Created by Om Chachad on 3/2/25. 6 | // 7 | 8 | import Foundation 9 | 10 | extension Notification.Name { 11 | static let addBookmark = Notification.Name("addBookmark") 12 | } 13 | -------------------------------------------------------------------------------- /Linkeeper/Helpers/Global/isMac.swift: -------------------------------------------------------------------------------- 1 | // 2 | // isMac.swift 3 | // Linkeeper 4 | // 5 | // Created by Om Chachad on 09/01/24. 6 | // 7 | 8 | import Foundation 9 | 10 | var isMac: Bool { 11 | #if os(macOS) 12 | return true 13 | #else 14 | return false 15 | #endif 16 | } 17 | -------------------------------------------------------------------------------- /Linkeeper/Assets.xcassets/AppIcon.solidimagestack/Middle.solidimagestacklayer/Content.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "vision", 5 | "scale" : "2x" 6 | } 7 | ], 8 | "info" : { 9 | "author" : "xcode", 10 | "version" : 1 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /Linkeeper/Assets.xcassets/DarkIcon.solidimagestack/Middle.solidimagestacklayer/Content.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "vision", 5 | "scale" : "2x" 6 | } 7 | ], 8 | "info" : { 9 | "author" : "xcode", 10 | "version" : 1 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /Linkeeper/CoreData/Folder+CoreDataClass.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Folder+CoreDataClass.swift 3 | // Linkeeper 4 | // 5 | // Created by Om Chachad on 25/05/22. 6 | // 7 | // 8 | 9 | import Foundation 10 | import CoreData 11 | 12 | @objc(Folder) 13 | public class Folder: NSManagedObject { 14 | 15 | } 16 | -------------------------------------------------------------------------------- /Linkeeper/Assets.xcassets/ClassicIcon.solidimagestack/Middle.solidimagestacklayer/Content.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "vision", 5 | "scale" : "2x" 6 | } 7 | ], 8 | "info" : { 9 | "author" : "xcode", 10 | "version" : 1 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /Linkeeper/CoreData/Bookmark+CoreDataClass.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Bookmark+CoreDataClass.swift 3 | // Linkeeper 4 | // 5 | // Created by Om Chachad on 25/05/22. 6 | // 7 | // 8 | 9 | import Foundation 10 | import CoreData 11 | 12 | @objc(Bookmark) 13 | public class Bookmark: NSManagedObject { 14 | 15 | } 16 | -------------------------------------------------------------------------------- /Linkeeper/Helpers/Enums/SortMethod.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SortMethod.swift 3 | // Linkeeper 4 | // 5 | // Created by Om Chachad on 05/01/24. 6 | // 7 | 8 | import Foundation 9 | 10 | enum SortMethod: String, Codable, CaseIterable { 11 | case dateCreated = "Creation Date" 12 | case title = "Title" 13 | } 14 | -------------------------------------------------------------------------------- /Linkeeper/Helpers/Global/isVisionOS.swift: -------------------------------------------------------------------------------- 1 | // 2 | // isVisionOS.swift 3 | // Linkeeper 4 | // 5 | // Created by Om Chachad on 14/11/23. 6 | // 7 | 8 | import Foundation 9 | 10 | var isVisionOS: Bool { 11 | #if os(visionOS) 12 | return true 13 | #else 14 | return false 15 | #endif 16 | } 17 | -------------------------------------------------------------------------------- /Linkeeper.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | PreviewsEnabled 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /Linkeeper.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /Linkeeper/Assets.xcassets/AppIcon.solidimagestack/Back.solidimagestacklayer/Content.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "Frame 3.png", 5 | "idiom" : "vision", 6 | "scale" : "2x" 7 | } 8 | ], 9 | "info" : { 10 | "author" : "xcode", 11 | "version" : 1 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /Linkeeper/Assets.xcassets/AppIcon.solidimagestack/Front.solidimagestacklayer/Content.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "Center.png", 5 | "idiom" : "vision", 6 | "scale" : "2x" 7 | } 8 | ], 9 | "info" : { 10 | "author" : "xcode", 11 | "version" : 1 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /Linkeeper/Assets.xcassets/ClassicIcon.solidimagestack/Back.solidimagestacklayer/Content.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "Frame 3.png", 5 | "idiom" : "vision", 6 | "scale" : "2x" 7 | } 8 | ], 9 | "info" : { 10 | "author" : "xcode", 11 | "version" : 1 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /Linkeeper/Assets.xcassets/ClassicIcon.solidimagestack/Front.solidimagestacklayer/Content.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "Center.png", 5 | "idiom" : "vision", 6 | "scale" : "2x" 7 | } 8 | ], 9 | "info" : { 10 | "author" : "xcode", 11 | "version" : 1 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /Linkeeper/Assets.xcassets/DarkIcon.solidimagestack/Back.solidimagestacklayer/Content.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "Frame 3-17.jpg", 5 | "idiom" : "vision", 6 | "scale" : "2x" 7 | } 8 | ], 9 | "info" : { 10 | "author" : "xcode", 11 | "version" : 1 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /Linkeeper/Assets.xcassets/DarkIcon.solidimagestack/Front.solidimagestacklayer/Content.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "Frame 3-13.png", 5 | "idiom" : "vision", 6 | "scale" : "2x" 7 | } 8 | ], 9 | "info" : { 10 | "author" : "xcode", 11 | "version" : 1 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /BookmarksWidget/BookmarksWidgetBundle.swift: -------------------------------------------------------------------------------- 1 | // 2 | // BookmarksWidgetBundle.swift 3 | // BookmarksWidget 4 | // 5 | // Created by Om Chachad on 07/01/24. 6 | // 7 | // 8 | import WidgetKit 9 | import SwiftUI 10 | 11 | @main 12 | struct BookmarksWidgetBundle: WidgetBundle { 13 | var body: some Widget { 14 | BookmarksWidget() 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /BookmarksWidget/BookmarksEntry.swift: -------------------------------------------------------------------------------- 1 | // 2 | // BookmarksEntry.swift 3 | // BookmarksWidgetExtension 4 | // 5 | // Created by Om Chachad on 01/01/24. 6 | // 7 | 8 | import Foundation 9 | import WidgetKit 10 | 11 | struct BookmarksEntry: TimelineEntry { 12 | let date: Date 13 | let bookmarks: [LinkeeperBookmarkEntity] 14 | let configuration: ConfigurationAppIntent 15 | } 16 | -------------------------------------------------------------------------------- /BookmarksWidget/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | NSExtension 6 | 7 | NSExtensionPointIdentifier 8 | com.apple.widgetkit-extension 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /Linkeeper/Helpers/Extensions/Bool.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Bool.swift 3 | // Linkeeper 4 | // 5 | // Created by Om Chachad on 05/01/24. 6 | // 7 | 8 | import Foundation 9 | 10 | extension Bool: Comparable { 11 | /// This function allows Comprable conformance for Bool 12 | public static func < (lhs: Bool, rhs: Bool) -> Bool { 13 | return lhs == false && rhs == true 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /Linkeeper/Assets.xcassets/AppIcon.solidimagestack/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | }, 6 | "layers" : [ 7 | { 8 | "filename" : "Front.solidimagestacklayer" 9 | }, 10 | { 11 | "filename" : "Middle.solidimagestacklayer" 12 | }, 13 | { 14 | "filename" : "Back.solidimagestacklayer" 15 | } 16 | ] 17 | } 18 | -------------------------------------------------------------------------------- /Linkeeper/Assets.xcassets/ClassicIcon.solidimagestack/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | }, 6 | "layers" : [ 7 | { 8 | "filename" : "Front.solidimagestacklayer" 9 | }, 10 | { 11 | "filename" : "Middle.solidimagestacklayer" 12 | }, 13 | { 14 | "filename" : "Back.solidimagestacklayer" 15 | } 16 | ] 17 | } 18 | -------------------------------------------------------------------------------- /Linkeeper/Assets.xcassets/DarkIcon.solidimagestack/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | }, 6 | "layers" : [ 7 | { 8 | "filename" : "Front.solidimagestacklayer" 9 | }, 10 | { 11 | "filename" : "Middle.solidimagestacklayer" 12 | }, 13 | { 14 | "filename" : "Back.solidimagestacklayer" 15 | } 16 | ] 17 | } 18 | -------------------------------------------------------------------------------- /Linkeeper/Helpers/Global/reloadAllWidgets.swift: -------------------------------------------------------------------------------- 1 | // 2 | // reloadAllWidgets.swift 3 | // Linkeeper 4 | // 5 | // Created by Om Chachad on 05/01/24. 6 | // 7 | 8 | #if canImport(WidgetKit) 9 | import WidgetKit 10 | #endif 11 | 12 | func reloadAllWidgets() { 13 | #if canImport(WidgetKit) 14 | if #available(iOS 17.0, macOS 14.0, *) { 15 | WidgetCenter.shared.reloadAllTimelines() 16 | } 17 | #endif 18 | } 19 | -------------------------------------------------------------------------------- /Linkeeper/StoreKit/Tips.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Tip1 6 | 🙏 7 | Tip2 8 | 💝 9 | Tip3 10 | 🌟 11 | Tip4 12 | 💎 13 | 14 | 15 | -------------------------------------------------------------------------------- /Linkeeper/Assets.xcassets/Om.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "filename" : "Om.jpg", 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "filename" : "Om.jpg", 14 | "idiom" : "universal", 15 | "scale" : "3x" 16 | } 17 | ], 18 | "info" : { 19 | "author" : "xcode", 20 | "version" : 1 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /Linkeeper/Assets.xcassets/ClassicIconImage.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "filename" : "512.png", 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "filename" : "1024.png", 14 | "idiom" : "universal", 15 | "scale" : "3x" 16 | } 17 | ], 18 | "info" : { 19 | "author" : "xcode", 20 | "version" : 1 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /Linkeeper/Assets.xcassets/DarkIconImage.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "filename" : "Frame 3-7 1.png", 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "filename" : "Frame 3-7.png", 14 | "idiom" : "universal", 15 | "scale" : "3x" 16 | } 17 | ], 18 | "info" : { 19 | "author" : "xcode", 20 | "version" : 1 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /PrivacyInfo.xcprivacy: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | NSPrivacyAccessedAPITypes 6 | 7 | 8 | NSPrivacyAccessedAPIType 9 | NSPrivacyAccessedAPICategoryUserDefaults 10 | NSPrivacyAccessedAPITypeReasons 11 | 12 | 1C8F.1 13 | 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /Linkeeper/Helpers/Extensions/Shape.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Shape.swift 3 | // Linkeeper 4 | // 5 | // Created by Om Chachad on 16/05/23. 6 | // 7 | 8 | import Foundation 9 | import SwiftUI 10 | 11 | extension Shape { 12 | func gradientify(with color: Color) -> some View { 13 | Group { 14 | if #available(iOS 16.0, macOS 13.0, *) { 15 | self 16 | .fill(color.gradient) 17 | } else { 18 | self 19 | .fill(color.gradientify()) 20 | } 21 | } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /Linkeeper/Helpers/Extensions/URL.swift: -------------------------------------------------------------------------------- 1 | // 2 | // URL.swift 3 | // Linkeeper 4 | // 5 | // Created by Om Chachad on 16/05/23. 6 | // 7 | 8 | import Foundation 9 | 10 | extension URL { 11 | var sanitise: URL { 12 | // Check if the scheme already exists 13 | if self.scheme == nil { 14 | // Append "http://" to the URL if it doesn't have a scheme 15 | let urlString = "https://" + self.absoluteString 16 | print(urlString) 17 | return URL(string: urlString) ?? self 18 | } 19 | return self 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /Linkeeper/Helpers/Extensions/String.swift: -------------------------------------------------------------------------------- 1 | // 2 | // String.swift 3 | // Linkeeper 4 | // 5 | // Created by Om Chachad on 16/05/23. 6 | // 7 | 8 | import Foundation 9 | 10 | 11 | extension String { 12 | var isValidURL: Bool { 13 | let detector = try! NSDataDetector(types: NSTextCheckingResult.CheckingType.link.rawValue) 14 | if let match = detector.firstMatch(in: self, options: [], range: NSRange(location: 0, length: self.utf16.count)) { 15 | return match.range.length == self.utf16.count 16 | } else { 17 | return false 18 | } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /Linkeeper/Helpers/Enums/EditState.swift: -------------------------------------------------------------------------------- 1 | // 2 | // EditState.swift 3 | // Linkeeper 4 | // 5 | // Created by Om Chachad on 07/01/24. 6 | // 7 | 8 | import Foundation 9 | import SwiftUI 10 | 11 | #if os(macOS) 12 | enum EditMode { 13 | case active 14 | case inactive 15 | case transient 16 | } 17 | 18 | private struct EditModeKey: EnvironmentKey { 19 | static let defaultValue: EditMode = .inactive 20 | } 21 | 22 | extension EnvironmentValues { 23 | var editMode: EditMode { 24 | get { self[EditModeKey.self] } 25 | set { self[EditModeKey.self] = newValue } 26 | } 27 | } 28 | #endif 29 | -------------------------------------------------------------------------------- /Linkeeper/Helpers/Drag & Dropping/FolderDropItem.swift: -------------------------------------------------------------------------------- 1 | // 2 | // FolderDropItem.swift 3 | // Linkeeper 4 | // 5 | // Created by Om Chachad on 17/06/23. 6 | // 7 | 8 | import Foundation 9 | import CoreTransferable 10 | 11 | @available(iOS 16.0, macOS 13.0, *) 12 | enum FolderDropItem: Codable, Transferable { 13 | case none 14 | case folder(DraggableFolder?) 15 | 16 | static var transferRepresentation: some TransferRepresentation { 17 | ProxyRepresentation { .folder($0) } 18 | } 19 | 20 | var folder: DraggableFolder? { 21 | switch self { 22 | case .folder(let folder): return folder 23 | default: return nil 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /Linkeeper/CoreData/StoreURL Extension.swift: -------------------------------------------------------------------------------- 1 | // 2 | // StoreURL Extension.swift 3 | // Linkeeper 4 | // 5 | // Created by Om Chachad on 11/07/23. 6 | // 7 | 8 | import Foundation 9 | 10 | public extension URL { 11 | 12 | /// Returns a URL for the given app group and database pointing to the sqlite database. 13 | static func storeURL(for appGroup: String, databaseName: String) -> URL { 14 | guard let fileContainer = FileManager.default.containerURL(forSecurityApplicationGroupIdentifier: appGroup) else { 15 | fatalError("Shared file container could not be created.") 16 | } 17 | 18 | return fileContainer.appendingPathComponent("\(databaseName).sqlite") 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /Share Extension Vision/Share Extension Vision.entitlements: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | aps-environment 6 | development 7 | com.apple.developer.icloud-container-identifiers 8 | 9 | iCloud.org.starlightapps.Linkeeper 10 | 11 | com.apple.developer.icloud-services 12 | 13 | CloudKit 14 | 15 | com.apple.security.application-groups 16 | 17 | group.starlightapps.linkeeper 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /Linkeeper.xcodeproj/project.xcworkspace/xcuserdata/omchachad.xcuserdatad/WorkspaceSettings.xcsettings: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | BuildLocationStyle 6 | UseAppPreferences 7 | CustomBuildLocationType 8 | RelativeToDerivedData 9 | DerivedDataLocationStyle 10 | Default 11 | IssueFilterStyle 12 | ShowActiveSchemeOnly 13 | LiveSourceIssuesEnabled 14 | 15 | ShowSharedSchemesAutomaticallyEnabled 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /Linkeeper/Helpers/Extensions/Color.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Color.swift 3 | // Linkeeper 4 | // 5 | // Created by Om Chachad on 16/05/23. 6 | // 7 | 8 | import Foundation 9 | import SwiftUI 10 | 11 | extension Color { 12 | func gradientify(colorScheme: ColorScheme = .light) -> LinearGradient { 13 | let lightModeGradient = LinearGradient(colors: [self.opacity(0.7), self], startPoint: .top, endPoint: .bottom) 14 | let darkModeGradient = LinearGradient(colors: [self.opacity(1.2), self.opacity(0.8)], startPoint: .top, endPoint: .bottom) 15 | 16 | // Return the appropriate gradient based on the color scheme 17 | return colorScheme == .dark ? darkModeGradient : lightModeGradient 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /Linkeeper/Helpers/Enums/SortDirection.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SortDirection.swift 3 | // Linkeeper 4 | // 5 | // Created by Om Chachad on 05/01/24. 6 | // 7 | 8 | import Foundation 9 | 10 | enum SortDirection: String, Codable, CaseIterable { 11 | case ascending 12 | case descending 13 | 14 | var label: String { 15 | let sortMethod: SortMethod = SortMethod(rawValue: UserDefaults.standard.string(forKey: "SortMethod") ?? "Date Created") ?? .dateCreated 16 | switch(sortMethod) { 17 | case .dateCreated: 18 | return self == .ascending ? "Oldest First" : "Newest First" 19 | case .title: 20 | return self == .ascending ? "Ascending" : "Descending" 21 | } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /Linkeeper/App Intents/Apple Intelligence Domains/DeleteBookmarks.swift: -------------------------------------------------------------------------------- 1 | // 2 | // DeleteBookmarks.swift 3 | // Linkeeper 4 | // 5 | // Created by Om Chachad on 1/23/25. 6 | // 7 | 8 | import Foundation 9 | import AppIntents 10 | 11 | @available(iOS 18.0, macOS 15.0, *) 12 | @AssistantIntent(schema: .browser.deleteBookmarks) 13 | struct DeleteBookmarksIntent: DeleteIntent { 14 | var entities: [BookmarkEntity] 15 | 16 | static var isDiscoverable: Bool = false 17 | 18 | func perform() async throws -> some IntentResult { 19 | for entity in entities { 20 | BookmarksManager.shared.deleteBookmark(withId: entity.id) 21 | } 22 | 23 | reloadAllWidgets() 24 | return .result() 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /BookmarksWidget/ConfigurationAppIntent.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppIntent.swift 3 | // BookmarksWidget 4 | // 5 | // Created by Om Chachad on 21/12/23. 6 | // 7 | 8 | import WidgetKit 9 | import AppIntents 10 | 11 | struct ConfigurationAppIntent: WidgetConfigurationIntent { 12 | static var title: LocalizedStringResource = "Folder" 13 | static var description = IntentDescription("Get quick access to one of your folders.") 14 | 15 | // An example configurable parameter. 16 | @Parameter(title: "Folder", default: nil) 17 | var folder: FolderEntity? 18 | } 19 | 20 | extension ConfigurationAppIntent { 21 | static var allBookmarks: ConfigurationAppIntent { 22 | let intent = ConfigurationAppIntent() 23 | intent.folder = nil 24 | return intent 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /Linkeeper/Helpers/Enums/ViewOption.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ViewOption.swift 3 | // Linkeeper 4 | // 5 | // Created by Om Chachad on 05/01/24. 6 | // 7 | 8 | import Foundation 9 | 10 | enum ViewOption: String, Codable, CaseIterable { 11 | case grid 12 | case list 13 | case table 14 | 15 | var iconString: String { 16 | switch(self) { 17 | case .grid: 18 | return "square.grid.2x2" 19 | case .list: 20 | return "list.bullet" 21 | case .table: 22 | return "table" 23 | } 24 | } 25 | 26 | var title: String { 27 | switch(self) { 28 | case .grid: 29 | return "Grid" 30 | case .list: 31 | return "List" 32 | case .table: 33 | return "Table" 34 | } 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /Linkeeper/Assets.xcassets/ShortcutIconForeground.colorset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "colors" : [ 3 | { 4 | "color" : { 5 | "color-space" : "extended-gray", 6 | "components" : { 7 | "alpha" : "1.000", 8 | "white" : "1.000" 9 | } 10 | }, 11 | "idiom" : "universal" 12 | }, 13 | { 14 | "appearances" : [ 15 | { 16 | "appearance" : "luminosity", 17 | "value" : "dark" 18 | } 19 | ], 20 | "color" : { 21 | "color-space" : "display-p3", 22 | "components" : { 23 | "alpha" : "1.000", 24 | "blue" : "1.000", 25 | "green" : "1.000", 26 | "red" : "1.000" 27 | } 28 | }, 29 | "idiom" : "universal" 30 | } 31 | ], 32 | "info" : { 33 | "author" : "xcode", 34 | "version" : 1 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /Linkeeper/Helpers/AlertView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AlertView.swift 3 | // Linkeeper 4 | // 5 | // Created by Om Chachad on 20/05/23. 6 | // 7 | import SwiftUI 8 | 9 | struct AlertView: View { 10 | var icon: String 11 | var title: String 12 | 13 | var body: some View { 14 | HStack { 15 | Image(systemName: icon) 16 | Text(title) 17 | } 18 | .font(.headline) 19 | .padding() 20 | .background(.thickMaterial.opacity(0.9)) 21 | .clipShape(Capsule()) 22 | .background { 23 | Capsule() 24 | .stroke(.secondary.opacity(0.2), lineWidth: 2) 25 | } 26 | .shadow(color: .secondary.opacity(0.3), radius: 10) 27 | } 28 | } 29 | 30 | struct AlertView_Previews: PreviewProvider { 31 | static var previews: some View { 32 | AlertView(icon: "list.and.film", title: "Playing Next") 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /Linkeeper/Helpers/macOS Bridge.swift: -------------------------------------------------------------------------------- 1 | // 2 | // macOS Bridge.swift 3 | // Linkeeper 4 | // 5 | // Created by Om Chachad on 07/01/24. 6 | // 7 | 8 | #if os(macOS) 9 | import Foundation 10 | import Cocoa 11 | import SwiftUI 12 | 13 | typealias UIImage = NSImage 14 | typealias UIColor = NSColor 15 | 16 | extension Color { 17 | init(uiColor: UIColor) { 18 | self.init(nsColor: uiColor) 19 | } 20 | } 21 | extension Image { 22 | init(uiImage: UIImage) { 23 | self.init(nsImage: uiImage) 24 | } 25 | } 26 | 27 | extension NSImage { 28 | func tinted(with color: NSColor) -> NSImage { 29 | let image = self.copy() as! NSImage 30 | image.lockFocus() 31 | color.set() 32 | let imageRect = NSRect(origin: .zero, size: image.size) 33 | imageRect.fill(using: .sourceAtop) 34 | image.unlockFocus() 35 | return image 36 | } 37 | } 38 | #endif 39 | -------------------------------------------------------------------------------- /Linkeeper/Assets.xcassets/primaryInverted.colorset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "colors" : [ 3 | { 4 | "color" : { 5 | "color-space" : "srgb", 6 | "components" : { 7 | "alpha" : "1.000", 8 | "blue" : "1.000", 9 | "green" : "1.000", 10 | "red" : "1.000" 11 | } 12 | }, 13 | "idiom" : "universal" 14 | }, 15 | { 16 | "appearances" : [ 17 | { 18 | "appearance" : "luminosity", 19 | "value" : "dark" 20 | } 21 | ], 22 | "color" : { 23 | "color-space" : "srgb", 24 | "components" : { 25 | "alpha" : "1.000", 26 | "blue" : "0.000", 27 | "green" : "0.000", 28 | "red" : "0.000" 29 | } 30 | }, 31 | "idiom" : "universal" 32 | } 33 | ], 34 | "info" : { 35 | "author" : "xcode", 36 | "version" : 1 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /Share Extension/ShareExtension.entitlements: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | aps-environment 6 | development 7 | com.apple.developer.aps-environment 8 | development 9 | com.apple.developer.icloud-container-identifiers 10 | 11 | iCloud.org.starlightapps.Linkeeper 12 | 13 | com.apple.developer.icloud-services 14 | 15 | CloudKit 16 | 17 | com.apple.security.app-sandbox 18 | 19 | com.apple.security.application-groups 20 | 21 | group.starlightapps.linkeeper 22 | 23 | com.apple.security.network.client 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /Linkeeper/Helpers/Drag & Dropping/DraggableFolder.swift: -------------------------------------------------------------------------------- 1 | // 2 | // DraggableFolder.swift 3 | // Linkeeper 4 | // 5 | // Created by Om Chachad on 17/06/23. 6 | // 7 | 8 | import Foundation 9 | import CoreTransferable 10 | import UniformTypeIdentifiers 11 | 12 | struct DraggableFolder: Codable { 13 | let id: UUID 14 | let title: String 15 | let symbol: String 16 | let index: Int 17 | let isPinned: Bool 18 | 19 | var folder: Folder? { 20 | FoldersManager.shared.findFolder(withId: self.id) 21 | } 22 | } 23 | 24 | @available(iOS 16.0, macOS 13.0, *) 25 | extension DraggableFolder: Transferable { 26 | static var transferRepresentation: some TransferRepresentation { 27 | CodableRepresentation(contentType: .draggableFolder) 28 | } 29 | } 30 | 31 | extension UTType { 32 | static var draggableFolder: UTType = UTType(exportedAs: "org.starlightapps.Linkeeper.folder") 33 | } 34 | -------------------------------------------------------------------------------- /Linkeeper/Assets.xcassets/AllBookmarksColor.colorset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "colors" : [ 3 | { 4 | "color" : { 5 | "color-space" : "display-p3", 6 | "components" : { 7 | "alpha" : "1.000", 8 | "blue" : "0.267", 9 | "green" : "0.271", 10 | "red" : "0.275" 11 | } 12 | }, 13 | "idiom" : "universal" 14 | }, 15 | { 16 | "appearances" : [ 17 | { 18 | "appearance" : "luminosity", 19 | "value" : "dark" 20 | } 21 | ], 22 | "color" : { 23 | "color-space" : "display-p3", 24 | "components" : { 25 | "alpha" : "1.000", 26 | "blue" : "0.400", 27 | "green" : "0.388", 28 | "red" : "0.388" 29 | } 30 | }, 31 | "idiom" : "universal" 32 | } 33 | ], 34 | "info" : { 35 | "author" : "xcode", 36 | "version" : 1 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /Linkeeper/Assets.xcassets/DetailsEditBarColor.colorset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "colors" : [ 3 | { 4 | "color" : { 5 | "color-space" : "display-p3", 6 | "components" : { 7 | "alpha" : "1.000", 8 | "blue" : "0.839", 9 | "green" : "0.820", 10 | "red" : "0.820" 11 | } 12 | }, 13 | "idiom" : "universal" 14 | }, 15 | { 16 | "appearances" : [ 17 | { 18 | "appearance" : "luminosity", 19 | "value" : "dark" 20 | } 21 | ], 22 | "color" : { 23 | "color-space" : "display-p3", 24 | "components" : { 25 | "alpha" : "1.000", 26 | "blue" : "0.235", 27 | "green" : "0.227", 28 | "red" : "0.227" 29 | } 30 | }, 31 | "idiom" : "universal" 32 | } 33 | ], 34 | "info" : { 35 | "author" : "xcode", 36 | "version" : 1 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /Linkeeper/Assets.xcassets/GridItemBackground.colorset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "colors" : [ 3 | { 4 | "color" : { 5 | "color-space" : "display-p3", 6 | "components" : { 7 | "alpha" : "1.000", 8 | "blue" : "0.771", 9 | "green" : "0.749", 10 | "red" : "0.750" 11 | } 12 | }, 13 | "idiom" : "universal" 14 | }, 15 | { 16 | "appearances" : [ 17 | { 18 | "appearance" : "luminosity", 19 | "value" : "dark" 20 | } 21 | ], 22 | "color" : { 23 | "color-space" : "display-p3", 24 | "components" : { 25 | "alpha" : "1.000", 26 | "blue" : "0.366", 27 | "green" : "0.355", 28 | "red" : "0.356" 29 | } 30 | }, 31 | "idiom" : "universal" 32 | } 33 | ], 34 | "info" : { 35 | "author" : "xcode", 36 | "version" : 1 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /Linkeeper/Assets.xcassets/ShortcutBackground1.colorset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "colors" : [ 3 | { 4 | "color" : { 5 | "color-space" : "display-p3", 6 | "components" : { 7 | "alpha" : "1.000", 8 | "blue" : "0.749", 9 | "green" : "0.247", 10 | "red" : "0.690" 11 | } 12 | }, 13 | "idiom" : "universal" 14 | }, 15 | { 16 | "appearances" : [ 17 | { 18 | "appearance" : "luminosity", 19 | "value" : "dark" 20 | } 21 | ], 22 | "color" : { 23 | "color-space" : "display-p3", 24 | "components" : { 25 | "alpha" : "1.000", 26 | "blue" : "0.749", 27 | "green" : "0.247", 28 | "red" : "0.690" 29 | } 30 | }, 31 | "idiom" : "universal" 32 | } 33 | ], 34 | "info" : { 35 | "author" : "xcode", 36 | "version" : 1 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /Linkeeper/Assets.xcassets/ShortcutBackground2.colorset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "colors" : [ 3 | { 4 | "color" : { 5 | "color-space" : "display-p3", 6 | "components" : { 7 | "alpha" : "1.000", 8 | "blue" : "0.251", 9 | "green" : "0.671", 10 | "red" : "0.906" 11 | } 12 | }, 13 | "idiom" : "universal" 14 | }, 15 | { 16 | "appearances" : [ 17 | { 18 | "appearance" : "luminosity", 19 | "value" : "dark" 20 | } 21 | ], 22 | "color" : { 23 | "color-space" : "display-p3", 24 | "components" : { 25 | "alpha" : "1.000", 26 | "blue" : "0.251", 27 | "green" : "0.671", 28 | "red" : "0.906" 29 | } 30 | }, 31 | "idiom" : "universal" 32 | } 33 | ], 34 | "info" : { 35 | "author" : "xcode", 36 | "version" : 1 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /BookmarksWidget/Assets.xcassets/WidgetBackground.colorset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "colors" : [ 3 | { 4 | "color" : { 5 | "color-space" : "display-p3", 6 | "components" : { 7 | "alpha" : "1.000", 8 | "blue" : "1.000", 9 | "green" : "1.000", 10 | "red" : "0.988" 11 | } 12 | }, 13 | "idiom" : "universal" 14 | }, 15 | { 16 | "appearances" : [ 17 | { 18 | "appearance" : "luminosity", 19 | "value" : "dark" 20 | } 21 | ], 22 | "color" : { 23 | "color-space" : "display-p3", 24 | "components" : { 25 | "alpha" : "1.000", 26 | "blue" : "0.200", 27 | "green" : "0.200", 28 | "red" : "0.197" 29 | } 30 | }, 31 | "idiom" : "universal" 32 | } 33 | ], 34 | "info" : { 35 | "author" : "xcode", 36 | "version" : 1 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /Share Extension Mac/Share_Extension_Mac.entitlements: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | com.apple.developer.aps-environment 6 | development 7 | com.apple.developer.icloud-container-identifiers 8 | 9 | iCloud.org.starlightapps.Linkeeper 10 | 11 | com.apple.developer.icloud-services 12 | 13 | CloudKit 14 | 15 | com.apple.security.app-sandbox 16 | 17 | com.apple.security.application-groups 18 | 19 | group.starlightapps.linkeeper 20 | 21 | com.apple.security.files.user-selected.read-only 22 | 23 | com.apple.security.network.client 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /Linkeeper/Helpers/Drag & Dropping/BookmarkDropItem.swift: -------------------------------------------------------------------------------- 1 | // 2 | // BookmarkDropItem.swift 3 | // Linkeeper 4 | // 5 | // Created by Om Chachad on 30/06/23. 6 | // 7 | 8 | import Foundation 9 | import CoreTransferable 10 | 11 | @available(iOS 16.0, macOS 13.0, *) 12 | enum BookmarkDropItem: Codable, Transferable { 13 | case none 14 | case bookmark(DraggableBookmark?) 15 | case url(URL) 16 | 17 | static var transferRepresentation: some TransferRepresentation { 18 | ProxyRepresentation { .url($0) } 19 | ProxyRepresentation { .bookmark($0) } 20 | } 21 | 22 | var bookmark: DraggableBookmark? { 23 | switch self { 24 | case .bookmark(let bookmark): return bookmark 25 | default: return nil 26 | } 27 | } 28 | 29 | var url: URL? { 30 | switch self { 31 | case.url(let url): return url 32 | default: return nil 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /Linkeeper/Helpers/AppConfiguration.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppConfiguration.swift 3 | // Linkeeper 4 | // 5 | // Created by Om Chachad on 3/21/25. 6 | // Source: https://stackoverflow.com/a/33830605 7 | 8 | import Foundation 9 | 10 | enum AppConfiguration { 11 | case Debug 12 | case TestFlight 13 | case AppStore 14 | } 15 | 16 | struct Config { 17 | // This is private because the use of 'appConfiguration' is preferred. 18 | private static let isTestFlight = Bundle.main.appStoreReceiptURL?.lastPathComponent == "sandboxReceipt" 19 | 20 | // This can be used to add debug statements. 21 | static var isDebug: Bool { 22 | #if DEBUG 23 | return true 24 | #else 25 | return false 26 | #endif 27 | } 28 | 29 | static var appConfiguration: AppConfiguration { 30 | if isDebug { 31 | return .Debug 32 | } else if isTestFlight { 33 | return .TestFlight 34 | } else { 35 | return .AppStore 36 | } 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /Linkeeper/Linkeeper.entitlements: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | aps-environment 6 | development 7 | com.apple.developer.icloud-container-identifiers 8 | 9 | iCloud.org.starlightapps.Linkeeper 10 | 11 | com.apple.developer.icloud-services 12 | 13 | CloudKit 14 | 15 | com.apple.security.app-sandbox 16 | 17 | com.apple.security.application-groups 18 | 19 | group.starlightapps.linkeeper 20 | 21 | com.apple.security.files.downloads.read-write 22 | 23 | com.apple.security.files.user-selected.read-write 24 | 25 | com.apple.security.network.client 26 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /Share Extension/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | NSExtension 6 | 7 | NSExtensionAttributes 8 | 9 | NSExtensionActivationRule 10 | 11 | NSExtensionActivationSupportsWebURLWithMaxCount 12 | 1 13 | NSExtensionActivationSupportsWebPageWithMaxCount 14 | 1 15 | NSExtensionActivationSupportsText 16 | 17 | NSExtensionActivationSupportsImageWithMaxCount 18 | 1 19 | 20 | 21 | NSExtensionPointIdentifier 22 | com.apple.share-services 23 | NSExtensionPrincipalClass 24 | $(PRODUCT_MODULE_NAME).ShareViewController 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /Share Extension Vision/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | NSExtension 6 | 7 | NSExtensionAttributes 8 | 9 | NSExtensionActivationRule 10 | 11 | NSExtensionActivationSupportsWebURLWithMaxCount 12 | 1 13 | NSExtensionActivationSupportsWebPageWithMaxCount 14 | 1 15 | NSExtensionActivationSupportsText 16 | 17 | NSExtensionActivationSupportsImageWithMaxCount 18 | 1 19 | 20 | 21 | NSExtensionPointIdentifier 22 | com.apple.share-services 23 | NSExtensionPrincipalClass 24 | $(PRODUCT_MODULE_NAME).ShareViewController 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /Linkeeper/Helpers/CommandKeyObserver.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CommandKeyObserver.swift 3 | // Linkeeper 4 | // 5 | // Created by Om Chachad on 7/15/25. 6 | // 7 | 8 | import SwiftUI 9 | 10 | struct CommandKeyObserver: ViewModifier { 11 | 12 | @Binding var isCommandKeyPressed: Bool 13 | 14 | func body(content: Content) -> some View { 15 | content 16 | #if os(macOS) 17 | .onAppear(perform: { 18 | NSEvent.addLocalMonitorForEvents(matching: .flagsChanged) { event in 19 | self.isCommandKeyPressed = event.modifierFlags.contains(.command) 20 | return event 21 | } 22 | }) 23 | #endif 24 | } 25 | } 26 | 27 | extension View { 28 | func commandKeyObserver(isCommandKeyPressed: Binding) -> some View { 29 | self 30 | .modifier(CommandKeyObserver(isCommandKeyPressed: isCommandKeyPressed)) 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /BookmarksWidget/BookmarksWidgetExtension.entitlements: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | aps-environment 6 | development 7 | com.apple.developer.aps-environment 8 | development 9 | com.apple.developer.icloud-container-identifiers 10 | 11 | iCloud.org.starlightapps.Linkeeper 12 | 13 | com.apple.developer.icloud-services 14 | 15 | CloudKit 16 | 17 | com.apple.security.app-sandbox 18 | 19 | com.apple.security.application-groups 20 | 21 | group.starlightapps.linkeeper 22 | 23 | com.apple.security.files.user-selected.read-write 24 | 25 | com.apple.security.network.client 26 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /Linkeeper/Helpers/Extensions/TextEditor.swift: -------------------------------------------------------------------------------- 1 | // 2 | // TextEditor.swift 3 | // Linkeeper 4 | // 5 | // Created by Om Chachad on 16/05/23. 6 | // 7 | 8 | import Foundation 9 | import SwiftUI 10 | 11 | extension TextEditor { 12 | func placeholder(_ text: String, contents: String) -> some View { 13 | ZStack(alignment: Alignment(horizontal: .center, vertical: .top)) { 14 | self 15 | .padding(EdgeInsets(top: -8, leading: -4, bottom: -7, trailing: -4)) 16 | if contents.isEmpty { 17 | HStack { 18 | Text(text) 19 | #if !os(macOS) 20 | .foregroundColor(Color(UIColor.placeholderText)) 21 | #else 22 | .foregroundColor(.secondary) 23 | #endif 24 | .allowsHitTesting(false) 25 | Spacer() 26 | } 27 | } 28 | } 29 | .padding(.top, 7) 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /Linkeeper/AppDelegate (Mac).swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate (Mac).swift 3 | // Linkeeper 4 | // 5 | // Created by Om Chachad on 3/2/25. 6 | // 7 | 8 | import Foundation 9 | import Cocoa 10 | import SwiftUI 11 | 12 | class AppDelegate: NSObject, NSApplicationDelegate { 13 | 14 | func applicationDockMenu(_ sender: NSApplication) -> NSMenu? { 15 | let menu = NSMenu() 16 | 17 | let item1 = NSMenuItem(title: "Add Bookmark", action: #selector(addBookmark), keyEquivalent: "") 18 | item1.target = self 19 | 20 | menu.addItem(item1) 21 | 22 | return menu 23 | } 24 | 25 | @objc func addBookmark() { 26 | if NSApplication.shared.windows.first?.title.isEmpty != false { 27 | let url = URL(string: "linkeeper://addBookmark")! 28 | NSWorkspace.shared.open(url) 29 | } else { 30 | NSApp.activate(ignoringOtherApps: true) 31 | NotificationCenter.default.post(name: .addBookmark, object: nil) 32 | } 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /Linkeeper/Helpers/Drag & Dropping/DraggableBookmark.swift: -------------------------------------------------------------------------------- 1 | // 2 | // DraggableBookmark.swift 3 | // Linkeeper 4 | // 5 | // Created by Om Chachad on 29/06/23. 6 | // 7 | 8 | import Foundation 9 | import CoreTransferable 10 | import UniformTypeIdentifiers 11 | 12 | struct DraggableBookmark: Codable { 13 | let id: UUID 14 | let title: String 15 | let url: URL 16 | let notes: String 17 | let dateAdded: Date 18 | let isFavorited: Bool 19 | 20 | var bookmark: Bookmark? { 21 | BookmarksManager.shared.findBookmark(withId: self.id) 22 | } 23 | } 24 | 25 | @available(iOS 16.0, macOS 13.0, *) 26 | extension DraggableBookmark: Transferable { 27 | static var transferRepresentation: some TransferRepresentation { 28 | CodableRepresentation(contentType: .draggableBookmark) 29 | ProxyRepresentation(exporting: \.url.absoluteString) 30 | } 31 | } 32 | 33 | extension UTType { 34 | static var draggableBookmark: UTType = UTType(exportedAs: "org.starlightapps.Linkeeper.bookmark") 35 | } 36 | -------------------------------------------------------------------------------- /Share Extension Mac/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleIconFile 6 | icon 7 | NSExtension 8 | 9 | NSExtensionAttributes 10 | 11 | NSExtensionActivationRule 12 | 13 | NSExtensionActivationSupportsWebURLWithMaxCount 14 | 1 15 | NSExtensionActivationSupportsWebPageWithMaxCount 16 | 1 17 | NSExtensionActivationSupportsText 18 | 19 | NSExtensionActivationSupportsImageWithMaxCount 20 | 1 21 | 22 | 23 | NSExtensionPointIdentifier 24 | com.apple.share-services 25 | NSExtensionPrincipalClass 26 | $(PRODUCT_MODULE_NAME).ShareViewController 27 | 28 | 29 | 30 | -------------------------------------------------------------------------------- /Linkeeper/Helpers/QuickActionsHandler.swift: -------------------------------------------------------------------------------- 1 | // 2 | // QuickActionsHandler.swift 3 | // Linkeeper 4 | // 5 | // Created by Om Chachad on 3/2/25. 6 | // 7 | 8 | import Foundation 9 | import UIKit 10 | 11 | class QuickActionsHandler: ObservableObject { 12 | static let shared = QuickActionsHandler() 13 | 14 | private init() {} 15 | 16 | func handleItem(_ item: UIApplicationShortcutItem) { 17 | guard let actionItem = QuickAction.allCases.first(where: {$0.id == item.type}) else { return } 18 | switch actionItem { 19 | case .addBookmark: 20 | if let url = URL(string: "linkeeper://addBookmark") { 21 | UIApplication.shared.open(url, options: [:], completionHandler: nil) 22 | } 23 | } 24 | } 25 | } 26 | 27 | enum QuickAction: Hashable, CaseIterable { 28 | case addBookmark 29 | 30 | var id: String { 31 | switch self { 32 | case .addBookmark: 33 | return "org.starlightapps.Linkeeper.AddBookmark" 34 | } 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /Linkeeper/Helpers/Functions/Metadata Fetcher.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Metadata Fetcher.swift 3 | // Linkeeper 4 | // 5 | // Created by Om Chachad on 07/07/23. 6 | // 7 | 8 | import Foundation 9 | import LinkPresentation 10 | 11 | @MainActor 12 | func startFetchingMetadata(for url: URL, fetchSubresources: Bool, timeout: TimeInterval?) async throws -> LPLinkMetadata? { 13 | return try? await withCheckedThrowingContinuation { continuation in 14 | let metadataProvider = LPMetadataProvider() 15 | metadataProvider.shouldFetchSubresources = fetchSubresources 16 | metadataProvider.timeout = timeout ?? metadataProvider.timeout 17 | 18 | metadataProvider.startFetchingMetadata(for: url) { metadata, error in 19 | if error != nil { 20 | continuation.resume(returning: nil) 21 | } else if let metadata = metadata { 22 | continuation.resume(returning: metadata) 23 | } else { 24 | continuation.resume(returning: nil) 25 | } 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /Linkeeper/Helpers/Drag & Dropping/DragExtension.swift: -------------------------------------------------------------------------------- 1 | // 2 | // DragExtension.swift 3 | // Linkeeper 4 | // 5 | // Created by Om Chachad on 29/06/23. 6 | // 7 | 8 | import Foundation 9 | import SwiftUI 10 | 11 | extension View { 12 | func draggable(_ bookmark: Bookmark) -> some View { 13 | Group { 14 | if #available(iOS 16.0, macOS 13.0, *) { 15 | self 16 | .draggable(bookmark.draggable) 17 | } else { 18 | self 19 | .onDrag { NSItemProvider(object: bookmark.wrappedURL as NSURL) } 20 | } 21 | } 22 | } 23 | 24 | // func draggable(_ folder: Folder) -> some View { 25 | // Group { 26 | // if #available(iOS 16.0, macOS 13.0, *) { 27 | // self 28 | // .draggable(folder.draggable) 29 | // } else { 30 | // self 31 | // .onDrag { 32 | // NSItemProvider(object: folder.wrappedUUID as NSString) 33 | // } 34 | // } 35 | // } 36 | // } 37 | } 38 | -------------------------------------------------------------------------------- /Linkeeper/AppDelegate (iOS, visionOS).swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.swift 3 | // Linkeeper 4 | // 5 | // Created by Om Chachad on 3/1/25. 6 | // 7 | 8 | import Foundation 9 | import UIKit 10 | 11 | class AppDelegate: NSObject, UIApplicationDelegate { 12 | func application(_ application: UIApplication, configurationForConnecting connectingSceneSession: UISceneSession, options: UIScene.ConnectionOptions) -> UISceneConfiguration { 13 | if let shortcutItem = options.shortcutItem { 14 | QuickActionsHandler.shared.handleItem(shortcutItem) 15 | } 16 | 17 | let sceneConfiguration = UISceneConfiguration(name: "Custom Configuration", sessionRole: connectingSceneSession.role) 18 | sceneConfiguration.delegateClass = CustomSceneDelegate.self 19 | 20 | return sceneConfiguration 21 | } 22 | } 23 | 24 | class CustomSceneDelegate: UIResponder, UIWindowSceneDelegate { 25 | func windowScene(_ windowScene: UIWindowScene, performActionFor shortcutItem: UIApplicationShortcutItem, completionHandler: @escaping (Bool) -> Void) { 26 | QuickActionsHandler.shared.handleItem(shortcutItem) 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /BookmarksWidget/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "mac", 5 | "scale" : "1x", 6 | "size" : "16x16" 7 | }, 8 | { 9 | "idiom" : "mac", 10 | "scale" : "2x", 11 | "size" : "16x16" 12 | }, 13 | { 14 | "idiom" : "mac", 15 | "scale" : "1x", 16 | "size" : "32x32" 17 | }, 18 | { 19 | "idiom" : "mac", 20 | "scale" : "2x", 21 | "size" : "32x32" 22 | }, 23 | { 24 | "idiom" : "mac", 25 | "scale" : "1x", 26 | "size" : "128x128" 27 | }, 28 | { 29 | "idiom" : "mac", 30 | "scale" : "2x", 31 | "size" : "128x128" 32 | }, 33 | { 34 | "idiom" : "mac", 35 | "scale" : "1x", 36 | "size" : "256x256" 37 | }, 38 | { 39 | "idiom" : "mac", 40 | "scale" : "2x", 41 | "size" : "256x256" 42 | }, 43 | { 44 | "idiom" : "mac", 45 | "scale" : "1x", 46 | "size" : "512x512" 47 | }, 48 | { 49 | "idiom" : "mac", 50 | "scale" : "2x", 51 | "size" : "512x512" 52 | } 53 | ], 54 | "info" : { 55 | "author" : "xcode", 56 | "version" : 1 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /Linkeeper/App Intents/Apple Intelligence Domains/CreateBookmark.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CreateBookmark.swift 3 | // Linkeeper 4 | // 5 | // Created by Om Chachad on 1/23/25. 6 | // 7 | 8 | import Foundation 9 | import AppIntents 10 | 11 | @available(iOS 18.0, macOS 18.0, *) 12 | @AssistantIntent(schema: .browser.bookmarkURL) 13 | struct BookmarkURLIntent { 14 | @Parameter(title: "title") 15 | var name: String? 16 | @Parameter(title: "url") 17 | var url: URL 18 | 19 | static var isDiscoverable: Bool = false 20 | 21 | func perform() async throws -> some ReturnsValue { 22 | do { 23 | if let name { 24 | if let bookmarkEntity = try await AddBookmark(bookmarkTitle: name, url: url).perform().value { 25 | return .result(value: BookmarkEntity(fromRegularEntity: bookmarkEntity)) 26 | } 27 | } else { 28 | if let bookmarkEntity = try await AddBookmark(autoTitle: true, url: url).perform().value { 29 | return .result(value: BookmarkEntity(fromRegularEntity: bookmarkEntity)) 30 | } 31 | } 32 | 33 | throw CustomError.message("Could not create bookmark.") 34 | } catch { 35 | throw error 36 | } 37 | } 38 | } 39 | 40 | -------------------------------------------------------------------------------- /Linkeeper/Bookmarks View/Table View/Column.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Column.swift 3 | // Linkeeper 4 | // 5 | // Created by Om Chachad on 7/22/25. 6 | // 7 | 8 | import Foundation 9 | 10 | enum Column: String, CaseIterable { 11 | case host 12 | case folder 13 | case dateAdded 14 | 15 | var title: String { 16 | switch self { 17 | case .host: return "Host" 18 | case .folder: return "Folder" 19 | case .dateAdded: return "Date Added" 20 | } 21 | } 22 | } 23 | 24 | import SwiftUI 25 | 26 | @propertyWrapper 27 | struct AppStorageColumns: DynamicProperty { 28 | @State private var value: [Column] 29 | 30 | private let key: String 31 | private let userDefaults: UserDefaults 32 | 33 | init(wrappedValue: [Column], _ key: String, store: UserDefaults = .standard) { 34 | self.key = key 35 | self.userDefaults = store 36 | let stored = (store.array(forKey: key) as? [String])? 37 | .compactMap(Column.init(rawValue:)) ?? wrappedValue 38 | _value = State(initialValue: stored) 39 | } 40 | 41 | var wrappedValue: [Column] { 42 | get { value } 43 | nonmutating set { 44 | value = newValue 45 | let rawValues = newValue.map(\.rawValue) 46 | userDefaults.set(rawValues, forKey: key) 47 | } 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /Linkeeper.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved: -------------------------------------------------------------------------------- 1 | { 2 | "originHash" : "f312abfedc5d3650ea96a448434575922d4ac1f6b53f841fe01ef701aac72633", 3 | "pins" : [ 4 | { 5 | "identity" : "pow", 6 | "kind" : "remoteSourceControl", 7 | "location" : "https://github.com/EmergeTools/Pow", 8 | "state" : { 9 | "revision" : "cc014eba4b65f689cadc281ad925de5607f2adc9", 10 | "version" : "1.0.3" 11 | } 12 | }, 13 | { 14 | "identity" : "simpletoast", 15 | "kind" : "remoteSourceControl", 16 | "location" : "https://github.com/sanzaru/SimpleToast.git", 17 | "state" : { 18 | "revision" : "7ced9fe877fa1e9aaf9634a611818b84d3317e40", 19 | "version" : "0.7.2" 20 | } 21 | }, 22 | { 23 | "identity" : "swiftsoup", 24 | "kind" : "remoteSourceControl", 25 | "location" : "https://github.com/scinfu/SwiftSoup", 26 | "state" : { 27 | "revision" : "0e96a20ffd37a515c5c963952d4335c89bed50a6", 28 | "version" : "2.6.0" 29 | } 30 | }, 31 | { 32 | "identity" : "swiftui-shimmer", 33 | "kind" : "remoteSourceControl", 34 | "location" : "https://github.com/markiv/SwiftUI-Shimmer", 35 | "state" : { 36 | "revision" : "1f3a620e4abe890d00008cb2af7023d810b433a7", 37 | "version" : "1.4.0" 38 | } 39 | } 40 | ], 41 | "version" : 3 42 | } 43 | -------------------------------------------------------------------------------- /Linkeeper/App Intents/AppShortcuts Provider.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppShortcuts Provider.swift 3 | // Linkeeper 4 | // 5 | // Created by Om Chachad on 06/06/23. 6 | // 7 | 8 | import Foundation 9 | import AppIntents 10 | 11 | @available(iOS 16.0, macOS 13.0, *) 12 | struct LinkeeperShortcuts: AppShortcutsProvider { 13 | static var shortcutTileColor: ShortcutTileColor = .purple 14 | 15 | static var appShortcuts: [AppShortcut] { 16 | AppShortcut( 17 | intent: AddBookmark(), 18 | phrases: ["Add a \(.applicationName) bookmark", 19 | "Create a bookmark in \(.applicationName)", 20 | "Create a \(.applicationName) bookmark", 21 | "Create a new \(.applicationName) bookmark", 22 | "Add a bookmark to \(.applicationName)", 23 | "Keep a link in \(.applicationName)" 24 | ], 25 | shortTitle: "New Bookmark", 26 | systemImageName: "plus" 27 | ) 28 | 29 | AppShortcut( 30 | intent: AddFolder(), 31 | phrases: ["Create a folder in \(.applicationName)", 32 | "Create a new \(.applicationName) folder", 33 | "Add a folder to \(.applicationName)" 34 | ], 35 | shortTitle: "New Folder", 36 | systemImageName: "folder.fill.badge.plus" 37 | ) 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /Linkeeper/Helpers/ModernLabel.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ModernLabel.swift 3 | // Linkeeper 4 | // 5 | // Created by Om Chachad on 2/18/25. 6 | // 7 | 8 | import SwiftUI 9 | 10 | struct ModernLabel: View { 11 | var title: String 12 | var subtitle: String? 13 | var systemImage: String? 14 | 15 | init(_ title: String, subtitle: String? = nil, systemImage: String? = nil) { 16 | self.title = title 17 | self.subtitle = subtitle 18 | self.systemImage = systemImage 19 | } 20 | 21 | var body: some View { 22 | Group { 23 | if #available(iOS 16.0, macOS 13.0, *) { 24 | Text(title) 25 | 26 | if let subtitle { 27 | Text(subtitle) 28 | } 29 | 30 | if let systemImage { 31 | Image(systemName: systemImage) 32 | } 33 | } else { 34 | if let subtitle { 35 | if let systemImage { 36 | Label("\(title)\n\(subtitle)", systemImage: systemImage) 37 | } else { 38 | Text("\(title)\n\(subtitle)") 39 | } 40 | } else if let systemImage { 41 | Label(title, systemImage: systemImage) 42 | } else { 43 | Text(title) 44 | } 45 | } 46 | } 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /Linkeeper/Bookmarks View/Table View/BookmarkTableNameView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // BookmarkTableNameView.swift 3 | // Linkeeper 4 | // 5 | // Created by Om Chachad on 06/01/24. 6 | // 7 | 8 | import Foundation 9 | import SwiftUI 10 | 11 | struct TableNameView: View { 12 | var bookmark: Bookmark 13 | 14 | var cachedPreview: cachedPreview? { 15 | return CacheManager.instance.get(for: bookmark) 16 | } 17 | 18 | var body: some View { 19 | HStack { 20 | Group { 21 | if let preview = cachedPreview?.image { 22 | preview 23 | .resizable() 24 | .scaledToFit() 25 | } else if let firstChar = bookmark.wrappedTitle.first { 26 | Text(String(firstChar)) 27 | .foregroundStyle(.white) 28 | .font(.title) 29 | .frame(width: 44, height: 44) 30 | .background(.tertiary) 31 | } 32 | } 33 | .scaledToFill() 34 | .frame(width: 44, height: 44) 35 | .clipped() 36 | .cornerRadius(8, style: .continuous) 37 | .shadow(radius: 1) 38 | .padding(.vertical, 5) 39 | .padding(.trailing, 10) 40 | 41 | Text(bookmark.wrappedTitle) 42 | } 43 | .task { 44 | bookmark.cachedImage(saveTo: .constant(nil)) // To refresh the cachedPreview 45 | } 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /Linkeeper/Helpers/ShareButton.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ShareButton.swift 3 | // Linkeeper 4 | // 5 | // Created by Om Chachad on 07/07/23. 6 | // 7 | 8 | import Foundation 9 | import SwiftUI 10 | 11 | struct ShareButton: View { 12 | var url: URL 13 | var label: () -> Content 14 | 15 | var body: some View { 16 | Group { 17 | if #available(iOS 16.0, macOS 13.0, *) { 18 | ShareLink(item: url) { 19 | label() 20 | } 21 | } else { 22 | Button { 23 | share(url: url) 24 | } label: { 25 | label() 26 | } 27 | } 28 | } 29 | } 30 | 31 | func share(url: URL) { 32 | #if os(macOS) 33 | if let contentView = NSApp.mainWindow?.contentView { 34 | let sharingPicker = NSSharingServicePicker(items: [url]) 35 | sharingPicker.show(relativeTo: NSZeroRect, of: contentView, preferredEdge: .minY) 36 | } 37 | #else 38 | let activityView = UIActivityViewController(activityItems: [url], applicationActivities: nil) 39 | 40 | let allScenes = UIApplication.shared.connectedScenes 41 | let scene = allScenes.first { $0.activationState == .foregroundActive } 42 | 43 | if let windowScene = scene as? UIWindowScene { 44 | windowScene.keyWindow?.rootViewController?.present(activityView, animated: true, completion: nil) 45 | } 46 | #endif 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /BookmarksWidget/WidgetProvider.swift: -------------------------------------------------------------------------------- 1 | // 2 | // WidgetProvider.swift 3 | // WidgetProvider 4 | // 5 | // Created by Om Chachad on 21/12/23. 6 | // 7 | 8 | import WidgetKit 9 | import SwiftUI 10 | 11 | struct Provider: AppIntentTimelineProvider { 12 | func placeholder(in context: Context) -> BookmarksEntry { 13 | BookmarksEntry(date: Date(), bookmarks: [], configuration: ConfigurationAppIntent()) 14 | } 15 | 16 | func snapshot(for configuration: ConfigurationAppIntent, in context: Context) async -> BookmarksEntry { 17 | BookmarksEntry(date: Date(), bookmarks: BookmarksManager.shared.getAllBookmarks().toEntity(), configuration: configuration) 18 | } 19 | 20 | func timeline(for configuration: ConfigurationAppIntent, in context: Context) async -> Timeline { 21 | var config = configuration 22 | 23 | let bookmarks = configuration.folder.flatMap { folder in 24 | FoldersManager.shared.doesExist(withId: folder.id) ? folder.bookmarks.sorted(by: { $0.dateAdded > $1.dateAdded }) : BookmarksManager.shared.getAllBookmarks().toEntity() 25 | } ?? BookmarksManager.shared.getAllBookmarks().toEntity() 26 | 27 | if let folder = config.folder { 28 | if !FoldersManager.shared.doesExist(withId: folder.id) { 29 | config = ConfigurationAppIntent.allBookmarks 30 | } 31 | } 32 | 33 | let entry = BookmarksEntry(date: Date.now, bookmarks: bookmarks, configuration: config) 34 | 35 | return Timeline(entries: [entry], policy: .never) 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /BookmarksWidget/BookmarksWidgetView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // FolderWidget.swift 3 | // Linkeeper 4 | // 5 | // Created by Om Chachad on 01/01/24. 6 | // 7 | 8 | import SwiftUI 9 | import WidgetKit 10 | 11 | struct BookmarksWidget: Widget { 12 | let kind: String = "BookmarksWidget" 13 | 14 | var body: some WidgetConfiguration { 15 | AppIntentConfiguration(kind: kind, intent: ConfigurationAppIntent.self, provider: Provider()) { entry in 16 | BookmarksWidgetEntryView(entry: entry) 17 | 18 | } 19 | .configurationDisplayName("Bookmarks") 20 | .description("Quickly glance at all your bookmarks, or bookmarks from a particular folder.") 21 | .containerBackgroundRemovable(true) 22 | .supportedFamilies([.systemSmall, .systemMedium, .systemLarge]) 23 | } 24 | } 25 | 26 | struct BookmarksWidgetEntryView : View { 27 | var entry: Provider.Entry 28 | @Environment(\.widgetFamily) var family 29 | 30 | var body: some View { 31 | Group { 32 | switch(family) { 33 | case .systemSmall: 34 | SmallWidgetView(entry: entry) 35 | default: 36 | MediumOrLargeWidgetView(entry: entry) 37 | } 38 | } 39 | .overlay { 40 | if entry.bookmarks.isEmpty { 41 | Text("No bookmarks here, yet!") 42 | .multilineTextAlignment(.center) 43 | .font(.caption.weight(.semibold)) 44 | .foregroundStyle(.tertiary) 45 | } 46 | } 47 | } 48 | } 49 | 50 | 51 | -------------------------------------------------------------------------------- /Linkeeper/Helpers/Enums/ColorOption.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ColorOption.swift 3 | // Marked 4 | // 5 | // Created by Om Chachad on 27/04/22. 6 | // 7 | 8 | import Foundation 9 | import SwiftUI 10 | 11 | enum ColorOption: String, CaseIterable { 12 | case gray 13 | case purple 14 | case brown 15 | case indigo 16 | case pink 17 | case blurple 18 | case orange 19 | case blue 20 | case yellow 21 | case cyan 22 | case green 23 | case mint 24 | 25 | #if os(macOS) 26 | private static var values: [ColorOption : Color] = [ 27 | .gray : Color(.systemGray), 28 | .purple : Color.purple, 29 | .orange : Color.orange, 30 | .pink : Color.red, 31 | .yellow : Color.yellow, 32 | .mint : Color.mint, 33 | .indigo : Color.indigo, 34 | .green : Color.green, 35 | .cyan : Color.cyan, 36 | .brown : Color.brown, 37 | .blue : Color.blue, 38 | .blurple : Color(red: 0.5294117647, green: 0.4823529412, blue: 0.9019607843) 39 | ] 40 | #else 41 | private static var values: [ColorOption : Color] = [ 42 | .gray : Color(uiColor: .systemGray), 43 | .purple : Color.purple, 44 | .orange : Color.orange, 45 | .pink : Color.red, 46 | .yellow : Color.yellow, 47 | .mint : Color.mint, 48 | .indigo : Color.indigo, 49 | .green : Color.green, 50 | .cyan : Color.cyan, 51 | .brown : Color.brown, 52 | .blue : Color.blue, 53 | .blurple : Color(red: 0.5294117647, green: 0.4823529412, blue: 0.9019607843) 54 | ] 55 | #endif 56 | 57 | var color: Color { 58 | return ColorOption.values[self] ?? .gray 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /Linkeeper/Helpers/Flashcard.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Flashcard.swift 3 | // Linkeeper 4 | // 5 | // Created by Om Chachad on 16/05/23. 6 | // 7 | 8 | import SwiftUI 9 | /// Custom Flip Transition Effect: https://www.youtube.com/watch?v=hwmDFxvUCRY 10 | struct Flashcard: View where Front: View, Back: View { 11 | var front: () -> Front 12 | var back: () -> Back 13 | 14 | @State var flipped = false 15 | @Binding var editing: Bool 16 | 17 | @State var flashcardRotation = 0 18 | @State var contentRotation = 0 19 | 20 | init(editing: Binding, @ViewBuilder front: @escaping () -> Front, @ViewBuilder back: @escaping () -> Back) { 21 | self.front = front 22 | self.back = back 23 | self._editing = editing 24 | } 25 | 26 | var body: some View { 27 | ZStack { 28 | if editing { 29 | back() 30 | .transition(.reverseFlip) 31 | } else { 32 | front() 33 | .transition(.flip) 34 | } 35 | } 36 | .animation(.bouncy, value: editing) 37 | .frame(maxWidth: 500) 38 | .padding(10) 39 | } 40 | } 41 | 42 | struct FlipTransition: ViewModifier, Animatable { 43 | var progress: CGFloat = 0 44 | var animatableData: CGFloat { 45 | get { progress } 46 | set { progress = newValue } 47 | } 48 | func body(content: Content) -> some View { 49 | content 50 | .opacity(progress < 0 ? (-progress < 0.5 ? 1 : 0) : (progress < 0.5 ? 1 : 0)) 51 | .rotation3DEffect(.init(degrees: progress * 180), axis: (x: 0.0, y: 1.0, z: 0.0)) 52 | } 53 | 54 | } 55 | 56 | extension AnyTransition { 57 | static let flip: AnyTransition = modifier( 58 | active: FlipTransition (progress: -1), identity: FlipTransition() 59 | ) 60 | static let reverseFlip: AnyTransition = modifier( 61 | active: FlipTransition (progress: 1), identity: FlipTransition()) 62 | } 63 | -------------------------------------------------------------------------------- /Linkeeper.xcodeproj/xcuserdata/omchachad.xcuserdatad/xcschemes/xcschememanagement.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | SchemeUserState 6 | 7 | AddBookmarkSheet.xcscheme_^#shared#^_ 8 | 9 | orderHint 10 | 1 11 | 12 | BookmarksMacWidgetExtension.xcscheme_^#shared#^_ 13 | 14 | orderHint 15 | 4 16 | 17 | BookmarksWidgetExtension.xcscheme_^#shared#^_ 18 | 19 | orderHint 20 | 2 21 | 22 | Debug.xcscheme_^#shared#^_ 23 | 24 | orderHint 25 | 6 26 | 27 | Linkeeper.xcscheme_^#shared#^_ 28 | 29 | orderHint 30 | 0 31 | 32 | Share Extension Mac.xcscheme_^#shared#^_ 33 | 34 | orderHint 35 | 4 36 | 37 | Share Extension Vision.xcscheme_^#shared#^_ 38 | 39 | orderHint 40 | 5 41 | 42 | Share Extension.xcscheme_^#shared#^_ 43 | 44 | orderHint 45 | 1 46 | 47 | 48 | SuppressBuildableAutocreation 49 | 50 | C2052BDA2B34688500185D29 51 | 52 | primary 53 | 54 | 55 | C2120C392B95A26E003FEDF2 56 | 57 | primary 58 | 59 | 60 | C2891D692B51857900C44C37 61 | 62 | primary 63 | 64 | 65 | C2A14A2A2A601C23003A5308 66 | 67 | primary 68 | 69 | 70 | C2C8DC3928171D93000F4AEB 71 | 72 | primary 73 | 74 | 75 | 76 | 77 | 78 | -------------------------------------------------------------------------------- /Linkeeper/StoreKit/Local Tips.storekit: -------------------------------------------------------------------------------- 1 | { 2 | "identifier" : "FFB466EE", 3 | "nonRenewingSubscriptions" : [ 4 | 5 | ], 6 | "products" : [ 7 | { 8 | "displayPrice" : "0.99", 9 | "familyShareable" : false, 10 | "internalID" : "F7107A17", 11 | "localizations" : [ 12 | { 13 | "description" : "Let the developer know you love the app!", 14 | "displayName" : "Appreciation", 15 | "locale" : "en_US" 16 | } 17 | ], 18 | "productID" : "Tip1", 19 | "referenceName" : "Appreciation", 20 | "type" : "NonConsumable" 21 | }, 22 | { 23 | "displayPrice" : "4.99", 24 | "familyShareable" : false, 25 | "internalID" : "66B46A83", 26 | "localizations" : [ 27 | { 28 | "description" : "Show your gratitude with a thoughtful token!", 29 | "displayName" : "Heartfelt Gift", 30 | "locale" : "en_US" 31 | } 32 | ], 33 | "productID" : "Tip2", 34 | "referenceName" : "Heartfelt Gift", 35 | "type" : "NonConsumable" 36 | }, 37 | { 38 | "displayPrice" : "9.99", 39 | "familyShareable" : false, 40 | "internalID" : "0992FC57", 41 | "localizations" : [ 42 | { 43 | "description" : "Make a meaningful impact to the developer!", 44 | "displayName" : "Impactful Gesture", 45 | "locale" : "en_US" 46 | } 47 | ], 48 | "productID" : "Tip3", 49 | "referenceName" : "Impactful Gesture", 50 | "type" : "NonConsumable" 51 | }, 52 | { 53 | "displayPrice" : "19.99", 54 | "familyShareable" : false, 55 | "internalID" : "56AB90E7", 56 | "localizations" : [ 57 | { 58 | "description" : "Supercharge the developer's efforts!", 59 | "displayName" : "Mega Support", 60 | "locale" : "en_US" 61 | } 62 | ], 63 | "productID" : "Tip4", 64 | "referenceName" : "Mega Support", 65 | "type" : "NonConsumable" 66 | } 67 | ], 68 | "settings" : { 69 | "_askToBuyEnabled" : true 70 | }, 71 | "subscriptionGroups" : [ 72 | 73 | ], 74 | "version" : { 75 | "major" : 2, 76 | "minor" : 0 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /Linkeeper/Helpers/Shapes/YouTube.swift: -------------------------------------------------------------------------------- 1 | // 2 | // YouTube.swift 3 | // Linkeeper 4 | // 5 | // Created by Om Chachad on 08/06/23. 6 | // 7 | 8 | import Foundation 9 | import SwiftUI 10 | 11 | struct YouTube: Shape { 12 | func path(in rect: CGRect) -> Path { 13 | var path = Path() 14 | let width = rect.size.width 15 | let height = rect.size.height 16 | path.move(to: CGPoint(x: 0.94848*width, y: 0.19482*height)) 17 | path.addCurve(to: CGPoint(x: width, y: 0.5*height), control1: CGPoint(x: 0.99404*width, y: 0.24194*height), control2: CGPoint(x: width, y: 0.31681*height)) 18 | path.addCurve(to: CGPoint(x: 0.94848*width, y: 0.80518*height), control1: CGPoint(x: width, y: 0.68319*height), control2: CGPoint(x: 0.99404*width, y: 0.75806*height)) 19 | path.addCurve(to: CGPoint(x: 0.5*width, y: 0.85602*height), control1: CGPoint(x: 0.90291*width, y: 0.8523*height), control2: CGPoint(x: 0.85546*width, y: 0.85602*height)) 20 | path.addCurve(to: CGPoint(x: 0.05152*width, y: 0.80518*height), control1: CGPoint(x: 0.14454*width, y: 0.85602*height), control2: CGPoint(x: 0.09709*width, y: 0.8523*height)) 21 | path.addCurve(to: CGPoint(x: 0, y: 0.5*height), control1: CGPoint(x: 0.00596*width, y: 0.75806*height), control2: CGPoint(x: 0, y: 0.68319*height)) 22 | path.addCurve(to: CGPoint(x: 0.05152*width, y: 0.19482*height), control1: CGPoint(x: 0, y: 0.31681*height), control2: CGPoint(x: 0.00596*width, y: 0.24194*height)) 23 | path.addCurve(to: CGPoint(x: 0.5*width, y: 0.14398*height), control1: CGPoint(x: 0.09707*width, y: 0.1477*height), control2: CGPoint(x: 0.14454*width, y: 0.14398*height)) 24 | path.addCurve(to: CGPoint(x: 0.94848*width, y: 0.19482*height), control1: CGPoint(x: 0.85546*width, y: 0.14398*height), control2: CGPoint(x: 0.90291*width, y: 0.1477*height)) 25 | path.closeSubpath() 26 | path.move(to: CGPoint(x: 0.419*width, y: 0.65088*height)) 27 | path.addLine(to: CGPoint(x: 0.66191*width, y: 0.50769*height)) 28 | path.addLine(to: CGPoint(x: 0.419*width, y: 0.36674*height)) 29 | path.addLine(to: CGPoint(x: 0.419*width, y: 0.65088*height)) 30 | path.closeSubpath() 31 | return path 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /BookmarksWidget/BookmarkItemView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // BookmarkItemView.swift 3 | // BookmarksWidgetExtension 4 | // 5 | // Created by Om Chachad on 01/01/24. 6 | // 7 | 8 | import Foundation 9 | import SwiftUI 10 | import WidgetKit 11 | 12 | struct BookmarkItemView: View { 13 | @Environment(\.widgetFamily) var family 14 | var bookmark: LinkeeperBookmarkEntity 15 | 16 | var body: some View { 17 | Link(destination: URL(string: "linkeeper://openURL/\(bookmark.id)")!) { 18 | HStack { 19 | VStack(alignment: .leading) { 20 | Text(bookmark.title) 21 | .lineLimit(1) 22 | Text(bookmark.host) 23 | .lineLimit(1) 24 | .foregroundStyle(.secondary) 25 | } 26 | 27 | Spacer() 28 | 29 | Group { 30 | if let thumbnail = CacheManager.instance.get(id: bookmark.id)?.image { 31 | thumbnail 32 | .resizable() 33 | } else { 34 | if let firstChar: Character = bookmark.title.first { 35 | Group { 36 | #if os(macOS) 37 | Color(uiColor: .gray) 38 | #else 39 | Color(uiColor: .systemGray2) 40 | #endif 41 | } 42 | .overlay( 43 | Text(String(firstChar).capitalized) 44 | .font(.body.weight(.medium)) 45 | .foregroundColor(.white) 46 | ) 47 | } 48 | } 49 | } 50 | .aspectRatio(contentMode: .fill) 51 | .frame(width: family == .systemMedium ? 37.5 : 40, height: family == .systemMedium ? 37.5 : 40) 52 | .clipped() 53 | .clipShape(RoundedRectangle(cornerRadius: 7.5)) 54 | .shadow(radius: 1) 55 | } 56 | } 57 | } 58 | } 59 | 60 | 61 | -------------------------------------------------------------------------------- /Linkeeper/StoreKit/Synced Tips.storekit: -------------------------------------------------------------------------------- 1 | { 2 | "identifier" : "83267910", 3 | "nonRenewingSubscriptions" : [ 4 | 5 | ], 6 | "products" : [ 7 | { 8 | "displayPrice" : "0.99", 9 | "familyShareable" : false, 10 | "internalID" : "6451053813", 11 | "localizations" : [ 12 | { 13 | "description" : "Let the developer know you love the app!", 14 | "displayName" : "Appreciation", 15 | "locale" : "en_US" 16 | } 17 | ], 18 | "productID" : "Tip1", 19 | "referenceName" : "Appreciation", 20 | "type" : "NonConsumable" 21 | }, 22 | { 23 | "displayPrice" : "4.99", 24 | "familyShareable" : false, 25 | "internalID" : "6451053753", 26 | "localizations" : [ 27 | { 28 | "description" : "Show your gratitude with a thoughtful token!", 29 | "displayName" : "Heartfelt Gift", 30 | "locale" : "en_US" 31 | } 32 | ], 33 | "productID" : "Tip2", 34 | "referenceName" : "Heartfelt Gift", 35 | "type" : "NonConsumable" 36 | }, 37 | { 38 | "displayPrice" : "9.99", 39 | "familyShareable" : false, 40 | "internalID" : "6451053861", 41 | "localizations" : [ 42 | { 43 | "description" : "Make a meaningful impact to the developer!", 44 | "displayName" : "Impactful Gesture", 45 | "locale" : "en_US" 46 | } 47 | ], 48 | "productID" : "Tip3", 49 | "referenceName" : "Impactful Gesture", 50 | "type" : "NonConsumable" 51 | }, 52 | { 53 | "displayPrice" : "19.99", 54 | "familyShareable" : false, 55 | "internalID" : "6451053885", 56 | "localizations" : [ 57 | { 58 | "description" : "Supercharge the developer's efforts!", 59 | "displayName" : "Mega Support", 60 | "locale" : "en_US" 61 | } 62 | ], 63 | "productID" : "Tip4", 64 | "referenceName" : "Mega Support", 65 | "type" : "NonConsumable" 66 | } 67 | ], 68 | "settings" : { 69 | "_applicationInternalID" : "6449708232", 70 | "_developerTeamID" : "3S6NT5MUQZ", 71 | "_lastSynchronizedDate" : 710446924.84905505 72 | }, 73 | "subscriptionGroups" : [ 74 | 75 | ], 76 | "version" : { 77 | "major" : 2, 78 | "minor" : 0 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /Linkeeper/CoreData/Linkeeper.xcdatamodeld/Linkeeper.xcdatamodel/contents: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /BookmarksWidget/MediumOrLargeWidgetView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MediumOrDefaultWidgetView.swift 3 | // BookmarksWidgetExtension 4 | // 5 | // Created by Om Chachad on 01/01/24. 6 | // 7 | 8 | import Foundation 9 | import WidgetKit 10 | import SwiftUI 11 | 12 | struct MediumOrLargeWidgetView: View { 13 | @Environment(\.widgetFamily) var family 14 | var entry: Provider.Entry 15 | 16 | var bookmarksLimit: Int { 17 | switch(family) { 18 | case .systemSmall: 19 | return 1 20 | case .systemMedium: 21 | return 2 22 | case .systemLarge: 23 | return 5 24 | default: 25 | fatalError() 26 | } 27 | } 28 | 29 | var body: some View { 30 | VStack(alignment: .leading) { 31 | Group { 32 | if let folder = entry.configuration.folder { 33 | Label(folder.title, systemImage: folder.symbol) 34 | .lineLimit(1) 35 | .foregroundColor(Color(uiColor: UIColor(ColorOption(rawValue: folder.color)?.color ?? .gray))) 36 | } else { 37 | Label("All Bookmarks", systemImage: "bookmark.fill") 38 | .foregroundColor(.secondary) 39 | } 40 | } 41 | .fontDesign(.rounded) 42 | .bold() 43 | 44 | if entry.bookmarks.count >= bookmarksLimit { 45 | Spacer() 46 | } 47 | 48 | ForEach(entry.bookmarks.prefix(bookmarksLimit), id: \.self) { bookmark in 49 | BookmarkItemView(bookmark: bookmark) 50 | .padding(.vertical, 5) 51 | } 52 | 53 | if entry.bookmarks.count < bookmarksLimit { 54 | Spacer() 55 | } 56 | } 57 | .frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .topLeading) 58 | .font(.system(size: 15)) 59 | .background { 60 | Color.clear 61 | if let folderID = entry.configuration.folder?.id { 62 | Link(destination: URL(string: "linkeeper://openFolder/\(folderID)")!, label: { 63 | Color.clear 64 | }) 65 | } 66 | } 67 | .containerBackground(Color("WidgetBackground"), for: .widget) 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /Linkeeper/CoreData/Bookmark+CoreDataProperties.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Bookmark+CoreDataProperties.swift 3 | // Linkeeper 4 | // 5 | // Created by Om Chachad on 25/05/22. 6 | // 7 | // 8 | 9 | import Foundation 10 | import CoreData 11 | 12 | extension Bookmark { 13 | 14 | @nonobjc public class func fetchRequest() -> NSFetchRequest { 15 | return NSFetchRequest(entityName: "Bookmark") 16 | } 17 | 18 | @NSManaged public var id: UUID? 19 | @NSManaged public var title: String? 20 | @NSManaged public var date: Date? 21 | @NSManaged public var notes: String? 22 | @NSManaged public var host: String? 23 | @NSManaged public var url: String? 24 | @NSManaged public var isFavorited: Bool 25 | @NSManaged public var folder: Folder? 26 | 27 | public var wrappedTitle: String { 28 | title ?? "Untitled folder" 29 | } 30 | 31 | public var wrappedNotes: String { 32 | notes ?? "" 33 | } 34 | 35 | public var wrappedHost: String { 36 | host ?? "Unknown Website" 37 | } 38 | 39 | public var wrappedURL: URL { 40 | URL(string: url ?? "https://starlightapps.org")!.sanitise 41 | } 42 | 43 | public var wrappedDate: Date { 44 | date ?? Date.now 45 | } 46 | 47 | public var wrappedUUID: String { 48 | String(describing: id?.uuidString) 49 | } 50 | 51 | public var wrappedFolderName: String { 52 | self.folder?.wrappedTitle ?? "None" 53 | } 54 | 55 | var draggable: DraggableBookmark { 56 | return DraggableBookmark(id: id ?? UUID(), title: wrappedTitle, url: wrappedURL, notes: wrappedNotes, dateAdded: wrappedDate, isFavorited: isFavorited) 57 | } 58 | 59 | func doesMatch(_ searchText: String) -> Bool { 60 | if let folder = self.folder { 61 | return (self.wrappedTitle + self.wrappedHost + self.wrappedNotes + folder.wrappedTitle).localizedCaseInsensitiveContains(searchText) 62 | } else { 63 | return (self.wrappedTitle + self.wrappedHost + self.wrappedNotes).localizedCaseInsensitiveContains(searchText) 64 | } 65 | } 66 | 67 | func doesMatch(_ searchText: String, folder: Folder) -> Bool { 68 | return self.folder == folder && (self.wrappedTitle + self.wrappedHost + self.wrappedNotes + self.folder!.wrappedTitle).localizedCaseInsensitiveContains(searchText) 69 | } 70 | } 71 | 72 | extension Bookmark : Identifiable { 73 | 74 | } 75 | -------------------------------------------------------------------------------- /Linkeeper/Helpers/FolderItemView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // FolderItemView.swift 3 | // Linkeeper 4 | // 5 | // Created by Om Chachad on 04/06/23. 6 | // 7 | 8 | import SwiftUI 9 | import CoreData 10 | 11 | struct FolderItemView: View { 12 | @Environment(\.managedObjectContext) var moc 13 | @ObservedObject var bookmarksInFolder = bookmarksCountFetcher() 14 | var folder: Folder 15 | var style: ListItem.Style = .sidebar 16 | 17 | @State private var editingFolder = false 18 | 19 | @Environment(\.editMode) var editMode 20 | 21 | @State private var deleteConfirmation: Bool = false 22 | 23 | var isEditing: Bool { 24 | #if os(macOS) 25 | return self._editMode.wrappedValue == .active 26 | #else 27 | return self.editMode?.wrappedValue == .active 28 | #endif 29 | } 30 | 31 | var requiresAdditionalPadding: Bool { 32 | #if os(iOS) 33 | if #available(iOS 18.0, *) { 34 | return UIDevice.current.userInterfaceIdiom == .pad 35 | } 36 | #endif 37 | 38 | return false 39 | } 40 | 41 | @State private var isTargeted = false 42 | 43 | var body: some View { 44 | ListItem(title: folder.wrappedTitle, systemName: folder.wrappedSymbol, color: folder.wrappedColor, subItemsCount: folder.countOfBookmarks, style: style) 45 | .folderActions(folder: folder, isEditing: isEditing) 46 | .dropDestination(isTargeted: $isTargeted) { bookmark, url in 47 | addDroppedBookmarkToFolder(bookmark: bookmark, url: url, folder: folder) 48 | } 49 | .opacity(isTargeted ? 0.1 : 1) 50 | .padding(.leading, requiresAdditionalPadding ? 5 : 0) 51 | .frame(height: 40) 52 | } 53 | 54 | func addDroppedBookmarkToFolder(bookmark: Bookmark?, url: URL, folder: Folder) { 55 | if let bookmark { 56 | bookmark.folder = folder 57 | try? moc.save() 58 | } else { 59 | BookmarksManager.shared.addDroppedURL(url, to: folder) 60 | } 61 | } 62 | 63 | class bookmarksCountFetcher: ObservableObject { 64 | func count(context: NSManagedObjectContext, folder: Folder) -> Int { 65 | let itemFetchRequest: NSFetchRequest = Bookmark.fetchRequest() 66 | itemFetchRequest.predicate = NSPredicate(format: "folder == %@", folder) 67 | 68 | return try! context.count(for: itemFetchRequest) 69 | } 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /Linkeeper/Helpers/Extensions/Bookmark.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Bookmark.swift 3 | // Linkeeper 4 | // 5 | // Created by Om Chachad on 06/07/23. 6 | // 7 | 8 | import Foundation 9 | import SwiftUI 10 | 11 | extension Bookmark { 12 | func copyURL() { 13 | #if os(macOS) 14 | let pasteboard = NSPasteboard.general 15 | pasteboard.clearContents() 16 | pasteboard.writeObjects([self.wrappedURL as NSPasteboardWriting]) 17 | #else 18 | UIPasteboard.general.url = self.wrappedURL 19 | #endif 20 | } 21 | 22 | @MainActor 23 | func cachedImage(saveTo preview: Binding) { 24 | let cacheManager = CacheManager.instance 25 | 26 | if let cachedPreview = cacheManager.get(for: self) { 27 | withAnimation { 28 | preview.wrappedValue = cachedPreview 29 | } 30 | } else { 31 | // Using task to perform fetching on the main actor 32 | Task { @MainActor in 33 | await self.cachePreviewInto(preview) 34 | 35 | if preview.wrappedValue == nil { 36 | withAnimation { 37 | preview.wrappedValue = cachedPreview(image: UIImage(), preview: .firstLetter) 38 | } 39 | } 40 | } 41 | } 42 | } 43 | 44 | func cachePreviewInto(_ preview: Binding) async { 45 | let cacheManager = CacheManager.instance 46 | 47 | let metadata = try? await startFetchingMetadata(for: self.wrappedURL, fetchSubresources: true, timeout: 15) 48 | if let metadata = metadata { 49 | if let imageProvider = metadata.imageProvider ?? metadata.iconProvider { 50 | let imageType: PreviewType = metadata.imageProvider != nil ? .thumbnail : .icon 51 | imageProvider.loadObject(ofClass: UIImage.self) { (image, error) in 52 | guard error == nil else { return } 53 | if let image = image as? UIImage { 54 | // Ensure UI updates happen on main thread 55 | Task { @MainActor in 56 | cacheManager.add(preview: cachedPreview(image: image, preview: imageType), for: self) 57 | preview.wrappedValue = cachedPreview(image: image, preview: imageType) 58 | } 59 | } 60 | } 61 | } 62 | } 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /Linkeeper/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | UIApplicationShortcutItems 6 | 7 | 8 | UIApplicationShortcutItemType 9 | org.starlightapps.Linkeeper.AddBookmark 10 | UIApplicationShortcutItemTitle 11 | Add Bookmark 12 | UIApplicationShortcutItemIconSymbolName 13 | bookmark.fill 14 | 15 | 16 | CFBundleIcons 17 | 18 | CFBundlePrimaryIcon 19 | 20 | NSAppIconActionTintColorName 21 | ShortcutIconForeground 22 | NSAppIconComplementingColorNames 23 | 24 | ShortcutBackground1 25 | ShortcutBackground2 26 | 27 | 28 | 29 | CFBundleURLTypes 30 | 31 | 32 | CFBundleTypeRole 33 | Editor 34 | CFBundleURLIconFile 35 | 36 | CFBundleURLName 37 | org.starlightapps.Linkeeper 38 | CFBundleURLSchemes 39 | 40 | linkeeper 41 | 42 | 43 | 44 | ITSAppUsesNonExemptEncryption 45 | 46 | UIApplicationSceneManifest 47 | 48 | UIApplicationSupportsMultipleScenes 49 | 50 | 51 | UIBackgroundModes 52 | 53 | fetch 54 | remote-notification 55 | 56 | UTExportedTypeDeclarations 57 | 58 | 59 | UTTypeConformsTo 60 | 61 | com.public.data 62 | 63 | UTTypeDescription 64 | Bookmark 65 | UTTypeIconFiles 66 | 67 | UTTypeIdentifier 68 | org.starlightapps.Linkeeper.bookmark 69 | UTTypeTagSpecification 70 | 71 | 72 | 73 | UTTypeConformsTo 74 | 75 | com.public.data 76 | 77 | UTTypeDescription 78 | Folder 79 | UTTypeIconFiles 80 | 81 | UTTypeIdentifier 82 | org.starlightapps.Linkeeper.folder 83 | UTTypeTagSpecification 84 | 85 | 86 | 87 | 88 | 89 | -------------------------------------------------------------------------------- /Linkeeper/App Intents/Folders/DeleteFolder.swift: -------------------------------------------------------------------------------- 1 | // 2 | // DeleteFolder.swift 3 | // Linkeeper 4 | // 5 | // Created by Om Chachad on 30/05/23. 6 | // 7 | 8 | import Foundation 9 | import AppIntents 10 | 11 | @available(iOS 16.0, macOS 13.0, *) 12 | struct DeleteFolder: AppIntent { 13 | static var title: LocalizedStringResource = "Delete Folder" 14 | 15 | static var description: IntentDescription = IntentDescription( 16 | "Permanently deletes the Folder from all iCloud Devices.", categoryName: "Edit") 17 | 18 | @Parameter(title: "Folders", requestValueDialog: IntentDialog("Which Folders would you like to delete?")) 19 | var folder: FolderEntity 20 | 21 | @Parameter(title: "Confirm Before Deleting", description: "If toggled, you will need to confirm the folder will be deleted", default: true) 22 | var confirmBeforeDeleting: Bool 23 | 24 | @Parameter(title: "Keep Bookmarks inside Folder", description: "If toggled, the bookmarks inside the folder will not be deleted.", default: false) 25 | var keepBookmarks: Bool 26 | 27 | static var parameterSummary: some ParameterSummary { 28 | When(\DeleteFolder.$confirmBeforeDeleting, .equalTo, true, { 29 | Summary("Delete \(\.$folder)") { 30 | \.$keepBookmarks 31 | \.$confirmBeforeDeleting 32 | } 33 | }, otherwise: { 34 | Summary("Immediately delete \(\.$folder)") { 35 | \.$keepBookmarks 36 | \.$confirmBeforeDeleting 37 | } 38 | }) 39 | } 40 | 41 | func perform() async throws -> some IntentResult { 42 | do { 43 | if confirmBeforeDeleting { 44 | try await requestConfirmation(result: .result(dialog: "Are you sure you want to delete this folder titled \(folder.title)?")) 45 | } 46 | 47 | for bookmark in folder.bookmarks { 48 | if keepBookmarks { 49 | BookmarksManager.shared.findBookmark(withId: bookmark.id).folder = nil 50 | } else { 51 | BookmarksManager.shared.deleteBookmark(withId: bookmark.id) 52 | } 53 | } 54 | 55 | FoldersManager.shared.delete(withId: folder.id) 56 | reloadAllWidgets() 57 | 58 | let messageSuffix = folder.bookmarks.count == 0 ? "" : ", \(keepBookmarks ? "keeping" : "alongside") \(folder.bookmarks.count) \(folder.bookmarks.count == 1 ? "Bookmark" : "Bookmarks")" 59 | return .result() 60 | 61 | } catch { 62 | throw error 63 | } 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /Linkeeper/BookmarkInfoViews/ChangeIconsView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ChangeIconsView.swift 3 | // Linkeeper 4 | // 5 | // Created by Om Chachad on 28/06/23. 6 | // 7 | 8 | import SwiftUI 9 | 10 | #if !os(macOS) 11 | @available(iOS 15.0, *) 12 | struct ChangeIconsView: View { 13 | let icons = ["ClassicIcon", "DarkIcon"] 14 | let displayNames = [ 15 | "ClassicIcon": "Classic", 16 | "DarkIcon": "Dark" 17 | ] 18 | 19 | @State private var initialised = false 20 | @State private var chosenIcon = "AppIcon" 21 | @State private var showErrorAlert = false 22 | @State private var errorMessage = "An unknown error occured." 23 | 24 | var body: some View { 25 | Form { 26 | Picker("Choose an icon", selection: $chosenIcon) { 27 | ForEach(icons, id: \.self) { icon in 28 | HStack { 29 | Image("\(icon)Image") 30 | .resizable() 31 | .scaledToFit() 32 | .frame(width: 65, height: 65) 33 | #if os(visionOS) 34 | .clipShape(.circle) 35 | #else 36 | .cornerRadius(13, style: .continuous) 37 | #endif 38 | .padding([.trailing, .top, .bottom], 5) 39 | 40 | Text(displayNames[icon]!) 41 | 42 | Spacer() 43 | } 44 | .tag(icon) 45 | } 46 | } 47 | .pickerStyle(.inline) 48 | } 49 | .alert(isPresented: $showErrorAlert) { 50 | Alert(title: Text("Error"), message: Text(errorMessage), dismissButton: .default(Text("OK"))) 51 | } 52 | .onAppear { 53 | chosenIcon = UIApplication.shared.alternateIconName ?? "AppIcon" 54 | DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) { 55 | initialised.toggle() 56 | } 57 | } 58 | .onChange(of: chosenIcon) { newIcon in 59 | setIcon(newIcon) 60 | } 61 | .navigationTitle("Change App Icon") 62 | .navigationBarTitleDisplayMode(.inline) 63 | } 64 | 65 | func setIcon(_ iconName: String) { 66 | if initialised { 67 | UIApplication.shared.setAlternateIconName(iconName) { error in 68 | if let error = error?.localizedDescription { 69 | errorMessage = error 70 | showErrorAlert.toggle() 71 | } else { 72 | return 73 | } 74 | } 75 | } 76 | } 77 | } 78 | 79 | struct ChangeIconsView_Previews: PreviewProvider { 80 | static var previews: some View { 81 | ChangeIconsView() 82 | } 83 | } 84 | #endif 85 | -------------------------------------------------------------------------------- /Linkeeper/Helpers/FolderPickerView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // FolderPickerView.swift 3 | // Linkeeper 4 | // 5 | // Created by Om Chachad on 11/07/24. 6 | // 7 | 8 | import SwiftUI 9 | 10 | struct FolderPickerView: View { 11 | @FetchRequest(sortDescriptors: [NSSortDescriptor(keyPath: \Folder.index, ascending: true)], predicate: NSPredicate(format: "parentFolder == nil")) var parentFolders: FetchedResults 12 | @Binding var selectedFolder: Folder? 13 | 14 | var type: PickerType 15 | enum PickerType { 16 | case simplePicker, moveBookmarks, moveFolder(excluding: Folder? = nil) 17 | } 18 | 19 | var body: some View { 20 | List { 21 | Section("Folders") { 22 | FolderButton(for: nil) // "None" Button 23 | 24 | Button("") { } 25 | .buttonStyle(.borderless) 26 | #if os(macOS) 27 | .padding(.vertical, 5) 28 | #endif 29 | .allowsHitTesting(false) 30 | .accessibilityHidden(true) 31 | 32 | OutlineGroup([Folder](parentFolders), id: \.self, children: \.childFoldersArray) { folder in 33 | switch type { 34 | case .moveFolder(excluding: let excludedFolder): 35 | if folder != excludedFolder { 36 | FolderButton(for: folder) 37 | } 38 | case .moveBookmarks, .simplePicker: 39 | FolderButton(for: folder) 40 | } 41 | } 42 | } 43 | } 44 | #if !os(visionOS) 45 | .listStyle(.plain) 46 | #endif 47 | } 48 | 49 | func FolderButton(for folder: Folder?) -> some View { 50 | Button { 51 | self.selectedFolder = folder 52 | } label: { 53 | HStack { 54 | Label { 55 | Text(folder?.wrappedTitle ?? "None") 56 | .lineLimit(1) 57 | .foregroundColor(.primary) 58 | } icon: { 59 | Image(systemName: folder?.wrappedSymbol ?? "xmark.circle") 60 | .foregroundColor(folder?.wrappedColor ?? .secondary) 61 | } 62 | 63 | if selectedFolder == folder && !isMac { 64 | Image(systemName: "checkmark") 65 | .foregroundColor(.accentColor) 66 | } 67 | 68 | Spacer() 69 | } 70 | #if os(macOS) 71 | .padding(.vertical, 5) 72 | #endif 73 | .frame(maxWidth: .infinity, alignment: .leading) 74 | } 75 | .buttonStyle(.borderless) 76 | #if os(macOS) 77 | .listRowBackground( 78 | Color.secondary.opacity(selectedFolder == folder ? 0.5 : 0.0) 79 | ) 80 | #endif 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /Linkeeper/Assets.xcassets/DarkIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "40.png", 5 | "idiom" : "universal", 6 | "platform" : "ios", 7 | "scale" : "2x", 8 | "size" : "20x20" 9 | }, 10 | { 11 | "filename" : "60.png", 12 | "idiom" : "universal", 13 | "platform" : "ios", 14 | "scale" : "3x", 15 | "size" : "20x20" 16 | }, 17 | { 18 | "filename" : "58.png", 19 | "idiom" : "universal", 20 | "platform" : "ios", 21 | "scale" : "2x", 22 | "size" : "29x29" 23 | }, 24 | { 25 | "filename" : "87.png", 26 | "idiom" : "universal", 27 | "platform" : "ios", 28 | "scale" : "3x", 29 | "size" : "29x29" 30 | }, 31 | { 32 | "filename" : "Frame 3-7 76.png", 33 | "idiom" : "universal", 34 | "platform" : "ios", 35 | "scale" : "2x", 36 | "size" : "38x38" 37 | }, 38 | { 39 | "filename" : "114.png", 40 | "idiom" : "universal", 41 | "platform" : "ios", 42 | "scale" : "3x", 43 | "size" : "38x38" 44 | }, 45 | { 46 | "filename" : "80.png", 47 | "idiom" : "universal", 48 | "platform" : "ios", 49 | "scale" : "2x", 50 | "size" : "40x40" 51 | }, 52 | { 53 | "filename" : "120.png", 54 | "idiom" : "universal", 55 | "platform" : "ios", 56 | "scale" : "3x", 57 | "size" : "40x40" 58 | }, 59 | { 60 | "filename" : "120 1.png", 61 | "idiom" : "universal", 62 | "platform" : "ios", 63 | "scale" : "2x", 64 | "size" : "60x60" 65 | }, 66 | { 67 | "filename" : "180.png", 68 | "idiom" : "universal", 69 | "platform" : "ios", 70 | "scale" : "3x", 71 | "size" : "60x60" 72 | }, 73 | { 74 | "filename" : "Frame 3-7 128.png", 75 | "idiom" : "universal", 76 | "platform" : "ios", 77 | "scale" : "2x", 78 | "size" : "64x64" 79 | }, 80 | { 81 | "filename" : "Frame 3-7 192.png", 82 | "idiom" : "universal", 83 | "platform" : "ios", 84 | "scale" : "3x", 85 | "size" : "64x64" 86 | }, 87 | { 88 | "filename" : "Frame 3-7 136.png", 89 | "idiom" : "universal", 90 | "platform" : "ios", 91 | "scale" : "2x", 92 | "size" : "68x68" 93 | }, 94 | { 95 | "filename" : "Frame 3-7 152.png", 96 | "idiom" : "universal", 97 | "platform" : "ios", 98 | "scale" : "2x", 99 | "size" : "76x76" 100 | }, 101 | { 102 | "filename" : "Frame 3-7 167.png", 103 | "idiom" : "universal", 104 | "platform" : "ios", 105 | "scale" : "2x", 106 | "size" : "83.5x83.5" 107 | }, 108 | { 109 | "filename" : "1024.png", 110 | "idiom" : "universal", 111 | "platform" : "ios", 112 | "size" : "1024x1024" 113 | } 114 | ], 115 | "info" : { 116 | "author" : "xcode", 117 | "version" : 1 118 | } 119 | } 120 | -------------------------------------------------------------------------------- /Linkeeper/Helpers/ListItem.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ListItem.swift 3 | // Linkeeper 4 | // 5 | // Created by Om Chachad on 04/06/23. 6 | // 7 | 8 | import SwiftUI 9 | 10 | struct ListItem: View { 11 | @Environment(\.colorScheme) var colorScheme 12 | 13 | var title: AttributedString 14 | var systemName: String 15 | var color: Color 16 | var subItemsCount: Int 17 | var style: Style 18 | 19 | init(title: String, systemName: String, color: Color, subItemsCount: Int, style: Style = .sidebar) { 20 | self.title = AttributedString(title) 21 | self.systemName = systemName 22 | self.color = color 23 | self.subItemsCount = subItemsCount 24 | self.style = style 25 | } 26 | 27 | init(markdown: String, systemName: String, color: Color, subItemsCount: Int, style: Style = .sidebar) { 28 | if let data = markdown.data(using: .utf8) { 29 | self.title = try! AttributedString(markdown: data) 30 | } else { 31 | self.title = AttributedString(markdown) 32 | } 33 | self.systemName = systemName 34 | self.color = color 35 | self.subItemsCount = subItemsCount 36 | self.style = style 37 | } 38 | 39 | enum Style { 40 | case sidebar 41 | case large 42 | } 43 | 44 | var body: some View { 45 | HStack { 46 | Label { 47 | Text(title) 48 | .lineLimit(1) 49 | .padding(.leading, 5) 50 | #if os(macOS) 51 | .padding(.leading, style == .large ? 10 : 0) 52 | #endif 53 | } icon: { 54 | icon() 55 | } 56 | #if os(macOS) 57 | .padding(.leading, 5) 58 | #endif 59 | .padding(style == .large ? 15 : 0) 60 | 61 | Spacer() 62 | 63 | Text(String(subItemsCount)) 64 | .foregroundColor(.secondary) 65 | .frame(minWidth: 15, alignment: .center) 66 | } 67 | } 68 | 69 | func icon() -> some View { 70 | Group { 71 | if style == .sidebar { 72 | #if os(macOS) 73 | Image(systemName: systemName) 74 | .imageScale(.medium) 75 | .padding(5) 76 | .frame(width: 30, height: 30) 77 | #else 78 | Image(systemName: systemName) 79 | .imageScale(.medium) 80 | .padding(7.5) 81 | .frame(width: 40, height: 40) 82 | #endif 83 | } else { 84 | Image(systemName: systemName) 85 | .imageScale(.large) 86 | .padding(5) 87 | .frame(width: 44, height: 44) 88 | } 89 | } 90 | .foregroundColor(.white) 91 | .background(color.gradientify(colorScheme: colorScheme), in: Circle()) 92 | .contentShape(.circle) 93 | .padding(5) 94 | .padding([.vertical, .trailing], style == .large ? 15 : 0) 95 | 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /Linkeeper.xcodeproj/xcshareddata/xcschemes/Debug.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 9 | 10 | 16 | 22 | 23 | 24 | 25 | 26 | 32 | 33 | 43 | 45 | 51 | 52 | 53 | 54 | 60 | 62 | 68 | 69 | 70 | 71 | 73 | 74 | 77 | 78 | 79 | -------------------------------------------------------------------------------- /Linkeeper/CoreData/Folder+CoreDataProperties.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Folder+CoreDataProperties.swift 3 | // Linkeeper 4 | // 5 | // Created by Om Chachad on 25/05/22. 6 | // 7 | // 8 | 9 | import Foundation 10 | import CoreData 11 | import SwiftUI 12 | 13 | extension Folder { 14 | 15 | @nonobjc public class func fetchRequest() -> NSFetchRequest { 16 | return NSFetchRequest(entityName: "Folder") 17 | } 18 | 19 | @NSManaged public var accentColor: String? 20 | @NSManaged public var id: UUID? 21 | @NSManaged public var index: Int16 22 | @NSManaged public var symbol: String? 23 | @NSManaged public var title: String? 24 | @NSManaged public var isPinned: Bool 25 | @NSManaged public var bookmark: NSSet? 26 | @NSManaged public var parentFolder: Folder? 27 | @NSManaged public var childFolders: NSSet? 28 | 29 | public var wrappedUUID: String { 30 | String(describing: id?.uuidString) 31 | } 32 | 33 | public var wrappedTitle: String { 34 | title ?? "Untitled folder" 35 | } 36 | 37 | public var wrappedSymbol: String { 38 | symbol ?? "questionmark.folder" 39 | } 40 | 41 | public var wrappedColor: Color { 42 | ColorOption(rawValue: accentColor ?? "gray")?.color ?? Color.gray 43 | } 44 | 45 | public var childFoldersArray: [Folder]? { 46 | if childFolders?.count == 0 { 47 | return nil 48 | } 49 | 50 | let set = childFolders as? Set ?? [] 51 | 52 | return set.sorted { $0.index < $1.index } 53 | } 54 | 55 | public var countOfChildFolders: Int { 56 | return childFoldersArray?.count ?? 0 57 | } 58 | 59 | public var bookmarksArray: [Bookmark] { 60 | let set = bookmark as? Set ?? [] 61 | 62 | return set.sorted { $0.wrappedDate < $1.wrappedDate } 63 | } 64 | 65 | public var countOfBookmarks: Int { 66 | return countTotalBookmarks(in: self) 67 | } 68 | 69 | private func countTotalBookmarks(in folder: Folder) -> Int { 70 | var totalBookmarks = folder.bookmark?.count ?? 0 71 | 72 | for childFolder in folder.childFoldersArray ?? [] { 73 | totalBookmarks += countTotalBookmarks(in: childFolder) 74 | } 75 | 76 | return totalBookmarks 77 | } 78 | 79 | var draggable: DraggableFolder { 80 | DraggableFolder(id: id ?? UUID(), title: title ?? "Untitled folder", symbol: symbol ?? "questionmark.folder", index: Int(index), isPinned: isPinned) 81 | } 82 | 83 | } 84 | 85 | // MARK: Generated accessors for bookmark 86 | extension Folder { 87 | 88 | @objc(addBookmarkObject:) 89 | @NSManaged public func addToBookmark(_ value: Bookmark) 90 | 91 | @objc(removeBookmarkObject:) 92 | @NSManaged public func removeFromBookmark(_ value: Bookmark) 93 | 94 | @objc(addBookmark:) 95 | @NSManaged public func addToBookmark(_ values: NSSet) 96 | 97 | @objc(removeBookmark:) 98 | @NSManaged public func removeFromBookmark(_ values: NSSet) 99 | 100 | } 101 | 102 | extension Folder : Identifiable { 103 | 104 | } 105 | -------------------------------------------------------------------------------- /Linkeeper/Bookmarks View/Grid View/FolderGridItem.swift: -------------------------------------------------------------------------------- 1 | // 2 | // FolderGridItem.swift 3 | // Linkeeper 4 | // 5 | // Created by Om Chachad on 12/06/24. 6 | // 7 | 8 | import SwiftUI 9 | 10 | struct FolderGridItem: View { 11 | @Environment(\.managedObjectContext) var moc 12 | @Environment(\.colorScheme) var colorScheme 13 | 14 | @ObservedObject var folder: Folder 15 | var namespace: Namespace.ID 16 | var isEditing: Bool 17 | 18 | @State private var isTargeted = false 19 | 20 | @AppStorage("ShadowsEnabled") var shadowsEnabled = true 21 | 22 | var body: some View { 23 | NavigationLink { 24 | BookmarksView(folder: folder) 25 | } label: { 26 | HStack { 27 | Image(systemName: folder.wrappedSymbol) 28 | .imageScale(.large) 29 | .frame(width: 30) 30 | .matchedGeometryEffect(id: "\(folder.wrappedUUID)-Icon", in: namespace) 31 | 32 | Text(folder.wrappedTitle) 33 | .bold() 34 | .lineLimit(2) 35 | .multilineTextAlignment(.leading) 36 | .matchedGeometryEffect(id: "\(folder.wrappedUUID)-Title", in: namespace) 37 | Spacer() 38 | } 39 | .frame(maxWidth: .infinity, alignment: .center) 40 | .frame(height: 50) 41 | .padding(.horizontal, 5) 42 | .padding(10) 43 | .foregroundColor(.primary.opacity(isMac ? 1 : 0.6)) 44 | .background(folder.wrappedColor.opacity(0.5).gradientify(colorScheme: colorScheme)) 45 | .background(.ultraThinMaterial) 46 | .cornerRadius(15, style: .continuous) 47 | #if !os(macOS) 48 | .contentShape(.hoverEffect, .rect(cornerRadius: 15)) 49 | #endif 50 | // .background { 51 | // RoundedRectangle(cornerRadius: 15, style: .continuous) 52 | // .stroke(LinearGradient(colors: [folder.wrappedColor.opacity(0.2), .black.opacity(0)], startPoint: .topLeading, endPoint: .bottomLeading), lineWidth: 4) 53 | // } 54 | .matchedGeometryEffect(id: "\(folder.wrappedUUID)-Background", in: namespace) 55 | #if !os(macOS) 56 | .hoverEffect(.lift) 57 | #endif 58 | .frame(maxWidth: .infinity, maxHeight: 60, alignment: .center) 59 | .opacity(isTargeted ? 0.2 : 1) 60 | .shadow(color: .black.opacity(0.2), radius: shadowsEnabled ? 2 : 0) // Checks if the shadows are enabled in Settings, otherwise only shows them when the bookmark is not selected. 61 | .padding(.vertical, 2.5) 62 | } 63 | .buttonStyle(.plain) 64 | .folderActions(folder: folder, isEditing: isEditing) 65 | .dropDestination(isTargeted: $isTargeted) { bookmark, url in 66 | addDroppedBookmarkToFolder(bookmark: bookmark, url: url, folder: folder) 67 | } 68 | } 69 | 70 | func addDroppedBookmarkToFolder(bookmark: Bookmark?, url: URL, folder: Folder) { 71 | if let bookmark { 72 | bookmark.folder = folder 73 | try? moc.save() 74 | } else { 75 | BookmarksManager.shared.addDroppedURL(url, to: folder) 76 | } 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /Linkeeper.xcodeproj/xcshareddata/xcschemes/Linkeeper.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 32 | 33 | 43 | 45 | 51 | 52 | 53 | 55 | 56 | 57 | 63 | 65 | 71 | 72 | 73 | 74 | 76 | 77 | 80 | 81 | 82 | -------------------------------------------------------------------------------- /BookmarksWidget/SmallWidgetView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SmallWidgetView.swift 3 | // BookmarksWidgetExtension 4 | // 5 | // Created by Om Chachad on 01/01/24. 6 | // 7 | 8 | import Foundation 9 | import WidgetKit 10 | import SwiftUI 11 | 12 | struct SmallWidgetView: View { 13 | var entry: Provider.Entry 14 | 15 | var body: some View { 16 | if let bookmark = entry.bookmarks.first { 17 | Link(destination: URL(string: "linkeeper://openURL/\(bookmark.id)")!) { 18 | VStack(alignment: .leading) { 19 | title 20 | 21 | Spacer() 22 | 23 | Text(bookmark.title) 24 | .lineLimit(4) 25 | .bold() 26 | .foregroundStyle(.primary) 27 | Text(bookmark.host) 28 | .lineLimit(1) 29 | .font(.system(size: 12.5)) 30 | .foregroundStyle(.secondary) 31 | } 32 | .shadow(color: Color("WidgetBackground").opacity(0.8), radius: 5) 33 | .frame(maxWidth: .infinity, alignment: .leading) 34 | .font(.system(size: 15, design: .rounded)) 35 | } 36 | .containerBackground(for: .widget) { 37 | Group { 38 | if let thumbnail = CacheManager.instance.get(id: bookmark.id)?.image { 39 | thumbnail 40 | .resizable() 41 | .blur(radius: 4) 42 | .scaleEffect(1.1) 43 | } else { 44 | if let firstChar: Character = bookmark.title.first { 45 | Group { 46 | #if os(macOS) 47 | Color(uiColor: .gray) 48 | #else 49 | Color(uiColor: .systemGray2) 50 | #endif 51 | } 52 | .overlay( 53 | Text(String(firstChar).capitalized) 54 | .font(.largeTitle.weight(.medium)) 55 | .foregroundColor(.white) 56 | ) 57 | } 58 | } 59 | } 60 | .aspectRatio(contentMode: .fill) 61 | .overlay(Color("WidgetBackground").gradient.opacity(0.7)) 62 | } 63 | } else { 64 | title 65 | .frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .topLeading) 66 | } 67 | } 68 | 69 | private var title: some View { 70 | Group { 71 | if let folder = entry.configuration.folder { 72 | Label(folder.title, systemImage: folder.symbol) 73 | .lineLimit(1) 74 | .foregroundColor(Color(uiColor: UIColor(ColorOption(rawValue: folder.color)?.color ?? .gray))) 75 | } else { 76 | Image(systemName: "bookmark.fill") 77 | .foregroundStyle(.secondary) 78 | } 79 | } 80 | .font(.system(size: 15, design: .rounded)) 81 | .fontWeight(.semibold) 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /Linkeeper/LinkeeperApp.swift: -------------------------------------------------------------------------------- 1 | // 2 | // LinkeeperApp.swift 3 | // Linkeeper 4 | // 5 | // Created by Om Chachad on 25/04/22. 6 | // 7 | 8 | import SwiftUI 9 | import CoreData 10 | #if canImport(WidgetKit) 11 | import WidgetKit 12 | #endif 13 | 14 | @main 15 | struct LinkeeperApp: App { 16 | #if canImport(UIKit) 17 | @UIApplicationDelegateAdaptor(AppDelegate.self) var appDelegate 18 | #else 19 | @NSApplicationDelegateAdaptor(AppDelegate.self) var appDelegate 20 | #endif 21 | 22 | @StateObject private var dataController = DataController.shared 23 | @AppStorage("showIntroduction") var showIntroduction = true 24 | @AppStorage("showWhatsNewv3.1") var showWhatsNew = true 25 | 26 | @ObservedObject var storeKit = Store.shared 27 | @AppStorage("tipPromptCompleted") var tipPromptCompleted = false 28 | @State private var showTipPrompt = false 29 | 30 | var body: some Scene { 31 | WindowGroup("Linkeeper") { 32 | ContentView() 33 | .environment(\.managedObjectContext, dataController.persistentCloudKitContainer.viewContext) 34 | .sheet(isPresented: $showIntroduction, onDismiss: { 35 | showIntroduction = false 36 | }, content: IntroductionView.init) 37 | .sheet(isPresented: $showWhatsNew, content: { 38 | WhatsNew() 39 | }) 40 | .task { 41 | if #available(iOS 16.0, macOS 13.0, *) { 42 | LinkeeperShortcuts.updateAppShortcutParameters() 43 | } 44 | reloadAllWidgets() 45 | CacheManager.instance.clearOutOld() 46 | 47 | try? await Task.sleep(nanoseconds: 2_000_000_000) // 2 seconds delay to ensure the app is fully loaded before checking for tip prompt. 48 | 49 | if !tipPromptCompleted && !storeKit.userHasTipped && (try! dataController.persistentCloudKitContainer.viewContext.count(for: NSFetchRequest(entityName: "Bookmark"))) > 20 { 50 | showTipPrompt = true 51 | } 52 | } 53 | .onAppear { 54 | migrateToAppGroup() 55 | 56 | if showIntroduction { 57 | showWhatsNew = false // Dismiss What's New if Introduction is shown 58 | } 59 | } 60 | .sheet(isPresented: $showTipPrompt, onDismiss: { 61 | tipPromptCompleted = true 62 | }) { 63 | TipRequestView() 64 | .frame(minHeight: 400) 65 | .environmentObject(storeKit) 66 | } 67 | } 68 | 69 | #if os(macOS) 70 | Settings { 71 | SettingsView() 72 | .frame(idealWidth: 500) 73 | } 74 | #endif 75 | } 76 | 77 | func migrateToAppGroup() { 78 | let keysToMigrate = ["removeTrackingParameters", "autoFetchTitles"] 79 | 80 | let userDefaults = UserDefaults.standard 81 | 82 | for key in keysToMigrate { 83 | if userDefaults.object(forKey: key) != nil { 84 | print("Migrating key: \(key) to SharedUserDefaults") 85 | 86 | let value = userDefaults.object(forKey: key) 87 | SharedUserDefaults.set(value, forKey: key) 88 | userDefaults.removeObject(forKey: key) 89 | } 90 | } 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /Linkeeper/CoreData/BookmarksManager.swift: -------------------------------------------------------------------------------- 1 | // 2 | // BookmarksManager.swift 3 | // Linkeeper 4 | // 5 | // Created by Om Chachad on 28/05/23. 6 | // 7 | 8 | import Foundation 9 | import CoreData 10 | import LinkPresentation 11 | 12 | class BookmarksManager { 13 | 14 | static let shared = BookmarksManager() 15 | 16 | let context = DataController.shared.persistentCloudKitContainer.viewContext 17 | 18 | func getAllBookmarks() -> [Bookmark] { 19 | let request: NSFetchRequest = Bookmark.fetchRequest() 20 | do { 21 | return try context.fetch(request).sorted(by: { $0.wrappedDate > $1.wrappedDate }) 22 | } catch let error { 23 | print("Couldn't fetch all bookmarks: \(error.localizedDescription)") 24 | return [] 25 | } 26 | } 27 | 28 | func addDroppedURL(_ url: URL, to folder: Folder? = nil) -> Bookmark? { 29 | let bookmark = addBookmark(title: "Loading...", url: url.absoluteString, host: url.host ?? "Unknown Host", notes: "", folder: folder) 30 | 31 | Task { 32 | if let metadata = try await startFetchingMetadata(for: url, fetchSubresources: false, timeout: 10) { 33 | DispatchQueue.main.async { 34 | if let URLTitle = metadata.title { 35 | bookmark.title = URLTitle 36 | } else { 37 | bookmark.title = "Could not fetch title..." 38 | } 39 | self.saveContext() 40 | } 41 | } 42 | 43 | saveContext() 44 | } 45 | 46 | return bookmark 47 | } 48 | 49 | func addBookmark(id: UUID? = UUID(), title: String, url: String, host: String, notes: String, folder: Folder?) -> Bookmark { 50 | let urlString: String = { 51 | if SharedUserDefaults.bool(forKey: "removeTrackingParameters") == true && !url.contains("youtube.com/watch") { 52 | return url.components(separatedBy: "?").first ?? url 53 | } else { 54 | return url 55 | } 56 | }() 57 | let bookmark = Bookmark(context: context) 58 | bookmark.id = id ?? UUID() 59 | bookmark.title = title 60 | bookmark.date = Date.now 61 | bookmark.host = host 62 | bookmark.notes = notes 63 | bookmark.url = urlString 64 | bookmark.folder = folder 65 | 66 | saveContext() 67 | return bookmark 68 | } 69 | 70 | func findBookmark(withId id: UUID) -> Bookmark { 71 | let request: NSFetchRequest = Bookmark.fetchRequest() 72 | request.fetchLimit = 1 73 | request.predicate = NSPredicate(format: "id = %@", id as CVarArg) 74 | 75 | guard let foundBook = try? context.fetch(request).first else { 76 | fatalError("Could not find Bookmark with given ID") 77 | } 78 | return foundBook 79 | } 80 | 81 | func deleteBookmark(_ bookmark: Bookmark) { 82 | context.delete(bookmark) 83 | 84 | let cacheManager = CacheManager.instance 85 | cacheManager.remove(for: bookmark) 86 | 87 | saveContext() 88 | } 89 | 90 | func deleteBookmark(withId id: UUID) { 91 | let matchingBookmark = findBookmark(withId: id) 92 | deleteBookmark(matchingBookmark) 93 | } 94 | 95 | func saveContext() { 96 | DispatchQueue.main.async { 97 | if self.context.hasChanges { 98 | try? self.context.save() 99 | } 100 | } 101 | } 102 | 103 | } 104 | -------------------------------------------------------------------------------- /Linkeeper/App Intents/Bookmarks/FavoriteToggle.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Add/Remove from Favorites.swift 3 | // Linkeeper 4 | // 5 | // Created by Om Chachad on 29/05/23. 6 | // 7 | 8 | import Foundation 9 | import AppIntents 10 | 11 | @available(iOS 16.0, macOS 13.0, *) 12 | struct FavoriteToggle: AppIntent { 13 | 14 | // Title of the action in the Shortcuts app 15 | static var title: LocalizedStringResource = "Set Favorite Status" 16 | // Description of the action in the Shortcuts app 17 | static var description: IntentDescription = IntentDescription("Change the favorite status for a Bookmark", categoryName: "Edit") 18 | 19 | @Parameter(title: "Set or Toggle", default: ToggleOrSet.set) 20 | var toggleTask: ToggleOrSet 21 | 22 | @Parameter(title: "Favorite Status", default: YesOrNo.yes) 23 | var OnOrOff: YesOrNo 24 | 25 | @Parameter(title: "Bookmark", description: "The bookmark of which the favorite status will change", requestValueDialog: IntentDialog("Choose a bookmark")) 26 | var bookmark: LinkeeperBookmarkEntity 27 | 28 | static var parameterSummary: some ParameterSummary { 29 | When(\FavoriteToggle.$toggleTask, .equalTo, .toggle, { 30 | Summary("\(\.$toggleTask) Favorite status for \(\.$bookmark)") 31 | }, otherwise: { 32 | Summary("\(\.$toggleTask) Favorite status for \(\.$bookmark) to \(\.$OnOrOff)") 33 | }) 34 | } 35 | 36 | func perform() async throws -> some IntentResult & ReturnsValue { 37 | if toggleTask == .toggle { 38 | bookmark.isFavorited.toggle() 39 | BookmarksManager.shared.findBookmark(withId: bookmark.id).isFavorited.toggle() 40 | } else { 41 | BookmarksManager.shared.findBookmark(withId: bookmark.id).isFavorited = (OnOrOff == .yes ? true : false) 42 | bookmark.isFavorited = (OnOrOff == .yes ? true : false) 43 | } 44 | BookmarksManager.shared.saveContext() 45 | 46 | return .result(value: bookmark) 47 | } 48 | } 49 | 50 | //@available(iOS 16.0, *) 51 | //enum ToggleTask: String, AppEnum { 52 | // case add = "Add" 53 | // case remove = "Remove" 54 | // case toggle = "Toggle" 55 | // 56 | // // This will be displayed as the title of the menu shown when picking from the options 57 | // static var typeDisplayRepresentation = TypeDisplayRepresentation(name: "Task") 58 | // 59 | // // The strings that will be shown for each item in the menu 60 | // static var caseDisplayRepresentations: [ToggleTask: DisplayRepresentation] = [ 61 | // .add: "Add", 62 | // .remove: "Remove", 63 | // .toggle: "Toggle" 64 | // ] 65 | //} 66 | 67 | @available(iOS 16.0, macOS 13.0, *) 68 | enum ToggleOrSet: String, AppEnum { 69 | case set 70 | case toggle 71 | 72 | // This will be displayed as the title of the menu shown when picking from the options 73 | static var typeDisplayRepresentation = TypeDisplayRepresentation(name: "ToggleOrSet") 74 | 75 | // The strings that will be shown for each item in the menu 76 | static var caseDisplayRepresentations: [ToggleOrSet: DisplayRepresentation] = [ 77 | .set: "Set", 78 | .toggle: "Toggle" 79 | ] 80 | } 81 | 82 | @available(iOS 16.0, macOS 13.0, *) 83 | enum YesOrNo: String, AppEnum { 84 | case yes 85 | case no 86 | 87 | // This will be displayed as the title of the menu shown when picking from the options 88 | static var typeDisplayRepresentation = TypeDisplayRepresentation(name: "YesOrNo") 89 | 90 | // The strings that will be shown for each item in the menu 91 | static var caseDisplayRepresentations: [YesOrNo: DisplayRepresentation] = [ 92 | .yes: "Yes", 93 | .no: "No" 94 | ] 95 | } 96 | -------------------------------------------------------------------------------- /Linkeeper/Bookmarks View/List View/BookmarkListItem.swift: -------------------------------------------------------------------------------- 1 | // 2 | // BookmarkListItem.swift 3 | // Linkeeper 4 | // 5 | // Created by Om Chachad on 06/01/24. 6 | // 7 | 8 | import Foundation 9 | import SwiftUI 10 | 11 | struct BookmarkListItem: View { 12 | @Environment(\.managedObjectContext) var moc 13 | @Environment(\.editMode) var editMode 14 | @Environment(\.openURL) var openURL 15 | 16 | var bookmark: Bookmark 17 | 18 | @Binding var showDetails: Bool 19 | @Binding var toBeEditedBookmark: Bookmark? 20 | 21 | @State private var cachedPreview: cachedPreview? 22 | 23 | var isEditing: Bool { 24 | #if os(macOS) 25 | return self._editMode.wrappedValue == .active 26 | #else 27 | return self.editMode?.wrappedValue == .active 28 | #endif 29 | } 30 | 31 | init(bookmark: Bookmark, showDetails: Binding, toBeEditedBookmark: Binding) { 32 | self.bookmark = bookmark 33 | self._showDetails = showDetails 34 | self._toBeEditedBookmark = toBeEditedBookmark 35 | 36 | #if !os(macOS) 37 | let cacheManager = CacheManager.instance 38 | 39 | self._cachedPreview = State(initialValue: cacheManager.get(for: bookmark)) 40 | #endif 41 | } 42 | 43 | var body: some View { 44 | HStack { 45 | Group { 46 | if let preview = cachedPreview?.image { 47 | preview 48 | .resizable() 49 | .scaledToFit() 50 | .scaledToFill() 51 | .frame(width: 44, height: 44) 52 | .clipped() 53 | .cornerRadius(8, style: .continuous) 54 | .shadow(radius: 2) 55 | } else if let firstChar = bookmark.wrappedTitle.first { 56 | Text(String(firstChar)) 57 | .font(.title) 58 | .foregroundColor(.white) 59 | .frame(width: 44, height: 44) 60 | .background { 61 | Group { 62 | #if os(macOS) 63 | Color(nsColor: .tertiaryLabelColor) 64 | #else 65 | Color(uiColor: .tertiaryLabel) 66 | #endif 67 | } 68 | .background(Color.white) 69 | .cornerRadius(8, style: .continuous) 70 | .shadow(radius: 2) 71 | } 72 | } 73 | } 74 | .padding([.vertical, .trailing], 5) 75 | .padding(.vertical, 5) 76 | 77 | VStack(alignment: .leading) { 78 | Text(bookmark.wrappedTitle) 79 | .lineLimit(3) 80 | Text(bookmark.wrappedHost) 81 | .lineLimit(1) 82 | .foregroundColor(.secondary) 83 | } 84 | 85 | Spacer() 86 | 87 | if bookmark.isFavorited { 88 | Image(systemName: "heart.fill") 89 | .foregroundColor(.pink) 90 | .font(.headline) 91 | } 92 | } 93 | .bookmarkItemActions(bookmark: bookmark, toBeEditedBookmark: $toBeEditedBookmark, showDetails: $showDetails, cachedPreview: $cachedPreview, includeOpenBookmarkButton: true) 94 | } 95 | 96 | func openBookmark() { 97 | if !isEditing { 98 | openURL(bookmark.wrappedURL) 99 | Task { 100 | await bookmark.cachePreviewInto($cachedPreview) 101 | } 102 | } 103 | } 104 | } 105 | -------------------------------------------------------------------------------- /Linkeeper.xcodeproj/xcshareddata/xcschemes/Share Extension.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 6 | 9 | 10 | 16 | 22 | 23 | 24 | 30 | 36 | 37 | 38 | 39 | 40 | 46 | 47 | 59 | 61 | 67 | 68 | 69 | 70 | 78 | 80 | 86 | 87 | 88 | 89 | 91 | 92 | 95 | 96 | 97 | -------------------------------------------------------------------------------- /Linkeeper.xcodeproj/xcshareddata/xcschemes/Share Extension Mac.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 6 | 10 | 11 | 17 | 23 | 24 | 25 | 31 | 37 | 38 | 39 | 40 | 41 | 47 | 48 | 60 | 62 | 68 | 69 | 70 | 71 | 79 | 81 | 87 | 88 | 89 | 90 | 92 | 93 | 96 | 97 | 98 | -------------------------------------------------------------------------------- /Linkeeper.xcodeproj/xcshareddata/xcschemes/Share Extension Vision.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 6 | 10 | 11 | 17 | 23 | 24 | 25 | 31 | 37 | 38 | 39 | 40 | 41 | 47 | 48 | 60 | 62 | 68 | 69 | 70 | 71 | 79 | 81 | 87 | 88 | 89 | 90 | 92 | 93 | 96 | 97 | 98 | -------------------------------------------------------------------------------- /Linkeeper/CoreData/FoldersManager.swift: -------------------------------------------------------------------------------- 1 | // 2 | // FoldersManager.swift 3 | // Linkeeper 4 | // 5 | // Created by Om Chachad on 29/05/23. 6 | // 7 | 8 | import Foundation 9 | import CoreData 10 | 11 | class FoldersManager { 12 | 13 | static let shared = FoldersManager() 14 | 15 | let context = DataController.shared.persistentCloudKitContainer.viewContext 16 | 17 | func getAllFolders() -> [Folder] { 18 | let request: NSFetchRequest = Folder.fetchRequest() 19 | do { 20 | return try context.fetch(request).sorted(by: { $0.index < $1.index }) 21 | } catch let error { 22 | print("Couldn't fetch all folders: \(error.localizedDescription)") 23 | return [] 24 | } 25 | } 26 | 27 | func getFolders(with predicate: NSPredicate) -> [Folder] { 28 | let request: NSFetchRequest = Folder.fetchRequest() 29 | request.predicate = predicate 30 | do { 31 | return try context.fetch(request).sorted(by: { $0.index < $1.index }) 32 | } catch let error { 33 | print("Couldn't fetch all folders: \(error.localizedDescription)") 34 | return [] 35 | } 36 | } 37 | 38 | func addFolder(title: String, accentColor: String, chosenSymbol: String, parentFolder: Folder? = nil) -> Folder { 39 | 40 | let newFolder = Folder(context: context) 41 | newFolder.id = UUID() 42 | newFolder.title = title 43 | newFolder.accentColor = accentColor 44 | newFolder.symbol = chosenSymbol 45 | newFolder.parentFolder = parentFolder 46 | if let parentFolder { 47 | newFolder.index = Int16((getFolders(with: NSPredicate(format: "parentFolder = %@", parentFolder)).last?.index ?? 0) + 1) 48 | } else { 49 | newFolder.index = Int16((getAllFolders().last?.index ?? 0) + 1) 50 | } 51 | 52 | saveContext() 53 | return newFolder 54 | } 55 | 56 | func findFolder(withId id: UUID) -> Folder { 57 | let request: NSFetchRequest = Folder.fetchRequest() 58 | request.fetchLimit = 1 59 | request.predicate = NSPredicate(format: "id = %@", id as CVarArg) 60 | 61 | guard let foundFolder = try? context.fetch(request).first else { 62 | fatalError("Could not find bookmark with ID") 63 | } 64 | 65 | return foundFolder 66 | } 67 | 68 | func doesExist(withId id: UUID) -> Bool { 69 | let request: NSFetchRequest = Folder.fetchRequest() 70 | request.fetchLimit = 1 71 | request.predicate = NSPredicate(format: "id = %@", id as CVarArg) 72 | 73 | guard (try? context.fetch(request).first) != nil else { 74 | return false 75 | } 76 | 77 | return true 78 | } 79 | 80 | enum DeletionAction { 81 | case keep 82 | case delete 83 | } 84 | 85 | func delete(_ folder: Folder, action: DeletionAction) { 86 | for bookmark in folder.bookmarksArray { 87 | switch(action) { 88 | case .keep: 89 | bookmark.folder = folder.parentFolder 90 | case .delete: 91 | context.delete(bookmark) 92 | } 93 | } 94 | 95 | for subFolder in folder.childFoldersArray ?? [] { 96 | switch(action) { 97 | case .keep: 98 | subFolder.parentFolder = folder.parentFolder 99 | case .delete: 100 | delete(subFolder, action: .delete) 101 | } 102 | } 103 | 104 | context.delete(folder) 105 | 106 | saveContext() 107 | } 108 | 109 | func delete(withId id: UUID, action: DeletionAction = .delete) { 110 | let matchingFolder = findFolder(withId: id) 111 | delete(matchingFolder, action: action) 112 | } 113 | 114 | func saveContext() { 115 | DispatchQueue.main.async { 116 | if self.context.hasChanges { 117 | try? self.context.save() 118 | } 119 | } 120 | } 121 | 122 | } 123 | 124 | -------------------------------------------------------------------------------- /Linkeeper/TipRequestView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // TipRequestView.swift 3 | // Linkeeper 4 | // 5 | // Created by Om Chachad on 11/05/24. 6 | // 7 | 8 | import SwiftUI 9 | 10 | struct TipRequestView: View { 11 | @State private var randomMessage = "Enjoying Linkeeper's magic? A tip would be the ultimate spell of appreciation! 🪄" 12 | @EnvironmentObject var storeKit: Store 13 | @Environment(\.openURL) var openURL 14 | @Environment(\.dismiss) var dismiss 15 | 16 | init() { 17 | let messages = ["Enjoying Linkeeper's magic? A tip would be the ultimate spell of appreciation! 🪄", "A tip would be the cherry on top! 🙏🏻", "Your love for Linkeeper is clear! How about showing some love to the developer too?\nTips appreciated! ❤️", "If you're feeling appreciative, a tip would be the icing on the virtual cake (and would keep the developer happy) 😉"] 18 | if let message = messages.randomElement() { 19 | self._randomMessage = State(initialValue: message) 20 | } 21 | } 22 | 23 | var body: some View { 24 | VStack { 25 | Button { 26 | dismiss() 27 | } label: { 28 | Image(systemName: "xmark") 29 | .font(.headline) 30 | .imageScale(.large) 31 | } 32 | .tint(.secondary) 33 | #if !os(macOS) 34 | .hoverEffect(.highlight) 35 | #endif 36 | #if os(visionOS) 37 | .glassBackgroundEffect(in: Circle()) 38 | #else 39 | .buttonStyle(.borderless) 40 | .padding(5) 41 | .background(.thickMaterial) 42 | .contentShape(Circle()) 43 | .clipShape(.circle) 44 | #endif 45 | .frame(maxWidth: .infinity, alignment: .trailing) 46 | 47 | Text("Looks like you are enjoying Linkeeper! 👀") 48 | .font(.title.bold()) 49 | .multilineTextAlignment(.center) 50 | .padding(.bottom, 5) 51 | Text(randomMessage) 52 | .multilineTextAlignment(.center) 53 | .padding(.bottom, 5) 54 | Spacer() 55 | 56 | if storeKit.tippableProducts.isEmpty { 57 | ProgressView() 58 | } else { 59 | VStack { 60 | ForEach(storeKit.tippableProducts) { product in 61 | TipItem(product: product) 62 | .environmentObject(storeKit) 63 | } 64 | } 65 | .padding(10) 66 | #if os(visionOS) 67 | .background(.thickMaterial) 68 | .cornerRadius(15, style: .continuous) 69 | #else 70 | .background(.thinMaterial) 71 | .cornerRadius(10, style: .continuous) 72 | #endif 73 | } 74 | 75 | Spacer() 76 | 77 | Text("OR") 78 | .font(.system(size: 12, weight: .thin)) 79 | .foregroundStyle(.secondary) 80 | Spacer() 81 | 82 | Button { 83 | openURL(URL(string:"https://apps.apple.com/app/id6449708232?mt=8&action=write-review")!) 84 | } label: { 85 | Text("Write a review") 86 | .foregroundColor(.white) 87 | .bold() 88 | .padding(isMac ? 10 : 15) 89 | .frame(maxWidth: .infinity) 90 | .background(Color.accentColor) 91 | } 92 | .buttonStyle(.borderless) 93 | #if os(visionOS) 94 | .background(.blue) 95 | .clipShape(.capsule) 96 | .buttonBorderShape(.capsule) 97 | #else 98 | .clipShape(RoundedRectangle(cornerRadius: 15, style: .continuous)) 99 | #endif 100 | }.padding() 101 | #if os(visionOS) 102 | .frame(maxWidth: 600, idealHeight: 550) 103 | #endif 104 | } 105 | } 106 | 107 | #Preview { 108 | TipRequestView() 109 | .environmentObject(Store()) 110 | } 111 | -------------------------------------------------------------------------------- /Linkeeper/Helpers/Extensions/View.swift: -------------------------------------------------------------------------------- 1 | // 2 | // View.swift 3 | // Linkeeper 4 | // 5 | // Created by Om Chachad on 16/05/23. 6 | // 7 | 8 | import Foundation 9 | import SwiftUI 10 | 11 | extension View { 12 | func navigationTitle(for folder: Folder?, folderTitle: Binding, onlyFavorites: Bool) -> some View { 13 | Group { 14 | #if os(macOS) 15 | self 16 | .navigationTitle(folder?.wrappedTitle ?? (onlyFavorites == true ? "Favorites" : "All Bookmarks")) 17 | #else 18 | if #available(iOS 16.0, macOS 13.0, *), folder != nil { 19 | self 20 | .navigationTitle(folderTitle) 21 | } else { 22 | self 23 | .navigationTitle(folder?.wrappedTitle ?? (onlyFavorites == true ? "Favorites" : "All Bookmarks")) 24 | } 25 | #endif 26 | } 27 | } 28 | 29 | func groupedFormStyle() -> some View { 30 | Group { 31 | #if os(macOS) 32 | if #available(macOS 13.0, *) { 33 | self 34 | .formStyle(.grouped) 35 | } else { 36 | self.padding() 37 | } 38 | #else 39 | self 40 | #endif 41 | } 42 | } 43 | 44 | func glow() -> some View { 45 | self 46 | .background(self.blur(radius: 5)) 47 | } 48 | 49 | func sideBarForMac() -> some View { 50 | #if os(macOS) 51 | return self 52 | .listStyle(.sidebar) 53 | #else 54 | return self 55 | .listStyle(.insetGrouped) 56 | #endif 57 | } 58 | 59 | func cornerRadius(_ radius: CGFloat, style: RoundedCornerStyle) -> some View { 60 | self 61 | .clipShape(RoundedRectangle(cornerRadius: radius, style: style)) 62 | } 63 | 64 | /// Applies the given transform if the given condition evaluates to `true`. 65 | /// - Parameters: 66 | /// - condition: The condition to evaluate. 67 | /// - transform: The transform to apply to the source `View`. 68 | /// - Returns: Either the original `View` or the modified `View` if the condition is `true`. 69 | @ViewBuilder func `if`(_ condition: Bool, transform: (Self) -> Content) -> some View { 70 | if condition { 71 | transform(self) 72 | } else { 73 | self 74 | } 75 | } 76 | 77 | func contentUnavailabilityView(for content: any Collection, @ViewBuilder unavailabilityView: () -> Content) -> some View { 78 | Group { 79 | if content.isEmpty { 80 | unavailabilityView() 81 | } else { 82 | self 83 | } 84 | } 85 | } 86 | 87 | func contentUnavailabilityView(isUnavailable: Bool, @ViewBuilder unavailabilityView: () -> Content) -> some View { 88 | Group { 89 | if isUnavailable { 90 | unavailabilityView() 91 | } else { 92 | self 93 | } 94 | } 95 | } 96 | 97 | func scrollContentBackground(visibility: Visibility) -> some View { 98 | if #available(iOS 16.0, macOS 13.0, *) { 99 | return self.scrollContentBackground(visibility) 100 | } else { 101 | return self 102 | } 103 | } 104 | 105 | #if os(iOS) 106 | func stackModeOniPhone() -> some View { 107 | Group { 108 | if UIDevice.current.userInterfaceIdiom == .phone { 109 | self 110 | .navigationViewStyle(.stack) 111 | } else { 112 | self 113 | } 114 | } 115 | } 116 | #endif 117 | 118 | func forceHiddenScrollIndicators() -> some View { 119 | Group { 120 | #if os(macOS) 121 | if #available(macOS 13.0, *) { 122 | self 123 | .scrollIndicators(.never) 124 | } else { 125 | self 126 | } 127 | #else 128 | self 129 | #endif 130 | } 131 | } 132 | } 133 | --------------------------------------------------------------------------------