Editor JS snippets [support thread]

New add-on, a derivation/improvement of Custom editor keymap - AnkiWeb, main differences:

  • In addition to being available from keyboard shortcuts the JS snippets and symbols are available from the context menu in the editor (in main context menu or as submenus, configurable).
  • Also available as separate shortcut-invokeable menus ("Ctrl+J" and "Ctrl+S" per default, configurable) allowing selection from the menus from keyboard only.
  • JS snippets have a Pre and Post JS string to allow for passing variables etc. to JS script stored in file. File is interpreted as filename and contents are loaded.
  • File names are interpreted as being located in the add-on folder/user_files (to avoid them being deleted on add-on update).
  • To store JS snippet in file config only just write it in the pre or post string.
  • Shortcuts in config specified in Qt format (see https://doc.qt.io/qt-5/qkeysequence.html)
  • It is implemented "python-side" rather than "javascript-side" although what is inserted is JavaScript
  • Just as in Custom editor keymap symbols (strings really) can be inserted as plain text or HTML.
  • Feel free to share your JS snippets in https://forums.ankiweb.net/t/useful-javascript-snippets-for-the-editor/14536

1


Outstanding

  • Figure out how to get X/Y position of caret to allow keyboard popping of menues at caret rather than mouse cursor (which may be located anywhere on the screen).

Any chance you could post the source code somewhere?

EDIT: never mind, that can obviously be found in the addon folder, my bad :slight_smile:

It might however still be nice to host to github or another git hosting service that way people could contribute, I’m not certain whether my changes are still helpful since I changed quite a lot and had no need for symbols but with the changes I made to your code snippets are now reloaded live, i.e. if you change a file in your user_files those changes are reflected immediately in the editor. I wasn’t able to get this to work for the config file itself, although I’m not certain it’s even possible. What is missing in my code and I do believe in yours is the ability to undo changes. Have you got any idea how to do that?

In any case here are my changes, take the code from them as you like or if you open a git repository I could see if I can open a pull request:
init.py

import os
import sys
from collections import namedtuple
from functools import partial

from anki.hooks import addHook
from aqt import gui_hooks, mw, utils
from aqt.qt import QCursor, QKeySequence, QMenu

JS_SECTION = "JS snippets"
JS_MENU_SCUT = None

# Build the JS commands from pre/post and file contents
def build_js(snippet) -> str:
    file = ""
    pre = snippet.pre
    post = snippet.post
    fname = snippet.fname
    file = ""
    if fname:
        for f in fname.split(";"):
            path = os.path.join(os.path.dirname(__file__), f"user_files/{f}")
            with open(path) as fh:
                file += fh.read().strip()
    # return pre + file + post
    return ";".join([pre, file, post])


# Add shortcuts to editor
def setup_cuts(snippets, scs, edit):
    def fn(s):
        edit.web.eval(build_js(s))
        return

    scs += [(s.cut, partial(fn, s), True) for s in snippets]

    menu = QMenu("Custom", edit.web)
    map(lambda s: menu.addAction(s.name, partial(fn, s), s.cut), snippets)
    menu.popup(QCursor.pos())

    # DEBUG:
    # for i in scs:
    #     if isinstance(i[0], QKeySequence):
    #         func = inspect.getsourcelines(i[1])[0]
    #         print(f"INFO:{func}")
    #     print(i)
    #     print("\n")


# Only set up if not already loaded
if not 2065559429 in sys.modules:
    config = mw.addonManager.getConfig(__name__)

    # Setup base shortcuts
    JS_MENU_SCUT = config["Snippet menu shortcut"]

    config_snippets = config["Snippets"]
    Snippet = namedtuple("Snippet", ["name", "cut", "fname", "pre", "post"])

    def get_snippets():
        return [
            Snippet(sc["Name"], sc["Shortcut"], sc["File"], sc["Pre"], sc["Post"])
            for sc in mw.addonManager.getConfig(__name__)["Snippets"]
        ]

    # addHook(
    #     "EditorWebView.contextMenuEvent", mouse_context
    # )  # Legacy hook but it does fire
    # gui_hooks.editor_will_show_context_menu.append(on_pop_context) # New style hooks doesn't fire until Image Occlusion Enhanced is fixed
    gui_hooks.editor_did_init_shortcuts.append(partial(setup_cuts, get_snippets()))

Config File

{
  "name": "js_snippets",
  "config": {
    "Snippet context submenu": "false",
    "Snippet menu shortcut": "Ctrl+J",
    "Snippets": [
      {
        "File": "test.js",
        "Name": "Test",
        "Post": "",
        "Pre": "",
        "Shortcut": "Alt+T"
      },
      {
        "File": "util.js;bracket.js",
        "Name": "Bracket",
        "Post": "",
        "Pre": "",
        "Shortcut": "Alt+B"
      },
      {
        "File": "util.js;jaxc.js",
        "Name": "Jaxc",
        "Post": "",
        "Pre": "",
        "Shortcut": "Alt+J"
      },
      {
        "File": "util.js;ht.js",
        "Name": "Highlight Text",
        "Post": "",
        "Pre": "",
        "Shortcut": "Alt+H,T"
      },
      {
        "File": "util.js;hm.js",
        "Name": "Highlight Math",
        "Post": "",
        "Pre": "",
        "Shortcut": "Alt+H,M"
      },
      {
        "File": "util.js;math.js",
        "Name": "Math",
        "Post": "",
        "Pre": "",
        "Shortcut": "Alt+M"
      },
      {
        "File": "color.js",
        "Name": "Color Green",
        "Post": "setColor('green')",
        "Pre": "",
        "Shortcut": "Ctrl+G,G"
      },
      {
        "File": "color.js",
        "Name": "Color Black",
        "Post": "setColor('black')",
        "Pre": "",
        "Shortcut": "Ctrl+G,B"
      }
    ],
    "Symbol context submenu": "true",
    "Symbol menu shortcut": "Ctrl+S",
    "Symbols": [
      { "HTML": "false", "Shortcut": "Alt+Left", "Symbol": "\u2190" },
      { "HTML": "false", "Shortcut": "Alt+Right", "Symbol": "\u2192" },
      { "HTML": "false", "Shortcut": "Alt+Up", "Symbol": "\u2191" },
      { "HTML": "false", "Shortcut": "Alt+Down", "Symbol": "\u2193" }
    ]
  }
}

I will look into putting it up on GitHub.

Let me know if you need any help in doing that, it’s not that hard but can be a bit tricky if you haven’t done so before.

Source uploaded to GitHub: GitHub - TRIAEIOU/Editor-Scripts-Symbols: Anki addon to run custom scripts and insert symbols/strings in the Anki editor using keyboard shortcuts or popup menus.

Please could you update the addon “Uppercase Lowercase” which exist for anki 2.0 for anki 2.1 ?

Sorry, that’s not my addon.

Hello, just to relate that your add-on are presenting some bugs in anki 2.1.54 qt5. Working fine at version qt6.

Ok, thanks, could you describe what the bugs seem to be?

Error
An error occurred. Please start Anki while holding down the shift key, which will temporarily disable the add-ons you have installed.
If the issue only occurs when add-ons are enabled, please use the Tools > Add-ons menu item to disable some add-ons and restart Anki, repeating until you discover the add-on that is causing the problem.
When you’ve discovered the add-on that is causing the problem, please report the issue to the add-on author.
Debug info:
Anki 2.1.54 (b6a7760c) Python 3.9.7 Qt 5.15.2 PyQt 5.15.5
Platform: Windows 10
Flags: frz=True ao=True sv=3
Add-ons, last update check: 2022-07-12 18:51:24

Caught exception:
Traceback (most recent call last):
File “aqt.editor”, line 1359, in contextMenuEvent
File “aqt.hooks_gen”, line 2372, in call
File “C:\Users\gusta\AppData\Roaming\Anki2\addons21\2065559429_init_.py”, line 313, in mouse_context
menu = build_menu(config.get(ENTRIES, ), menu, wedit.editor)
File “C:\Users\gusta\AppData\Roaming\Anki2\addons21\2065559429_init_.py”, line 292, in build_menu
menu.addMenu(build_menu(node[ITEMS], QMenu(editor.web), editor)).setText(node[MENU])
File “C:\Users\gusta\AppData\Roaming\Anki2\addons21\2065559429_init_.py”, line 292, in build_menu
menu.addMenu(build_menu(node[ITEMS], QMenu(editor.web), editor)).setText(node[MENU])
File “C:\Users\gusta\AppData\Roaming\Anki2\addons21\2065559429_init_.py”, line 294, in build_menu
menu.addAction(label, node.get(SHORTCUT, 0), lambda editor=editor, cmd=build_cmd(node): cmd(editor))
TypeError: arguments did not match any overloaded call:
addAction(self, QAction): argument 1 has unexpected type ‘str’
addAction(self, str): too many arguments
addAction(self, QIcon, str): argument 1 has unexpected type ‘str’
addAction(self, str, PYQT_SLOT, shortcut: Union[QKeySequence, QKeySequence.StandardKey, str, int] = 0): argument 3 has unexpected type ‘function’
addAction(self, QIcon, str, PYQT_SLOT, shortcut: Union[QKeySequence, QKeySequence.StandardKey, str, int] = 0): argument 1 has unexpected type ‘str’

The only add-on enabled was yours, of course :slight_smile:

The add-on seems to working fine at the keyboard shortcut, but doesn’t show the context menu with right click

Ok, I don’t have a Qt5 installation to test on but I am hoping the version I just pushed resolve the issue. Let me know if it doesn’t.