From 72c7d64876948c6eaaf9ec21c2397434df949a5d Mon Sep 17 00:00:00 2001 From: Damien Elmes Date: Fri, 15 Apr 2022 17:35:05 +1000 Subject: [PATCH 01/16] Fix compatibility with older macOS versions https://forums.ankiweb.net/t/anki-2-1-50-qt5-wont-open/19091 --- .bazelrc | 4 ++++ pylib/rsbridge/BUILD.bazel | 1 + 2 files changed, 5 insertions(+) diff --git a/.bazelrc b/.bazelrc index 81430cc64..872241350 100644 --- a/.bazelrc +++ b/.bazelrc @@ -10,6 +10,10 @@ build:windows --build_python_zip=false # record version/build hash build --workspace_status_command='bash ./tools/status.sh' +# support macOS 10.13+ +build:macos --action_env="MACOSX_DEPLOYMENT_TARGET=10.13" +build:macos --macos_minimum_os=10.13 + # run clippy when compiling rust in test mode test --aspects=@rules_rust//rust:defs.bzl%rust_clippy_aspect --output_groups=+clippy_checks diff --git a/pylib/rsbridge/BUILD.bazel b/pylib/rsbridge/BUILD.bazel index 6c2b6856b..97b27109a 100644 --- a/pylib/rsbridge/BUILD.bazel +++ b/pylib/rsbridge/BUILD.bazel @@ -8,6 +8,7 @@ cargo_build_script( srcs = ["build.rs"], build_script_env = { "PYO3_PYTHON": "$(location @python)", + "MACOSX_DEPLOYMENT_TARGET": "10.13", }, data = [ "@python", From 0835129a000e030567114e10429166001390a767 Mon Sep 17 00:00:00 2001 From: Luka Warren Date: Tue, 19 Apr 2022 06:39:32 +0100 Subject: [PATCH 02/16] Fix issue #1114 by scrolling the user to the top of the page (#1810) * Scroll stats to top when exporting (#1114) It's obviously a bit of a "hacky" solution, since it's slightly jarring for users to scroll down, click export, then find themselves all the way at the top again, but it's somewhat less confusing than wondering why your PDF is broken :-) * Clarified comment in stats.py (#1114) * Apply scrolling fix to new stats screen, not legacy stats (dae) Also wait for JS callback before saving the PDF, as JS is executed asynchronously. --- CONTRIBUTORS | 1 + qt/aqt/about.py | 1 + qt/aqt/stats.py | 11 +++++++++-- 3 files changed, 11 insertions(+), 2 deletions(-) diff --git a/CONTRIBUTORS b/CONTRIBUTORS index f56b75d18..c54faccb2 100644 --- a/CONTRIBUTORS +++ b/CONTRIBUTORS @@ -99,6 +99,7 @@ Sachin Govind Bruce Harris Patric Cunha Brayan Oliveira +Luka Warren ******************** diff --git a/qt/aqt/about.py b/qt/aqt/about.py index 116408b1c..79c2e758e 100644 --- a/qt/aqt/about.py +++ b/qt/aqt/about.py @@ -225,6 +225,7 @@ def show(mw: aqt.AnkiQt) -> QDialog: "Sergio Quintero", "Nicholas Flint", "Daniel Vieira Memoria10X", + "Luka Warren", ) ) diff --git a/qt/aqt/stats.py b/qt/aqt/stats.py index 90a3af964..6af03e118 100644 --- a/qt/aqt/stats.py +++ b/qt/aqt/stats.py @@ -83,8 +83,15 @@ class NewDeckStats(QDialog): path = self._imagePath() if not path: return - self.form.web.page().printToPdf(path) - tooltip(tr.statistics_saved()) + # When scrolled down in dark mode, the top of the page in the + # final PDF will have a white background, making the text and graphs + # unreadable. A simple fix for now is to scroll to the top of the + # page first. + def after_scroll(arg: Any) -> None: + self.form.web.page().printToPdf(path) + tooltip(tr.statistics_saved()) + + self.form.web.evalWithCallback("window.scrollTo(0, 0);", after_scroll) # legacy add-ons def changePeriod(self, n: Any) -> None: From 60172140424966c1dcb51642f1dba1f25df610f5 Mon Sep 17 00:00:00 2001 From: Henrik Giesel Date: Tue, 19 Apr 2022 08:08:32 +0200 Subject: [PATCH 03/16] Fix resetting of formatting modes when pressing modifer or dead keys (#1811) * Fix shift removing bold formatting * Remove input handler on selection change We listened to "non-input events" mostly to catch events that change the selection (like Ctrl-a, or arrow keys) anyway --- ts/lib/keys.ts | 13 +++++++++---- ts/lib/shortcuts.ts | 4 ++-- ts/sveltelib/input-handler.ts | 12 ++---------- 3 files changed, 13 insertions(+), 16 deletions(-) diff --git a/ts/lib/keys.ts b/ts/lib/keys.ts index 7beda3508..f7302dc84 100644 --- a/ts/lib/keys.ts +++ b/ts/lib/keys.ts @@ -16,11 +16,16 @@ function translateModifierToPlatform(modifier: Modifier): string { return platformModifiers[allModifiers.indexOf(modifier)]; } -const GENERAL_KEY = 0; -const NUMPAD_KEY = 3; +export function checkIfModifierKey(event: KeyboardEvent): boolean { + // At least the web view on Desktop Anki gives out the wrong values for + // `event.location`, which is why we do it like this. + let isInputKey = false; -export function checkIfInputKey(event: KeyboardEvent): boolean { - return event.location === GENERAL_KEY || event.location === NUMPAD_KEY; + for (const modifier of allModifiers) { + isInputKey ||= event.code.startsWith(modifier); + } + + return isInputKey; } export function keyboardEventIsPrintableKey(event: KeyboardEvent): boolean { diff --git a/ts/lib/shortcuts.ts b/ts/lib/shortcuts.ts index e4da2732d..7dab8e947 100644 --- a/ts/lib/shortcuts.ts +++ b/ts/lib/shortcuts.ts @@ -4,7 +4,7 @@ import { on } from "./events"; import type { Modifier } from "./keys"; import { - checkIfInputKey, + checkIfModifierKey, checkModifiers, keyToPlatformString, modifiersToPlatformString, @@ -135,7 +135,7 @@ function innerShortcut( function handler(event: KeyboardEvent): void { if (nextCheck(event)) { innerShortcut(target, event, callback, ...restChecks); - } else if (checkIfInputKey(event)) { + } else if (!checkIfModifierKey(event)) { // Any non-modifier key will cancel the shortcut sequence remove(); } diff --git a/ts/sveltelib/input-handler.ts b/ts/sveltelib/input-handler.ts index c91bc286f..ff0ec573b 100644 --- a/ts/sveltelib/input-handler.ts +++ b/ts/sveltelib/input-handler.ts @@ -3,7 +3,6 @@ import { getRange, getSelection } from "../lib/cross-browser"; import { on } from "../lib/events"; -import { keyboardEventIsPrintableKey } from "../lib/keys"; import { HandlerList } from "./handler-list"; const nbsp = "\xa0"; @@ -62,13 +61,6 @@ function useInputHandler(): [InputHandlerAPI, SetupInputHandlerAction] { insertText.clear(); } - function onKeydown(event: KeyboardEvent): void { - /* using arrow keys should cancel */ - if (!keyboardEventIsPrintableKey(event)) { - clearInsertText(); - } - } - function onInput(this: HTMLElement, event: InputEvent): void { // prevent unwanted
from being left behind when clearing field contents if ( @@ -87,7 +79,7 @@ function useInputHandler(): [InputHandlerAPI, SetupInputHandlerAction] { const blurOff = on(element, "blur", clearInsertText); const pointerDownOff = on(element, "pointerdown", clearInsertText); - const keyDownOff = on(element, "keydown", onKeydown); + const selectionChangeOff = on(document, "selectionchange", clearInsertText); return { destroy() { @@ -95,7 +87,7 @@ function useInputHandler(): [InputHandlerAPI, SetupInputHandlerAction] { inputOff(); blurOff(); pointerDownOff(); - keyDownOff(); + selectionChangeOff(); }, }; } From 856d4763a9c072e24395a4d1b94ef5ee76afe13c Mon Sep 17 00:00:00 2001 From: Damien Elmes Date: Tue, 19 Apr 2022 16:28:44 +1000 Subject: [PATCH 04/16] Switch strong/em back to b/i In the absence of strong arguments either way, and given the fact that strong/em has broken a few (perhaps rare) use-cases, reverting seems prudent. But happy to revisit this again in the future if someone has a convincing argument for why we should be doing it differently. Closes #1807 --- ts/editor/editor-toolbar/BoldButton.svelte | 2 +- ts/editor/editor-toolbar/ItalicButton.svelte | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/ts/editor/editor-toolbar/BoldButton.svelte b/ts/editor/editor-toolbar/BoldButton.svelte index 68b4fcfad..edd07a307 100644 --- a/ts/editor/editor-toolbar/BoldButton.svelte +++ b/ts/editor/editor-toolbar/BoldButton.svelte @@ -15,7 +15,7 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html import { context as editorToolbarContext } from "./EditorToolbar.svelte"; import { boldIcon } from "./icons"; - const surroundElement = document.createElement("strong"); + const surroundElement = document.createElement("b"); function matcher(element: HTMLElement | SVGElement, match: MatchType): void { if (element.tagName === "B" || element.tagName === "STRONG") { diff --git a/ts/editor/editor-toolbar/ItalicButton.svelte b/ts/editor/editor-toolbar/ItalicButton.svelte index 372ff7842..a4d9fd7d8 100644 --- a/ts/editor/editor-toolbar/ItalicButton.svelte +++ b/ts/editor/editor-toolbar/ItalicButton.svelte @@ -15,7 +15,7 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html import { context as editorToolbarContext } from "./EditorToolbar.svelte"; import { italicIcon } from "./icons"; - const surroundElement = document.createElement("em"); + const surroundElement = document.createElement("i"); function matcher(element: HTMLElement | SVGElement, match: MatchType): void { if (element.tagName === "I" || element.tagName === "EM") { From a7cb5e210e8733b97d1226cea3c9386593e6677a Mon Sep 17 00:00:00 2001 From: Damien Elmes Date: Tue, 19 Apr 2022 17:10:34 +1000 Subject: [PATCH 05/16] Close browser before full sync or colpkg import/export Closes #1797 --- qt/aqt/browser/browser.py | 6 ++++++ qt/aqt/exporting.py | 3 +++ qt/aqt/main.py | 10 +++------- qt/aqt/sync.py | 12 +++++++++--- qt/tools/genhooks_gui.py | 10 ++++++++++ 5 files changed, 31 insertions(+), 10 deletions(-) diff --git a/qt/aqt/browser/browser.py b/qt/aqt/browser/browser.py index 349203a91..ef996641c 100644 --- a/qt/aqt/browser/browser.py +++ b/qt/aqt/browser/browser.py @@ -897,6 +897,7 @@ class Browser(QMainWindow): gui_hooks.operation_did_execute.append(self.on_operation_did_execute) gui_hooks.focus_did_change.append(self.on_focus_change) gui_hooks.flag_label_did_change.append(self._update_flag_labels) + gui_hooks.collection_will_temporarily_close.append(self._on_temporary_close) def teardownHooks(self) -> None: gui_hooks.undo_state_did_change.remove(self.on_undo_state_change) @@ -905,6 +906,11 @@ class Browser(QMainWindow): gui_hooks.operation_did_execute.remove(self.on_operation_did_execute) gui_hooks.focus_did_change.remove(self.on_focus_change) gui_hooks.flag_label_did_change.remove(self._update_flag_labels) + gui_hooks.collection_will_temporarily_close.remove(self._on_temporary_close) + + def _on_temporary_close(self, col: Collection) -> None: + # we could reload browser columns in the future; for now we just close + self.close() # Undo ###################################################################### diff --git a/qt/aqt/exporting.py b/qt/aqt/exporting.py index de8009760..52ed11a21 100644 --- a/qt/aqt/exporting.py +++ b/qt/aqt/exporting.py @@ -15,6 +15,7 @@ from anki import hooks from anki.cards import CardId from anki.decks import DeckId from anki.exporting import Exporter, exporters +from aqt import gui_hooks from aqt.errors import show_exception from aqt.qt import * from aqt.utils import ( @@ -181,6 +182,8 @@ class ExportDialog(QDialog): else: self.on_export_finished() + if self.isVerbatim: + gui_hooks.collection_will_temporarily_close(self.mw.col) self.mw.progress.start() hooks.media_files_did_export.append(exported_media) diff --git a/qt/aqt/main.py b/qt/aqt/main.py index e9fbaa238..e3b1c5a2c 100644 --- a/qt/aqt/main.py +++ b/qt/aqt/main.py @@ -575,8 +575,9 @@ class AnkiQt(QMainWindow): self.col = Collection(cpath, backend=self.backend) self.setEnabled(True) - def reopen(self) -> None: - self.col.reopen() + def reopen(self, after_full_sync: bool = False) -> None: + self.col.reopen(after_full_sync=after_full_sync) + gui_hooks.collection_did_temporarily_close(self.col) def unloadCollection(self, onsuccess: Callable) -> None: def after_media_sync() -> None: @@ -629,11 +630,6 @@ class AnkiQt(QMainWindow): if corrupt: showWarning(tr.qt_misc_your_collection_file_appears_to_be()) - def _close_for_full_download(self) -> None: - "Backup and prepare collection to be overwritten." - self.create_backup_now() - self.col.close_for_full_sync() - def apply_collection_options(self) -> None: "Setup audio after collection loaded." aqt.sound.av_player.interrupt_current_audio = self.col.get_config_bool( diff --git a/qt/aqt/sync.py b/qt/aqt/sync.py index 6d828c716..8dfdd298a 100644 --- a/qt/aqt/sync.py +++ b/qt/aqt/sync.py @@ -14,6 +14,7 @@ from anki.errors import Interrupted, SyncError, SyncErrorKind from anki.lang import without_unicode_isolation from anki.sync import SyncOutput, SyncStatus from anki.utils import plat_desc +from aqt import gui_hooks from aqt.qt import ( QDialog, QDialogButtonBox, @@ -181,13 +182,17 @@ def full_download(mw: aqt.main.AnkiQt, on_done: Callable[[], None]) -> None: qconnect(timer.timeout, on_timer) timer.start(150) + # hook needs to be called early, on the main thread + gui_hooks.collection_will_temporarily_close(mw.col) + def download() -> None: - mw._close_for_full_download() + mw.create_backup_now() + mw.col.close_for_full_sync() mw.col.full_download(mw.pm.sync_auth()) def on_future_done(fut: Future) -> None: timer.stop() - mw.col.reopen(after_full_sync=True) + mw.reopen(after_full_sync=True) mw.reset() try: fut.result() @@ -204,6 +209,7 @@ def full_download(mw: aqt.main.AnkiQt, on_done: Callable[[], None]) -> None: def full_upload(mw: aqt.main.AnkiQt, on_done: Callable[[], None]) -> None: + gui_hooks.collection_will_temporarily_close(mw.col) mw.col.close_for_full_sync() def on_timer() -> None: @@ -215,7 +221,7 @@ def full_upload(mw: aqt.main.AnkiQt, on_done: Callable[[], None]) -> None: def on_future_done(fut: Future) -> None: timer.stop() - mw.col.reopen(after_full_sync=True) + mw.reopen(after_full_sync=True) mw.reset() try: fut.result() diff --git a/qt/tools/genhooks_gui.py b/qt/tools/genhooks_gui.py index 8213e9026..573c3305a 100644 --- a/qt/tools/genhooks_gui.py +++ b/qt/tools/genhooks_gui.py @@ -688,6 +688,16 @@ gui_hooks.webview_did_inject_style_into_page.append(mytest) """, ), Hook(name="profile_will_close", legacy_hook="unloadProfile"), + Hook( + name="collection_will_temporarily_close", + args=["col: anki.collection.Collection"], + doc="""Called before one-way syncs and colpkg imports/exports.""", + ), + Hook( + name="collection_did_temporarily_close", + args=["col: anki.collection.Collection"], + doc="""Called after one-way syncs and colpkg imports/exports.""", + ), Hook( name="collection_did_load", args=["col: anki.collection.Collection"], From adbe96ca0025b6aa1379640807ed7b7e2a3eef33 Mon Sep 17 00:00:00 2001 From: Damien Elmes Date: Tue, 19 Apr 2022 17:16:56 +1000 Subject: [PATCH 06/16] Load page script after body created (#1802) document.body must exist by the time the top level Svelte component initializes. https://forums.ankiweb.net/t/2-1-50-editor-wont-show-when-addons-load-many-js-files/19036 --- qt/aqt/webview.py | 6 ++++-- ts/page.html | 2 +- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/qt/aqt/webview.py b/qt/aqt/webview.py index 7892853a4..955893e83 100644 --- a/qt/aqt/webview.py +++ b/qt/aqt/webview.py @@ -499,7 +499,7 @@ html {{ {font} }} from aqt import mw - head = mw.baseHTML() + csstxt + jstxt + web_content.head + head = mw.baseHTML() + csstxt + web_content.head body_class = theme_manager.body_class() if theme_manager.night_mode: @@ -520,7 +520,9 @@ html {{ {font} }} {head} -{web_content.body} + +{jstxt} +{web_content.body} """ # print(html) self.setHtml(html) diff --git a/ts/page.html b/ts/page.html index 577569617..39acc6792 100644 --- a/ts/page.html +++ b/ts/page.html @@ -4,7 +4,7 @@ - + From cf78a555c6bfaf8ea95fe4f7a8b3719567b640e3 Mon Sep 17 00:00:00 2001 From: Henrik Giesel Date: Tue, 19 Apr 2022 09:20:26 +0200 Subject: [PATCH 07/16] Add some component comment (#1779) * Add a component comment for NoteEditor * Move comments above component template * EditorField -> EditingArea --- ts/editor/FocusTrap.svelte | 4 ++++ ts/editor/NoteEditor.svelte | 8 ++++++++ 2 files changed, 12 insertions(+) diff --git a/ts/editor/FocusTrap.svelte b/ts/editor/FocusTrap.svelte index 2b9b4e792..e9920ed7e 100644 --- a/ts/editor/FocusTrap.svelte +++ b/ts/editor/FocusTrap.svelte @@ -18,6 +18,10 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html } +