[Feature request]  Context menu for easy access to all "X on click" functions
Hello there! Having used this addon for a few months now, I find that the default 'Copy' context menu action isn't very effective, considering how finicky it is to actually select a kanji in the grid 🤕.

Also, having clicking be assigned only one action (copy, browse, search online) at any given time seems very limiting. For instance, it'd be more effective (IMO) to allow browsing for found kanji while also allowing copying and searching online for missing kanji (since browsing isn't useful here).

With a context menu, all 3 actions can be accessed very conveniently, regardless of what the LMB or MMB do:

![image](https://github.com/user-attachments/assets/746b61ac-65a4-498f-b053-707df83f25ed)

This isn't without precedence. [Kanji Table](https://ankiweb.net/shared/info/1828617820), another addon I used to use, also features a context menu. But their impl is different since they use a QTableWidget and not html (which might be why it seems slower 🤔)

I've included a small (exploratory) diff patch below on how I got it to work. It uses the `mouseenter` and `mouseleave` events to call `bridgeCommand` to inform the python side of the hovered-over character

```diff
diff --git a/__init__.py b/__init__.py
index 82f9f9f..9b4a5c9 100644
--- a/__init__.py
+++ b/__init__.py
@@ -13,11 +13,11 @@ import shlex
 from functools import reduce
 
 from anki.utils import ids2str
-from aqt import mw, dialogs
+from aqt import mw, dialogs, gui_hooks
 from aqt.webview import AnkiWebView
 from aqt.qt import (QAction, QSizePolicy, QDialog, QHBoxLayout,
                     QVBoxLayout, QGroupBox, QLabel, QCheckBox, QSpinBox,
-                    QComboBox, QPushButton, QLineEdit)
+                    QComboBox, QPushButton, QLineEdit, QMenu, QApplication, qconnect)
 
 from . import config_util, data, util, save
 
@@ -32,14 +32,16 @@ class KanjiGrid:
         def kanjitile(char, bgcolor, count = 0, avg_interval = 0):
             tile = ""
             color = "#000"
-
+            # TODO: right now it's gated under config.browseonclick as an exploratory quickfix
+            # perhaps another option like config.contextmenu or config.exporting (internally set) could be added
+            context_menu_events = f" onmouseenter=\"bridgeCommand('h:{char}');\" onmouseleave=\"bridgeCommand('l:{char}');\"" if config.browseonclick else ""
             if config.tooltips:
                 tooltip = "Character: %s" % unicodedata.name(char)
                 if avg_interval:
                     tooltip += " | Avg Interval: " + str("{:.2f}".format(avg_interval)) + " | Score: " + str("{:.2f}".format(util.scoreAdjust(avg_interval / config.interval)))
-                tile += "\t<div class=\"grid-item\" style=\"background:%s;\" title=\"%s\">" % (bgcolor, tooltip)
+                tile += "\t<div class=\"grid-item\" style=\"background:%s;\" title=\"%s\"%s>" % (bgcolor, tooltip, context_menu_events)
             else:
-                tile += "\t<div style=\"background:%s;\">" % (bgcolor)
+                tile += "\t<div style=\"background:%s;\"%s>" % (bgcolor, context_menu_events)
 
             if config.copyonclick:
                 tile += "<a style=\"color:" + color + ";cursor: pointer;\">" + char + "</a>"
@@ -154,13 +156,44 @@ class KanjiGrid:
         browser.form.searchEdit.lineEdit().setText("deck:\"" + deckname + "\" " + fields_string)
         browser.onSearchActivated()
 
+    def open_search_link(self, config, char):
+        link = util.get_search(config, char)
+        self.wv.eval(f"window.open('{link}', '_blank');")
+
+    def link_handler(self, link):
+        if link[:2] == "h:":
+            self.hovered = link[2:]
+        elif link[:2] == "l:":
+            if link[2:] == self.hovered:
+                # clear when outside grid
+                self.hovered = ""
+        else:
+            self.on_browse_cmd(link)
+
+    def add_webview_context_menu_items(self, wv: AnkiWebView, m: QMenu) -> None:
+        # hook is active while kanjigrid is open, and right clicking on the main window (deck list) will also trigger this, so check wv
+        if wv is self.wv and self.hovered != "":
+            char = self.hovered
+            a = m.addAction(f"Copy {char} to clipboard")
+            qconnect(a.triggered, lambda: self.on_copy_cmd(char))
+            a = m.addAction(f"Search deck for {char}")
+            qconnect(a.triggered, lambda: self.on_browse_cmd(char))
+            a = m.addAction(f"Search online for {char}")
+            qconnect(a.triggered, lambda: self.on_search_cmd(char))
+
     def displaygrid(self, config, deckname, units):
         self.generate(config, units)
         self.timepoint("HTML generated")
         self.win = QDialog(mw)
         self.wv = AnkiWebView()
-        fields_list = config.pattern
-        self.wv.set_bridge_command(lambda search_string: self.open_note_browser(mw, deckname, fields_list, search_string), None)
+        self.hovered = ""
+        self.on_browse_cmd = lambda char: self.open_note_browser(mw, deckname, config.pattern, char)
+        self.on_search_cmd = lambda char: self.open_search_link(config, char)
+        self.on_copy_cmd = QApplication.clipboard().setText
+        self.wv.set_bridge_command(self.link_handler, None)
+        # add webview context menu hook and defer cleanup
+        gui_hooks.webview_will_show_context_menu.append(self.add_webview_context_menu_items)
+        qconnect(self.win.finished, lambda: gui_hooks.webview_will_show_context_menu.remove(self.add_webview_context_menu_items))
         vl = QVBoxLayout()
         vl.setContentsMargins(0, 0, 0, 0)
         vl.addWidget(self.wv)

```

Thanks for your time!