How to enable "undo" action in addCard window

I programmatically paste content into a note in the Editor (more specifically in the AddCard window).

How can I enable “undo paste” in the AddCard window please?

I use a checkpoint to enable undo’s when calling the addon from the browser window:

def pasteContentIntoBrowser(browser):

    mw = browser.mw
    mw.checkpoint("paste content into notes")  # enables 'undo action'
    mw.progress.start()
    browser.model.beginReset()

    for nid in browser.selectedNotes():
         ' paste something into note
   
    browser.model.endReset()
    mw.requireReset()
    mw.progress.finish()
    mw.reset()

What would be the equivalent for a call from the Editor / AddCard window?

Below my sample code that does not currently work:

def pasteContentIntoEditor(editor):

    mw = editor.mw
    mw.checkpoint("paste content into note")
    mw.progress.start()
   
    ' paste something into note

    mw.requireReset()
    mw.progress.finish()
    editor.parentWindow.onReset()

Ctrl+z in a field will undo normal edits - no special checkpointing is required.

Hi Damien

Thanks for the reply.

The addon modifies the content of a field:

note[fld] = newvalue

Ctrl+z doesn’t work unfortunately to undo the addon’s action.

Is there another way to change the field’s content that is ”undoable”?

I think there may be an easier way to do that, but you can try the following:

from dataclasses import dataclass
from typing import Optional

import anki
import aqt

FIELDS_HISTORY = "fields history"


@dataclass
class FieldsHistory:
    note: Optional[anki.notes.Note] = None
    fields: Optional[dict] = None

    def clear(self):
        self.note = None
        self.fields = None


def modify_content(editor: aqt.editor.Editor, fields_history: FieldsHistory) -> None:
    fields_history.note = editor.note
    fields_history.fields = {k: v for k, v in editor.note.items()}
    editor.note.fields[editor.currentField] = "New Value"
    if not editor.addMode:
        editor.note.flush()
    editor.loadNote(focusTo=editor.currentField)


def restore_fields(editor: aqt.editor.Editor, fields_history: FieldsHistory) -> None:
    for k, v in fields_history.fields.items():
        editor.note[k] = v
    if not editor.addMode:
        editor.note.flush()
    editor.loadNote(focusTo=editor.currentField)
    fields_history.clear()


def on_editor_did_init(editor: aqt.editor.Editor) -> None:
    fields_history = FieldsHistory()
    setattr(editor, FIELDS_HISTORY, fields_history)


def on_editor_will_show_context_menu(
    editor_webview: aqt.editor.EditorWebView, menu: aqt.qt.QMenu
) -> None:
    editor = editor_webview.editor
    fields_history: FieldsHistory = getattr(editor, FIELDS_HISTORY)

    if editor.currentField is not None:
        menu.addAction("Modify content", lambda: modify_content(editor, fields_history))

    if fields_history.fields and (editor.note is fields_history.note):
        menu.addAction(
            "Undo add-on's action", lambda: restore_fields(editor, fields_history)
        )
    else:
        fields_history.clear()


aqt.gui_hooks.editor_did_init.append(on_editor_did_init)
aqt.gui_hooks.editor_will_show_context_menu.append(on_editor_will_show_context_menu)

This should also work on “Edit Current” and “Browser”.

3 Likes

If you’re just inserting HTML, you may want to consider inserting it like a paste does, as that should be undoable. A checkpoint won’t work in the add screen, as the note is not written until the user adds it.

1 Like

Thanks for the replies.

Damien’s response enables me to use the built-in “undo” function (ie Ctrl+Z).

So I implemented the following:

import json

def getOrd(note, fname):
    return note._fieldOrd(fname)


def sortByFieldOrd(note, contentsByFieldName):
    return sorted([(getOrd(note, fname), fcontent) for fname, fcontent in contentsByFieldName.items()])


def pasteFieldsIntoEditor(editor):

    note = editor.note
    currentFld = editor.currentField

    fieldsToUpdate = {
        "some field": "new HTML value 1",
        "another field": "new HTML value 2"
    }

    contentsByOrd = sortByFieldOrd(note, fieldsToUpdate)

    for ord, fcontent in contentsByOrd:
        js = '''
                focusField(%d);
                document.execCommand("selectall");
                setFormat("inserthtml", %s);
             ''' % (ord, json.dumps(fcontent))
        editor.web.eval(js)

    # restore focus
    if currentFld:
        editor.web.eval("focusField(%d)" % currentFld)