├── .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 |
--------------------------------------------------------------------------------