| // Copyright 2013 The Chromium Authors |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "chrome/browser/ui/bookmarks/bookmark_editor.h" |
| |
| #include <stddef.h> |
| |
| #include "base/containers/flat_set.h" |
| #include "chrome/grit/generated_resources.h" |
| #include "components/bookmarks/browser/bookmark_model.h" |
| #include "components/bookmarks/browser/bookmark_node.h" |
| #include "components/bookmarks/common/bookmark_metrics.h" |
| #include "components/strings/grit/components_strings.h" |
| |
| using bookmarks::BookmarkModel; |
| using bookmarks::BookmarkNode; |
| |
| namespace { |
| |
| const BookmarkNode* CreateNewNode(BookmarkModel* model, |
| const BookmarkNode* parent, |
| const BookmarkEditor::EditDetails& details, |
| const std::u16string& new_title, |
| const GURL& new_url) { |
| const BookmarkNode* node = nullptr; |
| // When create the new one to right-clicked folder, add it to the next to the |
| // folder's position. Because |details.index| has a index of the folder when |
| // it was right-clicked, it might cause out of range exception when another |
| // bookmark manager edits contents of the folder. |
| // So we must check the range. |
| size_t child_count = parent->children().size(); |
| size_t insert_index = |
| (parent == details.parent_node && details.index.has_value() && |
| details.index.value() <= child_count) |
| ? details.index.value() |
| : child_count; |
| switch (details.type) { |
| case BookmarkEditor::EditDetails::NEW_URL: |
| node = model->AddNewURL(parent, insert_index, new_title, new_url); |
| break; |
| case BookmarkEditor::EditDetails::NEW_FOLDER: |
| case BookmarkEditor::EditDetails::CONVERT_TAB_GROUP_TO_FOLDER: { |
| node = model->AddFolder(parent, insert_index, new_title); |
| for (const auto& bookmark_data : details.bookmark_data.children) { |
| if (bookmark_data.url.has_value()) { |
| model->AddURL(node, node->children().size(), bookmark_data.title, |
| bookmark_data.url.value()); |
| } else { |
| const BookmarkNode* nested_node = model->AddFolder( |
| node, node->children().size(), bookmark_data.title); |
| for (auto& child : bookmark_data.children) { |
| // We do not expect to create new folders more than 2 levels deep. |
| DCHECK(child.url.has_value()); |
| model->AddURL(nested_node, nested_node->children().size(), |
| child.title, child.url.value()); |
| } |
| model->SetDateFolderModified(node, base::Time::Now()); |
| } |
| } |
| model->SetDateFolderModified(parent, base::Time::Now()); |
| |
| break; |
| } |
| case BookmarkEditor::EditDetails::EXISTING_NODE: |
| case BookmarkEditor::EditDetails::MOVE: |
| NOTREACHED(); |
| } |
| |
| return node; |
| } |
| |
| const BookmarkNode* GetParentNodeForMove( |
| bookmarks::BookmarkModel* model, |
| const std::vector< |
| raw_ptr<const bookmarks::BookmarkNode, VectorExperimental>>& nodes) { |
| CHECK(!nodes.empty()); |
| const BookmarkNode* first_parent = nodes[0].get()->parent(); |
| |
| bool same_parent = std::ranges::all_of(nodes, [&first_parent](auto node) { |
| return node->parent() == first_parent; |
| }); |
| |
| // Default to the parent node if all of the nodes have the same parent. |
| if (same_parent) { |
| return first_parent; |
| } |
| |
| // If the nodes do not have the same parent, but at least one of them is |
| // saved to account storage, default to the account other node. |
| if (model->account_other_node()) { |
| bool only_local_nodes = std::ranges::all_of( |
| nodes, [&model](auto node) { return model->IsLocalOnlyNode(*node); }); |
| |
| if (!only_local_nodes) { |
| return model->account_other_node(); |
| } |
| } |
| |
| // If the nodes are all saved to local storage or sync is enabled, default |
| // to the local other node. |
| return model->other_node(); |
| } |
| |
| } // namespace |
| |
| BookmarkEditor::EditDetails::BookmarkData::BookmarkData() = default; |
| |
| BookmarkEditor::EditDetails::BookmarkData::BookmarkData( |
| BookmarkData const& other) = default; |
| |
| BookmarkEditor::EditDetails::BookmarkData::~BookmarkData() = default; |
| |
| BookmarkEditor::EditDetails::EditDetails(Type node_type) : type(node_type) {} |
| |
| bool BookmarkEditor::EditDetails::CanChangeUrl() const { |
| switch (type) { |
| case EXISTING_NODE: |
| return existing_node->type() == BookmarkNode::URL; |
| case NEW_URL: |
| return true; |
| case NEW_FOLDER: |
| case CONVERT_TAB_GROUP_TO_FOLDER: |
| case MOVE: |
| return false; |
| } |
| } |
| |
| int BookmarkEditor::EditDetails::GetWindowTitleId() const { |
| int dialog_title = IDS_BOOKMARK_EDITOR_TITLE; |
| switch (type) { |
| case EditDetails::EXISTING_NODE: |
| case EditDetails::NEW_URL: |
| dialog_title = (type == EditDetails::EXISTING_NODE && |
| existing_node->type() == BookmarkNode::FOLDER) |
| ? IDS_BOOKMARK_FOLDER_EDITOR_WINDOW_TITLE |
| : IDS_BOOKMARK_EDITOR_TITLE; |
| break; |
| case EditDetails::NEW_FOLDER: |
| dialog_title = bookmark_data.children.empty() |
| ? IDS_BOOKMARK_FOLDER_EDITOR_WINDOW_TITLE_NEW |
| : IDS_BOOKMARK_ALL_TABS_DIALOG_TITLE; |
| break; |
| case EditDetails::CONVERT_TAB_GROUP_TO_FOLDER: |
| dialog_title = IDS_TAB_GROUP_TO_BOOKMARK_FOLDER_TITLE; |
| break; |
| case EditDetails::MOVE: |
| dialog_title = IDS_BOOKMARK_MOVE_DIALOG_TITLE; |
| break; |
| default: |
| NOTREACHED(); |
| } |
| return dialog_title; |
| } |
| |
| BookmarkEditor::EditDetails BookmarkEditor::EditDetails::EditNode( |
| const BookmarkNode* node) { |
| EditDetails details(EXISTING_NODE); |
| details.existing_node = node; |
| if (node) { |
| details.parent_node = node->parent(); |
| } |
| return details; |
| } |
| |
| BookmarkEditor::EditDetails BookmarkEditor::EditDetails::AddNodeInFolder( |
| const BookmarkNode* parent_node, |
| size_t index, |
| const GURL& url, |
| const std::u16string& title) { |
| EditDetails details(NEW_URL); |
| details.parent_node = parent_node; |
| details.index = index; |
| details.bookmark_data.url = url; |
| details.bookmark_data.title = title; |
| return details; |
| } |
| |
| BookmarkEditor::EditDetails BookmarkEditor::EditDetails::AddFolder( |
| const BookmarkNode* parent_node, |
| size_t index) { |
| EditDetails details(NEW_FOLDER); |
| details.parent_node = parent_node; |
| details.index = index; |
| return details; |
| } |
| |
| BookmarkEditor::EditDetails BookmarkEditor::EditDetails::TabGroupToFolder( |
| const BookmarkNode* parent_node, |
| size_t index, |
| const std::u16string& title) { |
| EditDetails details(CONVERT_TAB_GROUP_TO_FOLDER); |
| details.parent_node = parent_node; |
| details.index = index; |
| details.bookmark_data.title = title; |
| return details; |
| } |
| |
| BookmarkEditor::EditDetails BookmarkEditor::EditDetails::MoveNodes( |
| bookmarks::BookmarkModel* model, |
| const std::vector< |
| raw_ptr<const bookmarks::BookmarkNode, VectorExperimental>>& nodes) { |
| EditDetails details(MOVE); |
| |
| details.existing_nodes_to_move = base::MakeFlatSet< |
| raw_ptr<const bookmarks::BookmarkNode, VectorExperimental>>(nodes); |
| |
| details.parent_node = GetParentNodeForMove(model, nodes); |
| |
| return details; |
| } |
| |
| BookmarkEditor::EditDetails::EditDetails(const EditDetails& other) = default; |
| |
| BookmarkEditor::EditDetails::~EditDetails() = default; |
| |
| // static |
| void BookmarkEditor::ApplyEdits(BookmarkModel* model, |
| const BookmarkNode* new_parent, |
| const EditDetails& details, |
| const std::u16string& new_title, |
| const GURL& new_url) { |
| if (details.type == EditDetails::NEW_URL || |
| details.type == EditDetails::NEW_FOLDER || |
| details.type == EditDetails::CONVERT_TAB_GROUP_TO_FOLDER) { |
| CreateNewNode(model, new_parent, details, new_title, new_url); |
| return; |
| } |
| |
| if (details.type == EditDetails::MOVE) { |
| std::vector<raw_ptr<const bookmarks::BookmarkNode, VectorExperimental>> |
| nodes(details.existing_nodes_to_move.begin(), |
| details.existing_nodes_to_move.end()); |
| |
| // Persist the nodes' order within the same parent folder in the new |
| // location. Between different parent folders, choose the order depending on |
| // when they were last modified. More recently modified folders are rowed |
| // last, as new bookmarks are usually added to the back of a folder. |
| std::sort( |
| nodes.begin(), nodes.end(), |
| [](const bookmarks::BookmarkNode* l, const bookmarks::BookmarkNode* r) { |
| if (l->parent() != r->parent()) { |
| return l->parent()->date_folder_modified() < |
| r->parent()->date_folder_modified(); |
| } |
| return l->parent()->GetIndexOf(l) < r->parent()->GetIndexOf(r); |
| }); |
| |
| for (const bookmarks::BookmarkNode* node_to_move : nodes) { |
| model->Move(node_to_move, new_parent, new_parent->children().size()); |
| } |
| return; |
| } |
| |
| const BookmarkNode* node = details.existing_node; |
| DCHECK(node); |
| |
| if (new_parent != node->parent()) { |
| model->Move(node, new_parent, new_parent->children().size()); |
| } |
| if (node->is_url()) { |
| model->SetURL(node, new_url, bookmarks::metrics::BookmarkEditSource::kUser); |
| } |
| |
| model->SetTitle(node, new_title, |
| bookmarks::metrics::BookmarkEditSource::kUser); |
| } |