~brhiggins/anki-add-nid

0c89daa8a97c8ee36af0fc0d00721078151665c7 — Benjamin Higgins 1 year, 5 months ago master
Add plugin
8 files changed, 143 insertions(+), 0 deletions(-)

A .flake8
A .gitignore
A LICENSE
A README.md
A __init__.py
A img/dialog.png
A img/menu-item.png
A img/result.png
A  => .flake8 +6 -0
@@ 1,6 @@
# They dropped support for XDG config files in 2021, so this file has to be symlinked to
# project/.flake8 https://github.com/pycqa/flake8/pull/1404

[flake8]
max-line-length = 88
extend-ignore = E203

A  => .gitignore +3 -0
@@ 1,3 @@
__pycache__/
meta.json
.mypy_cache/

A  => LICENSE +24 -0
@@ 1,24 @@
This is free and unencumbered software released into the public domain.

Anyone is free to copy, modify, publish, use, compile, sell, or
distribute this software, either in source code form or as a compiled
binary, for any purpose, commercial or non-commercial, and by any
means.

In jurisdictions that recognize copyright laws, the author or authors
of this software dedicate any and all copyright interest in the
software to the public domain. We make this dedication for the benefit
of the public at large and to the detriment of our heirs and
successors. We intend this dedication to be an overt act of
relinquishment in perpetuity of all present and future rights to this
software under copyright law.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
OTHER DEALINGS IN THE SOFTWARE.

For more information, please refer to <https://unlicense.org>

A  => README.md +18 -0
@@ 1,18 @@
# Anki add note id

Simple plugin to add note id(s) to a chosen field on all selected cards.

For some reason all variants of this plugin that I have found online try to have some
clever logic that automatically fills in the note id for an appropriately named field
upon creation or modification, or some such behaviour. This plugin is really simple and
literally just puts the value from the database into the field you choose on the card
when you click the button, and does nothing else.


![](./img/menu-item.png)


![](./img/dialog.png)


![](./img/result.png)

A  => __init__.py +92 -0
@@ 1,92 @@
from typing import Sequence

from anki.notes import NoteId, NotetypeDict
from aqt import Collection, gui_hooks
from aqt.browser import Browser
from aqt.operations import CollectionOp, OpChanges
from aqt.qt import QDialog  # type: ignore
from aqt.qt import QPushButton  # type: ignore
from aqt.qt import QButtonGroup, QLabel, QRadioButton, QVBoxLayout, QWidget
from aqt.utils import showWarning, tooltip


# Mostly copied from here:
# https://github.com/realmayus/anki_forvo_dl/blob/main/src/FieldSelector.py
class FieldSelector(QDialog):
    def __init__(self, parent: QWidget, note_type: NotetypeDict, prompt: str) -> None:
        super().__init__(parent)

        self.layout = QVBoxLayout()
        self.setLayout(self.layout)

        self.setWindowTitle(prompt)
        self.layout.addWidget(QLabel(f"<h1>{prompt}</h1>"))

        self.button_group = QButtonGroup()

        for idx, field in enumerate(note_type["flds"]):
            radio = QRadioButton(field["name"])
            radio.field = field["name"]
            self.button_group.addButton(radio)
            self.layout.addWidget(radio)

        self.button_group.buttonClicked.connect(self.handle_selection_change)
        self.sel_fld = ""  # Default value used if user kills dialog without selection

        self.next_btn = QPushButton("Continue")
        self.next_btn.clicked.connect(self.close)
        self.next_btn.setEnabled(False)

        self.layout.addWidget(self.next_btn)

    def handle_selection_change(self) -> None:
        sel = self.button_group.checkedButton()
        self.sel_fld = sel.field if sel is not None else ""
        self.next_btn.setEnabled(sel is not None)


def add_nids(browser: Browser) -> None:
    nids: Sequence[NoteId] = browser.selected_notes()
    notes = [browser.mw.col.get_note(nid) for nid in nids]

    if len(notes) == 0:
        showWarning("No notes selected.", parent=browser)
        return

    note_type = notes[0].note_type()
    assert note_type  # Make mypy shut up

    field_selector = FieldSelector(browser, note_type, "Select nid field")
    field_selector.exec()
    nid_field = field_selector.sel_fld

    if nid_field == "":
        tooltip("No field selected, cannot add nid(s)", parent=browser)
        return

    for note in notes:
        if nid_field not in note.keys():
            showWarning(
                "Note %d is missing field %s, cannot add nids" % (note.id, nid_field),
                parent=browser,
            )
            return

    def execute(col: Collection) -> OpChanges:
        undo_entry = col.add_custom_undo_entry("Add nid(s)")
        for note in notes:
            note[nid_field] = str(note.id)
        col.update_notes(notes)
        return col.merge_undo_entries(undo_entry)

    CollectionOp(browser, lambda col: execute(col)).success(
        lambda _: tooltip("Added nids to %s card(s)" % len(nids))
    ).run_in_background()


def add_menu_opt(browser: Browser) -> None:
    action = browser.form.menu_Notes.addAction("Add note id(s)")
    action.triggered.connect(lambda: add_nids(browser))


gui_hooks.browser_menus_did_init.append(add_menu_opt)

A  => img/dialog.png +0 -0
A  => img/menu-item.png +0 -0
A  => img/result.png +0 -0