diff --git a/ftl/core/actions.ftl b/ftl/core/actions.ftl index 5822bb7ab..11be42642 100644 --- a/ftl/core/actions.ftl +++ b/ftl/core/actions.ftl @@ -24,6 +24,7 @@ actions-rebuild = Rebuild actions-red-flag = Red Flag actions-rename = Rename actions-rename-deck = Rename Deck +actions-rename-tag = Rename Tag actions-replay-audio = Replay Audio actions-reposition = Reposition actions-save = Save diff --git a/ftl/core/browsing.ftl b/ftl/core/browsing.ftl index b359e2012..eea2c7fb5 100644 --- a/ftl/core/browsing.ftl +++ b/ftl/core/browsing.ftl @@ -94,6 +94,7 @@ browsing-step = Step: browsing-studied-today = Studied Today browsing-suspended = Suspended browsing-tag-duplicates = Tag Duplicates +browsing-tag-rename-warning-empty = You can't rename a tag that has no notes. browsing-target-field = Target field: browsing-today = Today browsing-toggle-mark = Toggle Mark diff --git a/qt/aqt/sidebar.py b/qt/aqt/sidebar.py index 5cfe40686..c55091d29 100644 --- a/qt/aqt/sidebar.py +++ b/qt/aqt/sidebar.py @@ -3,12 +3,14 @@ # License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html from __future__ import annotations +import re from enum import Enum import aqt from anki.errors import DeckRenameError +from aqt.main import ResetReason from aqt.qt import * -from aqt.utils import TR, getOnlyText, showWarning, tr +from aqt.utils import TR, getOnlyText, showInfo, showWarning, tr class SidebarItemType(Enum): @@ -70,6 +72,7 @@ class NewSidebarTreeView(SidebarTreeViewBase): self.customContextMenuRequested.connect(self.onContextMenu) # type: ignore self.context_menus = { SidebarItemType.DECK: ((tr(TR.ACTIONS_RENAME), self.rename_deck),), + SidebarItemType.TAG: ((tr(TR.ACTIONS_RENAME), self.rename_tag),), } def onContextMenu(self, point: QPoint) -> None: @@ -103,3 +106,25 @@ class NewSidebarTreeView(SidebarTreeViewBase): return showWarning(e.description) self.browser.maybeRefreshSidebar() self.mw.deckBrowser.refresh() + + def rename_tag(self, item: "aqt.browser.SidebarItem") -> None: + self.browser.editor.saveNow(lambda: self._rename_tag(item)) + + def _rename_tag(self, item: "aqt.browser.SidebarItem") -> None: + old_name = item.name + escaped_name = re.sub(r"[*_\\]", r"\\\g<0>", old_name) + escaped_name = '"{}"'.format(escaped_name.replace('"', '\\"')) + nids = self.col.find_notes("tag:" + escaped_name) + if len(nids) == 0: + showInfo(tr(TR.BROWSING_TAG_RENAME_WARNING_EMPTY)) + return + new_name = getOnlyText(tr(TR.ACTIONS_NEW_NAME), default=old_name) + if new_name == old_name or not new_name: + return + self.mw.checkpoint(tr(TR.ACTIONS_RENAME_TAG)) + self.browser.model.beginReset() + self.col.tags.bulk_update(list(nids), old_name, new_name, False) + self.browser.model.endReset() + self.browser.clearUnusedTags() + self.mw.requireReset(reason=ResetReason.BrowserAddTags, context=self) + self.browser.maybeRefreshSidebar()