Enable strict_optional for aqt/editor.py (#3500)

* Enable strict_optional for aqt/editor.py

* Fix mypy errors in EditorWebView class

* Fix mypy errors in Editor class

* DRY

* Match same short circuiting behavior

* Convention
This commit is contained in:
Ben Nguyen 2024-10-15 08:08:24 -07:00 committed by GitHub
parent 694a5089a9
commit a537d349b8
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 101 additions and 51 deletions

View file

@ -38,6 +38,8 @@ strict_optional = True
strict_optional = True strict_optional = True
[mypy-aqt.operations.*] [mypy-aqt.operations.*]
strict_optional = True strict_optional = True
[mypy-aqt.editor]
strict_optional = True
[mypy-anki.scheduler.base] [mypy-anki.scheduler.base]
strict_optional = True strict_optional = True
[mypy-anki._backend.rsbridge] [mypy-anki._backend.rsbridge]

View file

@ -34,7 +34,7 @@ from anki.collection import Config, SearchNode
from anki.consts import MODEL_CLOZE from anki.consts import MODEL_CLOZE
from anki.hooks import runFilter from anki.hooks import runFilter
from anki.httpclient import HttpClient from anki.httpclient import HttpClient
from anki.models import NotetypeId, StockNotetype from anki.models import NotetypeDict, NotetypeId, StockNotetype
from anki.notes import Note, NoteFieldsCheckResult, NoteId from anki.notes import Note, NoteFieldsCheckResult, NoteId
from anki.utils import checksum, is_lin, is_mac, is_win, namedtmp from anki.utils import checksum, is_lin, is_mac, is_win, namedtmp
from aqt import AnkiQt, colors, gui_hooks from aqt import AnkiQt, colors, gui_hooks
@ -242,12 +242,9 @@ require("anki/ui").loaded.then(() => require("anki/NoteEditor").instances[0].too
rightside: bool = True, rightside: bool = True,
) -> str: ) -> str:
"""Assign func to bridge cmd, register shortcut, return button""" """Assign func to bridge cmd, register shortcut, return button"""
if func:
def wrapped_func(editor: Editor) -> None: def wrapped_func(editor: Editor) -> None:
self.call_after_note_saved( self.call_after_note_saved(functools.partial(func, editor), keepFocus=True)
functools.partial(func, editor), keepFocus=True
)
self._links[cmd] = wrapped_func self._links[cmd] = wrapped_func
@ -363,7 +360,7 @@ require("anki/ui").loaded.then(() => require("anki/NoteEditor").instances[0].too
def _onFields(self) -> None: def _onFields(self) -> None:
from aqt.fields import FieldDialog from aqt.fields import FieldDialog
FieldDialog(self.mw, self.note.note_type(), parent=self.parentWindow) FieldDialog(self.mw, self.note_type(), parent=self.parentWindow)
def onCardLayout(self) -> None: def onCardLayout(self) -> None:
self.call_after_note_saved(self._onCardLayout) self.call_after_note_saved(self._onCardLayout)
@ -375,6 +372,8 @@ require("anki/ui").loaded.then(() => require("anki/NoteEditor").instances[0].too
ord = self.card.ord ord = self.card.ord
else: else:
ord = 0 ord = 0
assert self.note is not None
CardLayout( CardLayout(
self.mw, self.mw,
self.note, self.note,
@ -435,7 +434,7 @@ require("anki/ui").loaded.then(() => require("anki/NoteEditor").instances[0].too
gui_hooks.editor_did_focus_field(self.note, self.currentField) gui_hooks.editor_did_focus_field(self.note, self.currentField)
elif cmd.startswith("toggleStickyAll"): elif cmd.startswith("toggleStickyAll"):
model = self.note.note_type() model = self.note_type()
flds = model["flds"] flds = model["flds"]
any_sticky = any([fld["sticky"] for fld in flds]) any_sticky = any([fld["sticky"] for fld in flds])
@ -456,7 +455,7 @@ require("anki/ui").loaded.then(() => require("anki/NoteEditor").instances[0].too
(type, num) = cmd.split(":", 1) (type, num) = cmd.split(":", 1)
ord = int(num) ord = int(num)
model = self.note.note_type() model = self.note_type()
fld = model["flds"][ord] fld = model["flds"][ord]
new_state = not fld["sticky"] new_state = not fld["sticky"]
fld["sticky"] = new_state fld["sticky"] = new_state
@ -469,10 +468,12 @@ require("anki/ui").loaded.then(() => require("anki/NoteEditor").instances[0].too
elif cmd.startswith("lastTextColor"): elif cmd.startswith("lastTextColor"):
(_, textColor) = cmd.split(":", 1) (_, textColor) = cmd.split(":", 1)
assert self.mw.pm.profile is not None
self.mw.pm.profile["lastTextColor"] = textColor self.mw.pm.profile["lastTextColor"] = textColor
elif cmd.startswith("lastHighlightColor"): elif cmd.startswith("lastHighlightColor"):
(_, highlightColor) = cmd.split(":", 1) (_, highlightColor) = cmd.split(":", 1)
assert self.mw.pm.profile is not None
self.mw.pm.profile["lastHighlightColor"] = highlightColor self.mw.pm.profile["lastHighlightColor"] = highlightColor
elif cmd.startswith("saveTags"): elif cmd.startswith("saveTags"):
@ -545,11 +546,12 @@ require("anki/ui").loaded.then(() => require("anki/NoteEditor").instances[0].too
for fld, val in self.note.items() for fld, val in self.note.items()
] ]
flds = self.note.note_type()["flds"] note_type = self.note_type()
flds = note_type["flds"]
collapsed = [fld["collapsed"] for fld in flds] collapsed = [fld["collapsed"] for fld in flds]
plain_texts = [fld.get("plainText", False) for fld in flds] plain_texts = [fld.get("plainText", False) for fld in flds]
descriptions = [fld.get("description", "") for fld in flds] descriptions = [fld.get("description", "") for fld in flds]
notetype_meta = {"id": self.note.mid, "modTime": self.note.note_type()["mod"]} notetype_meta = {"id": self.note.mid, "modTime": note_type["mod"]}
self.widget.show() self.widget.show()
@ -566,6 +568,7 @@ require("anki/ui").loaded.then(() => require("anki/NoteEditor").instances[0].too
self.web.setFocus() self.web.setFocus()
gui_hooks.editor_did_load_note(self) gui_hooks.editor_did_load_note(self)
assert self.mw.pm.profile is not None
text_color = self.mw.pm.profile.get("lastTextColor", "#0000ff") text_color = self.mw.pm.profile.get("lastTextColor", "#0000ff")
highlight_color = self.mw.pm.profile.get("lastHighlightColor", "#0000ff") highlight_color = self.mw.pm.profile.get("lastHighlightColor", "#0000ff")
@ -590,7 +593,7 @@ require("anki/ui").loaded.then(() => require("anki/NoteEditor").instances[0].too
""" """
if self.addMode: if self.addMode:
sticky = [field["sticky"] for field in self.note.note_type()["flds"]] sticky = [field["sticky"] for field in self.note_type()["flds"]]
js += " setSticky(%s);" % json.dumps(sticky) js += " setSticky(%s);" % json.dumps(sticky)
if ( if (
@ -607,6 +610,9 @@ require("anki/ui").loaded.then(() => require("anki/NoteEditor").instances[0].too
def _save_current_note(self) -> None: def _save_current_note(self) -> None:
"Call after note is updated with data from webview." "Call after note is updated with data from webview."
if not self.note:
return
update_note(parent=self.widget, note=self.note).run_in_background( update_note(parent=self.widget, note=self.note).run_in_background(
initiator=self initiator=self
) )
@ -614,7 +620,7 @@ require("anki/ui").loaded.then(() => require("anki/NoteEditor").instances[0].too
def fonts(self) -> list[tuple[str, int, bool]]: def fonts(self) -> list[tuple[str, int, bool]]:
return [ return [
(gui_hooks.editor_will_use_font_for_field(f["font"]), f["size"], f["rtl"]) (gui_hooks.editor_will_use_font_for_field(f["font"]), f["size"], f["rtl"])
for f in self.note.note_type()["flds"] for f in self.note_type()["flds"]
] ]
def call_after_note_saved( def call_after_note_saved(
@ -648,6 +654,7 @@ require("anki/ui").loaded.then(() => require("anki/NoteEditor").instances[0].too
checkValid = _check_and_update_duplicate_display_async checkValid = _check_and_update_duplicate_display_async
def _update_duplicate_display(self, result: NoteFieldsCheckResult.V) -> None: def _update_duplicate_display(self, result: NoteFieldsCheckResult.V) -> None:
assert self.note is not None
cols = [""] * len(self.note.fields) cols = [""] * len(self.note.fields)
cloze_hint = "" cloze_hint = ""
if result == NoteFieldsCheckResult.DUPLICATE: if result == NoteFieldsCheckResult.DUPLICATE:
@ -665,13 +672,14 @@ require("anki/ui").loaded.then(() => require("anki/NoteEditor").instances[0].too
) )
def showDupes(self) -> None: def showDupes(self) -> None:
assert self.note is not None
aqt.dialogs.open( aqt.dialogs.open(
"Browser", "Browser",
self.mw, self.mw,
search=( search=(
SearchNode( SearchNode(
dupe=SearchNode.Dupe( dupe=SearchNode.Dupe(
notetype_id=self.note.note_type()["id"], notetype_id=self.note_type()["id"],
first_field=self.note.fields[0], first_field=self.note.fields[0],
) )
), ),
@ -681,7 +689,7 @@ require("anki/ui").loaded.then(() => require("anki/NoteEditor").instances[0].too
def fieldsAreBlank(self, previousNote: Note | None = None) -> bool: def fieldsAreBlank(self, previousNote: Note | None = None) -> bool:
if not self.note: if not self.note:
return True return True
m = self.note.note_type() m = self.note_type()
for c, f in enumerate(self.note.fields): for c, f in enumerate(self.note.fields):
f = f.replace("<br>", "").strip() f = f.replace("<br>", "").strip()
notChangedvalues = {"", "<br>"} notChangedvalues = {"", "<br>"}
@ -696,7 +704,7 @@ require("anki/ui").loaded.then(() => require("anki/NoteEditor").instances[0].too
# prevent any remaining evalWithCallback() events from firing after C++ object deleted # prevent any remaining evalWithCallback() events from firing after C++ object deleted
if self.web: if self.web:
self.web.cleanup() self.web.cleanup()
self.web = None self.web = None # type: ignore
# legacy # legacy
@ -729,9 +737,11 @@ require("anki/ui").loaded.then(() => require("anki/NoteEditor").instances[0].too
if self.tags.col != self.mw.col: if self.tags.col != self.mw.col:
self.tags.setCol(self.mw.col) self.tags.setCol(self.mw.col)
if not self.tags.text() or not self.addMode: if not self.tags.text() or not self.addMode:
assert self.note is not None
self.tags.setText(self.note.string_tags().strip()) self.tags.setText(self.note.string_tags().strip())
def on_tag_focus_lost(self) -> None: def on_tag_focus_lost(self) -> None:
assert self.note is not None
self.note.tags = self.mw.col.tags.split(self.tags.text()) self.note.tags = self.mw.col.tags.split(self.tags.text())
gui_hooks.editor_did_update_tags(self.note) gui_hooks.editor_did_update_tags(self.note)
if not self.addMode: if not self.addMode:
@ -826,7 +836,7 @@ require("anki/ui").loaded.then(() => require("anki/NoteEditor").instances[0].too
# Media downloads # Media downloads
###################################################################### ######################################################################
def urlToLink(self, url: str) -> str | None: def urlToLink(self, url: str) -> str:
fname = self.urlToFile(url) fname = self.urlToFile(url)
if not fname: if not fname:
return '<a href="{}">{}</a>'.format( return '<a href="{}">{}</a>'.format(
@ -1037,8 +1047,11 @@ require("anki/ui").loaded.then(() => require("anki/NoteEditor").instances[0].too
###################################################################### ######################################################################
def current_notetype_is_image_occlusion(self) -> bool: def current_notetype_is_image_occlusion(self) -> bool:
return bool(self.note) and ( if not self.note:
self.note.note_type().get("originalStockKind", None) return False
return (
self.note_type().get("originalStockKind", None)
== StockNotetype.OriginalStockKind.ORIGINAL_STOCK_KIND_IMAGE_OCCLUSION == StockNotetype.OriginalStockKind.ORIGINAL_STOCK_KIND_IMAGE_OCCLUSION
) )
@ -1049,6 +1062,7 @@ require("anki/ui").loaded.then(() => require("anki/NoteEditor").instances[0].too
image_path=image_path, notetype_id=0 image_path=image_path, notetype_id=0
) )
else: else:
assert self.note is not None
self.setup_mask_editor_for_existing_note( self.setup_mask_editor_for_existing_note(
note_id=self.note.id, image_path=image_path note_id=self.note.id, image_path=image_path
) )
@ -1075,8 +1089,10 @@ require("anki/ui").loaded.then(() => require("anki/NoteEditor").instances[0].too
def select_image_from_clipboard_and_occlude(self) -> None: def select_image_from_clipboard_and_occlude(self) -> None:
"""Set up the mask editor for the image in the clipboard.""" """Set up the mask editor for the image in the clipboard."""
clipoard = self.mw.app.clipboard() clipboard = self.mw.app.clipboard()
mime = clipoard.mimeData() assert clipboard is not None
mime = clipboard.mimeData()
assert mime is not None
if not mime.hasImage(): if not mime.hasImage():
showWarning(tr.editing_no_image_found_on_clipboard()) showWarning(tr.editing_no_image_found_on_clipboard())
return return
@ -1160,6 +1176,7 @@ require("anki/ui").loaded.then(() => require("anki/NoteEditor").instances[0].too
@deprecated(info=_js_legacy) @deprecated(info=_js_legacy)
def _onHtmlEdit(self, field: int) -> None: def _onHtmlEdit(self, field: int) -> None:
assert self.note is not None
d = QDialog(self.widget, Qt.WindowType.Window) d = QDialog(self.widget, Qt.WindowType.Window)
form = aqt.forms.edithtml.Ui_Dialog() form = aqt.forms.edithtml.Ui_Dialog()
form.setupUi(d) form.setupUi(d)
@ -1223,7 +1240,7 @@ require("anki/ui").loaded.then(() => require("anki/NoteEditor").instances[0].too
@deprecated(info=_js_legacy) @deprecated(info=_js_legacy)
def _onCloze(self) -> None: def _onCloze(self) -> None:
# check that the model is set up for cloze deletion # check that the model is set up for cloze deletion
if self.note.note_type()["type"] != MODEL_CLOZE: if self.note_type()["type"] != MODEL_CLOZE:
if self.addMode: if self.addMode:
tooltip(tr.editing_warning_cloze_deletions_will_not_work()) tooltip(tr.editing_warning_cloze_deletions_will_not_work())
else: else:
@ -1231,7 +1248,8 @@ require("anki/ui").loaded.then(() => require("anki/NoteEditor").instances[0].too
return return
# find the highest existing cloze # find the highest existing cloze
highest = 0 highest = 0
for name, val in list(self.note.items()): assert self.note is not None
for _, val in list(self.note.items()):
m = re.findall(r"\{\{c(\d+)::", val) m = re.findall(r"\{\{c(\d+)::", val)
if m: if m:
highest = max(highest, sorted(int(x) for x in m)[-1]) highest = max(highest, sorted(int(x) for x in m)[-1])
@ -1243,6 +1261,7 @@ require("anki/ui").loaded.then(() => require("anki/NoteEditor").instances[0].too
self.web.eval("wrap('{{c%d::', '}}');" % highest) self.web.eval("wrap('{{c%d::', '}}');" % highest)
def setupForegroundButton(self) -> None: def setupForegroundButton(self) -> None:
assert self.mw.pm.profile is not None
self.fcolour = self.mw.pm.profile.get("lastColour", "#00f") self.fcolour = self.mw.pm.profile.get("lastColour", "#00f")
# use last colour # use last colour
@ -1276,6 +1295,7 @@ require("anki/ui").loaded.then(() => require("anki/NoteEditor").instances[0].too
@deprecated(info=_js_legacy) @deprecated(info=_js_legacy)
def onColourChanged(self) -> None: def onColourChanged(self) -> None:
self._updateForegroundButton() self._updateForegroundButton()
assert self.mw.pm.profile is not None
self.mw.pm.profile["lastColour"] = self.fcolour self.mw.pm.profile["lastColour"] = self.fcolour
@deprecated(info=_js_legacy) @deprecated(info=_js_legacy)
@ -1300,6 +1320,7 @@ require("anki/ui").loaded.then(() => require("anki/NoteEditor").instances[0].too
(tr.editing_edit_html(), self.onHtmlEdit, "Ctrl+Shift+X"), (tr.editing_edit_html(), self.onHtmlEdit, "Ctrl+Shift+X"),
): ):
a = m.addAction(text) a = m.addAction(text)
assert a is not None
qconnect(a.triggered, handler) qconnect(a.triggered, handler)
a.setShortcut(QKeySequence(shortcut)) a.setShortcut(QKeySequence(shortcut))
@ -1387,6 +1408,12 @@ require("anki/ui").loaded.then(() => require("anki/NoteEditor").instances[0].too
addImageForOcclusionFromClipboard=Editor.select_image_from_clipboard_and_occlude, addImageForOcclusionFromClipboard=Editor.select_image_from_clipboard_and_occlude,
) )
def note_type(self) -> NotetypeDict:
assert self.note is not None
note_type = self.note.note_type()
assert note_type is not None
return note_type
# Pasting, drag & drop, and keyboard layouts # Pasting, drag & drop, and keyboard layouts
###################################################################### ######################################################################
@ -1403,6 +1430,7 @@ class EditorWebView(AnkiWebView):
self._internal_field_text_for_paste: str | None = None self._internal_field_text_for_paste: str | None = None
self._last_known_clipboard_mime: QMimeData | None = None self._last_known_clipboard_mime: QMimeData | None = None
clip = self.editor.mw.app.clipboard() clip = self.editor.mw.app.clipboard()
assert clip is not None
clip.dataChanged.connect(self._on_clipboard_change) clip.dataChanged.connect(self._on_clipboard_change)
gui_hooks.editor_web_view_did_init(self) gui_hooks.editor_web_view_did_init(self)
@ -1411,7 +1439,7 @@ class EditorWebView(AnkiWebView):
self._internal_field_text_for_paste = None self._internal_field_text_for_paste = None
def _on_clipboard_change(self) -> None: def _on_clipboard_change(self) -> None:
self._last_known_clipboard_mime = self.editor.mw.app.clipboard().mimeData() self._last_known_clipboard_mime = self._clipboard().mimeData()
if self._store_field_content_on_next_clipboard_change: if self._store_field_content_on_next_clipboard_change:
# if the flag was set, save the field data # if the flag was set, save the field data
self._internal_field_text_for_paste = self._get_clipboard_html_for_field() self._internal_field_text_for_paste = self._get_clipboard_html_for_field()
@ -1423,8 +1451,9 @@ class EditorWebView(AnkiWebView):
self._internal_field_text_for_paste = None self._internal_field_text_for_paste = None
def _get_clipboard_html_for_field(self): def _get_clipboard_html_for_field(self):
clip = self.editor.mw.app.clipboard() clip = self._clipboard()
mime = clip.mimeData() mime = clip.mimeData()
assert mime is not None
if not mime.hasHtml(): if not mime.hasHtml():
return return
return mime.html() return mime.html()
@ -1440,6 +1469,7 @@ class EditorWebView(AnkiWebView):
def _opened_context_menu_on_image(self) -> bool: def _opened_context_menu_on_image(self) -> bool:
context_menu_request = self.lastContextMenuRequest() context_menu_request = self.lastContextMenuRequest()
assert context_menu_request is not None
return ( return (
context_menu_request.mediaType() context_menu_request.mediaType()
== context_menu_request.MediaType.MediaTypeImage == context_menu_request.MediaType.MediaTypeImage
@ -1455,7 +1485,8 @@ class EditorWebView(AnkiWebView):
def _onPaste(self, mode: QClipboard.Mode) -> None: def _onPaste(self, mode: QClipboard.Mode) -> None:
# Since _on_clipboard_change doesn't always trigger properly on macOS, we do a double check if any changes were made before pasting # Since _on_clipboard_change doesn't always trigger properly on macOS, we do a double check if any changes were made before pasting
if self._last_known_clipboard_mime != self.editor.mw.app.clipboard().mimeData(): clipboard = self._clipboard()
if self._last_known_clipboard_mime != clipboard.mimeData():
self._on_clipboard_change() self._on_clipboard_change()
extended = self._wantsExtendedPaste() extended = self._wantsExtendedPaste()
if html := self._internal_field_text_for_paste: if html := self._internal_field_text_for_paste:
@ -1463,7 +1494,8 @@ class EditorWebView(AnkiWebView):
self.editor.doPaste(html, True, extended) self.editor.doPaste(html, True, extended)
else: else:
print("use clipboard") print("use clipboard")
mime = self.editor.mw.app.clipboard().mimeData(mode=mode) mime = clipboard.mimeData(mode=mode)
assert mime is not None
html, internal = self._processMime(mime, extended) html, internal = self._processMime(mime, extended)
if html: if html:
self.editor.doPaste(html, internal, extended) self.editor.doPaste(html, internal, extended)
@ -1474,12 +1506,15 @@ class EditorWebView(AnkiWebView):
def onMiddleClickPaste(self) -> None: def onMiddleClickPaste(self) -> None:
self._onPaste(QClipboard.Mode.Selection) self._onPaste(QClipboard.Mode.Selection)
def dragEnterEvent(self, evt: QDragEnterEvent) -> None: def dragEnterEvent(self, evt: QDragEnterEvent | None) -> None:
assert evt is not None
evt.accept() evt.accept()
def dropEvent(self, evt: QDropEvent) -> None: def dropEvent(self, evt: QDropEvent | None) -> None:
assert evt is not None
extended = self._wantsExtendedPaste() extended = self._wantsExtendedPaste()
mime = evt.mimeData() mime = evt.mimeData()
assert mime is not None
cursor_pos = self.mapFromGlobal(QCursor.pos()) cursor_pos = self.mapFromGlobal(QCursor.pos())
if evt.source() and mime.hasHtml(): if evt.source() and mime.hasHtml():
@ -1585,12 +1620,13 @@ class EditorWebView(AnkiWebView):
return fname return fname
def contextMenuEvent(self, evt: QContextMenuEvent) -> None: def contextMenuEvent(self, evt: QContextMenuEvent | None) -> None:
m = QMenu(self) m = QMenu(self)
if self.hasSelection(): if self.hasSelection():
self._add_cut_action(m) self._add_cut_action(m)
self._add_copy_action(m) self._add_copy_action(m)
a = m.addAction(tr.editing_paste()) a = m.addAction(tr.editing_paste())
assert a is not None
qconnect(a.triggered, self.onPaste) qconnect(a.triggered, self.onPaste)
if self._opened_context_menu_on_image(): if self._opened_context_menu_on_image():
self._add_image_menu(m) self._add_image_menu(m)
@ -1599,26 +1635,38 @@ class EditorWebView(AnkiWebView):
def _add_cut_action(self, menu: QMenu) -> None: def _add_cut_action(self, menu: QMenu) -> None:
a = menu.addAction(tr.editing_cut()) a = menu.addAction(tr.editing_cut())
assert a is not None
qconnect(a.triggered, self.onCut) qconnect(a.triggered, self.onCut)
def _add_copy_action(self, menu: QMenu) -> None: def _add_copy_action(self, menu: QMenu) -> None:
a = menu.addAction(tr.actions_copy()) a = menu.addAction(tr.actions_copy())
assert a is not None
qconnect(a.triggered, self.onCopy) qconnect(a.triggered, self.onCopy)
def _add_image_menu(self, menu: QMenu) -> None: def _add_image_menu(self, menu: QMenu) -> None:
a = menu.addAction(tr.editing_copy_image()) a = menu.addAction(tr.editing_copy_image())
assert a is not None
qconnect(a.triggered, self.on_copy_image) qconnect(a.triggered, self.on_copy_image)
url = self.lastContextMenuRequest().mediaUrl() context_menu_request = self.lastContextMenuRequest()
assert context_menu_request is not None
url = context_menu_request.mediaUrl()
file_name = url.fileName() file_name = url.fileName()
path = os.path.join(self.editor.mw.col.media.dir(), file_name) path = os.path.join(self.editor.mw.col.media.dir(), file_name)
a = menu.addAction(tr.editing_open_image()) a = menu.addAction(tr.editing_open_image())
assert a is not None
qconnect(a.triggered, lambda: openFolder(path)) qconnect(a.triggered, lambda: openFolder(path))
if is_win or is_mac: if is_win or is_mac:
a = menu.addAction(tr.editing_show_in_folder()) a = menu.addAction(tr.editing_show_in_folder())
assert a is not None
qconnect(a.triggered, lambda: show_in_folder(path)) qconnect(a.triggered, lambda: show_in_folder(path))
def _clipboard(self) -> QClipboard:
clipboard = self.editor.mw.app.clipboard()
assert clipboard is not None
return clipboard
# QFont returns "Kozuka Gothic Pro L" but WebEngine expects "Kozuka Gothic Pro Light" # QFont returns "Kozuka Gothic Pro L" but WebEngine expects "Kozuka Gothic Pro Light"
# - there may be other cases like a trailing 'Bold' that need fixing, but will # - there may be other cases like a trailing 'Bold' that need fixing, but will
@ -1648,7 +1696,7 @@ gui_hooks.editor_will_munge_html.append(reverse_url_quoting)
def set_cloze_button(editor: Editor) -> None: def set_cloze_button(editor: Editor) -> None:
action = "show" if editor.note.note_type()["type"] == MODEL_CLOZE else "hide" action = "show" if editor.note_type()["type"] == MODEL_CLOZE else "hide"
editor.web.eval( editor.web.eval(
'require("anki/ui").loaded.then(() =>' 'require("anki/ui").loaded.then(() =>'
f'require("anki/NoteEditor").instances[0].toolbar.toolbar.{action}("cloze")' f'require("anki/NoteEditor").instances[0].toolbar.toolbar.{action}("cloze")'