Skip to content

Commit

Permalink
Feature: Search and sort bookmarks management (#3093)
Browse files Browse the repository at this point in the history
  • Loading branch information
jotaemepereira authored Aug 9, 2024
1 parent 8b2424c commit 8a3d57f
Show file tree
Hide file tree
Showing 18 changed files with 1,269 additions and 222 deletions.
18 changes: 18 additions & 0 deletions DuckDuckGo.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -2533,13 +2533,19 @@
B6F9BDE52B45CD1900677B33 /* ModalView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6F9BDE32B45CD1900677B33 /* ModalView.swift */; };
B6FA893F269C424500588ECD /* PrivacyDashboardViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6FA893E269C424500588ECD /* PrivacyDashboardViewController.swift */; };
B6FA8941269C425400588ECD /* PrivacyDashboardPopover.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6FA8940269C425400588ECD /* PrivacyDashboardPopover.swift */; };
BB470EBB2C5A66D6002EE91D /* BookmarkManagementDetailViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = BB470EBA2C5A66D6002EE91D /* BookmarkManagementDetailViewModel.swift */; };
BB470EBC2C5A66D6002EE91D /* BookmarkManagementDetailViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = BB470EBA2C5A66D6002EE91D /* BookmarkManagementDetailViewModel.swift */; };
BB5789722B2CA70F0009DFE2 /* DataBrokerProtectionSubscriptionEventHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = BB5789712B2CA70F0009DFE2 /* DataBrokerProtectionSubscriptionEventHandler.swift */; };
BB7B5F982C4ED73800BA4AF8 /* BookmarksSearchAndSortMetrics.swift in Sources */ = {isa = PBXBuildFile; fileRef = BB7B5F972C4ED73800BA4AF8 /* BookmarksSearchAndSortMetrics.swift */; };
BB7B5F992C4ED73800BA4AF8 /* BookmarksSearchAndSortMetrics.swift in Sources */ = {isa = PBXBuildFile; fileRef = BB7B5F972C4ED73800BA4AF8 /* BookmarksSearchAndSortMetrics.swift */; };
BBB881882C4029BA001247C6 /* BookmarkListTreeControllerSearchDataSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = BBB881872C4029BA001247C6 /* BookmarkListTreeControllerSearchDataSource.swift */; };
BBB881892C4029BA001247C6 /* BookmarkListTreeControllerSearchDataSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = BBB881872C4029BA001247C6 /* BookmarkListTreeControllerSearchDataSource.swift */; };
BBBEE1BF2C4FF63600035ABA /* SortBookmarksViewModelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = BBBEE1BE2C4FF63600035ABA /* SortBookmarksViewModelTests.swift */; };
BBBEE1C02C4FF63600035ABA /* SortBookmarksViewModelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = BBBEE1BE2C4FF63600035ABA /* SortBookmarksViewModelTests.swift */; };
BBC063E82C5A9E4B007BDC18 /* BookmarkManagementDetailViewModelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = BBC063E72C5A9E4B007BDC18 /* BookmarkManagementDetailViewModelTests.swift */; };
BBC063E92C5A9E4B007BDC18 /* BookmarkManagementDetailViewModelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = BBC063E72C5A9E4B007BDC18 /* BookmarkManagementDetailViewModelTests.swift */; };
BBE013EA2C5BFD660025F2C6 /* BookmarksEmptyStateContent.swift in Sources */ = {isa = PBXBuildFile; fileRef = BBE013E92C5BFD660025F2C6 /* BookmarksEmptyStateContent.swift */; };
BBE013EB2C5BFD660025F2C6 /* BookmarksEmptyStateContent.swift in Sources */ = {isa = PBXBuildFile; fileRef = BBE013E92C5BFD660025F2C6 /* BookmarksEmptyStateContent.swift */; };
BBFB727F2C48047C0088884C /* SortBookmarksViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = BBFB727E2C48047C0088884C /* SortBookmarksViewModel.swift */; };
BBFB72802C48047C0088884C /* SortBookmarksViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = BBFB727E2C48047C0088884C /* SortBookmarksViewModel.swift */; };
BBFF355D2C4AF26200DA3289 /* BookmarksSortModeTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = BBFF355C2C4AF26200DA3289 /* BookmarksSortModeTests.swift */; };
Expand Down Expand Up @@ -4220,10 +4226,13 @@
B6F9BDE32B45CD1900677B33 /* ModalView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ModalView.swift; sourceTree = "<group>"; };
B6FA893E269C424500588ECD /* PrivacyDashboardViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PrivacyDashboardViewController.swift; sourceTree = "<group>"; };
B6FA8940269C425400588ECD /* PrivacyDashboardPopover.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PrivacyDashboardPopover.swift; sourceTree = "<group>"; };
BB470EBA2C5A66D6002EE91D /* BookmarkManagementDetailViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BookmarkManagementDetailViewModel.swift; sourceTree = "<group>"; };
BB5789712B2CA70F0009DFE2 /* DataBrokerProtectionSubscriptionEventHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DataBrokerProtectionSubscriptionEventHandler.swift; sourceTree = "<group>"; };
BB7B5F972C4ED73800BA4AF8 /* BookmarksSearchAndSortMetrics.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BookmarksSearchAndSortMetrics.swift; sourceTree = "<group>"; };
BBB881872C4029BA001247C6 /* BookmarkListTreeControllerSearchDataSource.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BookmarkListTreeControllerSearchDataSource.swift; sourceTree = "<group>"; };
BBBEE1BE2C4FF63600035ABA /* SortBookmarksViewModelTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SortBookmarksViewModelTests.swift; sourceTree = "<group>"; };
BBC063E72C5A9E4B007BDC18 /* BookmarkManagementDetailViewModelTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BookmarkManagementDetailViewModelTests.swift; sourceTree = "<group>"; };
BBE013E92C5BFD660025F2C6 /* BookmarksEmptyStateContent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BookmarksEmptyStateContent.swift; sourceTree = "<group>"; };
BBFB727E2C48047C0088884C /* SortBookmarksViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SortBookmarksViewModel.swift; sourceTree = "<group>"; };
BBFF355C2C4AF26200DA3289 /* BookmarksSortModeTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BookmarksSortModeTests.swift; sourceTree = "<group>"; };
BD384AC72BBC821100EF3735 /* vpn-light-mode.json */ = {isa = PBXFileReference; lastKnownFileType = text.json; path = "vpn-light-mode.json"; sourceTree = "<group>"; };
Expand Down Expand Up @@ -6670,6 +6679,7 @@
9FA5A0A82BC900FC00153786 /* BookmarkAllTabsDialogViewModelTests.swift */,
BBFF355C2C4AF26200DA3289 /* BookmarksSortModeTests.swift */,
BBBEE1BE2C4FF63600035ABA /* SortBookmarksViewModelTests.swift */,
BBC063E72C5A9E4B007BDC18 /* BookmarkManagementDetailViewModelTests.swift */,
);
path = ViewModels;
sourceTree = "<group>";
Expand Down Expand Up @@ -7498,6 +7508,8 @@
9F9C4A002BC7F36D0099738D /* BookmarkAllTabsDialogCoordinatorViewModel.swift */,
9F9C49FC2BC7E9820099738D /* BookmarkAllTabsDialogViewModel.swift */,
BBFB727E2C48047C0088884C /* SortBookmarksViewModel.swift */,
BB470EBA2C5A66D6002EE91D /* BookmarkManagementDetailViewModel.swift */,
BBE013E92C5BFD660025F2C6 /* BookmarksEmptyStateContent.swift */,
);
path = ViewModel;
sourceTree = "<group>";
Expand Down Expand Up @@ -10171,6 +10183,7 @@
3706FB62293F65D500E42796 /* CSVParser.swift in Sources */,
B626A75B29921FAA00053070 /* NavigationActionPolicyExtension.swift in Sources */,
4B9DB02A2A983B24000927DB /* WaitlistStorage.swift in Sources */,
BB470EBC2C5A66D6002EE91D /* BookmarkManagementDetailViewModel.swift in Sources */,
3706FB65293F65D500E42796 /* PrivacyDashboardWebView.swift in Sources */,
372BC2A22A4AFA47001D8FD5 /* SyncCredentialsAdapter.swift in Sources */,
3706FB66293F65D500E42796 /* AppearancePreferences.swift in Sources */,
Expand Down Expand Up @@ -10336,6 +10349,7 @@
4BF97ADC2B43C5E200EB4240 /* VPNFeedbackSender.swift in Sources */,
987799F72999996B005D8EB6 /* BookmarkDatabase.swift in Sources */,
F1C5763F2BFF972900C78647 /* SubscriptionUIHandling.swift in Sources */,
BBE013EB2C5BFD660025F2C6 /* BookmarksEmptyStateContent.swift in Sources */,
BDA7647D2BC497BE00D0400C /* DefaultVPNLocationFormatter.swift in Sources */,
3706FBCC293F65D500E42796 /* CustomRoundedCornersShape.swift in Sources */,
3706FBCD293F65D500E42796 /* LocaleExtension.swift in Sources */,
Expand Down Expand Up @@ -10699,6 +10713,7 @@
3706FDE5293F661700E42796 /* URLEventHandlerTests.swift in Sources */,
3706FDE6293F661700E42796 /* BookmarkOutlineViewDataSourceTests.swift in Sources */,
3706FDE7293F661700E42796 /* RecentlyVisitedSiteModelTests.swift in Sources */,
BBC063E92C5A9E4B007BDC18 /* BookmarkManagementDetailViewModelTests.swift in Sources */,
3706FDE8293F661700E42796 /* TemporaryFileHandlerTests.swift in Sources */,
3706FDE9293F661700E42796 /* StateRestorationManagerTests.swift in Sources */,
374EF08429B7575B003D2E87 /* RecentlyClosedCoordinatorTests.swift in Sources */,
Expand Down Expand Up @@ -11459,6 +11474,7 @@
AAC6881928626BF800D54247 /* RecentlyClosedTab.swift in Sources */,
B688B4DF27420D290087BEAF /* PDFSearchTextMenuItemHandler.swift in Sources */,
AA7E919728746BCC00AB6B62 /* HistoryMenu.swift in Sources */,
BB470EBB2C5A66D6002EE91D /* BookmarkManagementDetailViewModel.swift in Sources */,
F4A6198C283CFFBB007F2080 /* ContentScopeFeatureFlagging.swift in Sources */,
85707F24276A332A00DC0649 /* OnboardingButtonStyles.swift in Sources */,
4B8A4E0127C8447E005F40E8 /* SaveIdentityPopover.swift in Sources */,
Expand Down Expand Up @@ -11556,6 +11572,7 @@
CB24F70C29A3D9CB006DCC58 /* AppConfigurationURLProvider.swift in Sources */,
1DEF3BAD2BD145A9004A2FBA /* AutoClearHandler.swift in Sources */,
37CBCA9A2A8966E60050218F /* SyncSettingsAdapter.swift in Sources */,
BBE013EA2C5BFD660025F2C6 /* BookmarksEmptyStateContent.swift in Sources */,
EEC4A66D2B2C894F00F7C0AA /* VPNLocationPreferenceItemModel.swift in Sources */,
3776582F27F82E62009A6B35 /* AutofillPreferences.swift in Sources */,
7BEC20422B0F505F00243D3E /* AddBookmarkPopoverView.swift in Sources */,
Expand Down Expand Up @@ -12349,6 +12366,7 @@
B6A5A2A825BAA35500AA7ADA /* WindowManagerStateRestorationTests.swift in Sources */,
B6AE39F129373AF200C37AA4 /* EmptyAttributionRulesProver.swift in Sources */,
4BB99D1126FE1A84001E4761 /* SafariBookmarksReaderTests.swift in Sources */,
BBC063E82C5A9E4B007BDC18 /* BookmarkManagementDetailViewModelTests.swift in Sources */,
1DA860722BE3AE950027B813 /* DockPositionProviderTests.swift in Sources */,
4BBF0925283083EC00EE1418 /* FileSystemDSLTests.swift in Sources */,
4B11060A25903EAC0039B979 /* CoreDataEncryptionTests.swift in Sources */,
Expand Down
14 changes: 13 additions & 1 deletion DuckDuckGo/Bookmarks/Model/BookmarkManager.swift
Original file line number Diff line number Diff line change
Expand Up @@ -61,12 +61,14 @@ protocol BookmarkManager: AnyObject {
var listPublisher: Published<BookmarkList?>.Publisher { get }
var list: BookmarkList? { get }

var sortModePublisher: Published<BookmarksSortMode>.Publisher { get }
var sortMode: BookmarksSortMode { get set }

func requestSync()

}

final class LocalBookmarkManager: BookmarkManager {

static let shared = LocalBookmarkManager()

init(bookmarkStore: BookmarkStore? = nil, faviconManagement: FaviconManagement? = nil) {
Expand All @@ -76,7 +78,9 @@ final class LocalBookmarkManager: BookmarkManager {
if let faviconManagement {
self.faviconManagement = faviconManagement
}

self.subscribeToFavoritesDisplayMode()
self.sortMode = sortRepository.storedSortMode
}

private func subscribeToFavoritesDisplayMode() {
Expand All @@ -93,8 +97,16 @@ final class LocalBookmarkManager: BookmarkManager {
@Published private(set) var list: BookmarkList?
var listPublisher: Published<BookmarkList?>.Publisher { $list }

@Published var sortMode: BookmarksSortMode = .manual {
didSet {
sortRepository.storedSortMode = sortMode
}
}
var sortModePublisher: Published<BookmarksSortMode>.Publisher { $sortMode }

private lazy var bookmarkStore: BookmarkStore = LocalBookmarkStore(bookmarkDatabase: BookmarkDatabase.shared)
private lazy var faviconManagement: FaviconManagement = FaviconManager.shared
private lazy var sortRepository: SortBookmarksRepository = SortBookmarksUserDefaults()

private var favoritesDisplayMode: FavoritesDisplayMode = .displayNative(.desktop)
private var favoritesDisplayModeCancellable: AnyCancellable?
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -209,8 +209,8 @@ final class BookmarkOutlineViewDataSource: NSObject, NSOutlineViewDataSource, NS
return .none
}

let bookmarks = PasteboardBookmark.pasteboardBookmarks(with: info.draggingPasteboard)
let folders = PasteboardFolder.pasteboardFolders(with: info.draggingPasteboard)
let bookmarks = PasteboardBookmark.pasteboardBookmarks(with: info.draggingPasteboard.pasteboardItems)
let folders = PasteboardFolder.pasteboardFolders(with: info.draggingPasteboard.pasteboardItems)

if let bookmarks = bookmarks, let folders = folders {
let canMoveBookmarks = validateDrop(for: bookmarks, destination: destinationNode) == .move
Expand Down
50 changes: 28 additions & 22 deletions DuckDuckGo/Bookmarks/Model/BookmarkSidebarTreeController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ import Foundation
final class BookmarkSidebarTreeController: BookmarkTreeControllerDataSource {

func treeController(childNodesFor node: BookmarkNode, sortMode: BookmarksSortMode) -> [BookmarkNode] {
return node.isRoot ? childNodesForRootNode(node) : childNodes(for: node)
return node.isRoot ? childNodesForRootNode(node) : childNodes(for: node, sortMode: sortMode)
}

private let bookmarkManager: BookmarkManager
Expand All @@ -40,13 +40,13 @@ final class BookmarkSidebarTreeController: BookmarkTreeControllerDataSource {
return [bookmarksNode]
}

private func childNodes(for parentNode: BookmarkNode) -> [BookmarkNode] {
private func childNodes(for parentNode: BookmarkNode, sortMode: BookmarksSortMode) -> [BookmarkNode] {
if let pseudoFolder = parentNode.representedObject as? PseudoFolder, pseudoFolder == PseudoFolder.bookmarks {
return childNodesForBookmarksPseudoFolder(parentNode)
return childNodesForBookmarksPseudoFolder(parentNode, sortMode: sortMode)
}

if let folder = parentNode.representedObject as? BookmarkFolder {
return childNodes(for: folder, parentNode: parentNode)
return childNodes(for: folder, parentNode: parentNode, sortMode: sortMode)
}

return []
Expand All @@ -59,39 +59,45 @@ final class BookmarkSidebarTreeController: BookmarkTreeControllerDataSource {
return node
}

private func childNodesForBookmarksPseudoFolder(_ parent: BookmarkNode) -> [BookmarkNode] {
let nodes = bookmarkManager.list?.topLevelEntities.compactMap { (possibleFolder) -> BookmarkNode? in
guard let folder = possibleFolder as? BookmarkFolder else { return nil }
private func childNodesForBookmarksPseudoFolder(_ parent: BookmarkNode, sortMode: BookmarksSortMode) -> [BookmarkNode] {
let nodes = bookmarkManager.list?.topLevelEntities
.sorted(by: sortMode)
.compactMap { (possibleFolder) -> BookmarkNode? in
guard let folder = possibleFolder as? BookmarkFolder else { return nil }

let folderNode = parent.findOrCreateChildNode(with: folder)
folderNode.canHaveChildNodes = !folder.childFolders.isEmpty
let folderNode = parent.findOrCreateChildNode(with: folder)
folderNode.canHaveChildNodes = !folder.childFolders.isEmpty

return folderNode
} ?? []
return folderNode
} ?? []

return nodes
}

private func childNodes(for folder: BookmarkFolder, parentNode: BookmarkNode) -> [BookmarkNode] {
var children = [BookmarkFolder]()
private func childNodes(for folder: BookmarkFolder, parentNode: BookmarkNode, sortMode: BookmarksSortMode) -> [BookmarkNode] {
var children = [BaseBookmarkEntity]()
var updatedChildNodes = [BookmarkNode]()

for folder in folder.childFolders {
children.append(folder)
}

children.forEach { folder in
if let existingNode = parentNode.childNodeRepresenting(object: folder) {
if !updatedChildNodes.contains(existingNode) {
updatedChildNodes += [existingNode]
return
children
.sorted(by: sortMode)
.forEach { folder in
if let folder = folder as? BookmarkFolder {
if let existingNode = parentNode.childNodeRepresenting(object: folder) {
if !updatedChildNodes.contains(existingNode) {
updatedChildNodes += [existingNode]
return
}
}

let newNode = self.createNode(with: folder, parent: parentNode)
updatedChildNodes += [newNode]
}
}

let newNode = self.createNode(with: folder, parent: parentNode)
updatedChildNodes += [newNode]
}

return updatedChildNodes
}

Expand Down
4 changes: 2 additions & 2 deletions DuckDuckGo/Bookmarks/Model/PasteboardBookmark.swift
Original file line number Diff line number Diff line change
Expand Up @@ -58,8 +58,8 @@ struct PasteboardBookmark: Hashable {
self.init(dictionary: dictionary)
}

static func pasteboardBookmarks(with pasteboard: NSPasteboard) -> Set<PasteboardBookmark>? {
guard let items = pasteboard.pasteboardItems else {
static func pasteboardBookmarks(with pasteboardItems: [NSPasteboardItem]?) -> Set<PasteboardBookmark>? {
guard let items = pasteboardItems else {
return nil
}

Expand Down
4 changes: 2 additions & 2 deletions DuckDuckGo/Bookmarks/Model/PasteboardFolder.swift
Original file line number Diff line number Diff line change
Expand Up @@ -56,8 +56,8 @@ struct PasteboardFolder: Hashable {
self.init(dictionary: dictionary)
}

static func pasteboardFolders(with pasteboard: NSPasteboard) -> Set<PasteboardFolder>? {
guard let items = pasteboard.pasteboardItems else {
static func pasteboardFolders(with pasteboardItems: [NSPasteboardItem]?) -> Set<PasteboardFolder>? {
guard let items = pasteboardItems else {
return nil
}

Expand Down
Loading

0 comments on commit 8a3d57f

Please sign in to comment.