Merge remote-tracking branch 'upstream/HEAD' into apkg

This commit is contained in:
RumovZ 2022-04-20 20:52:02 +02:00
commit bf5254e882
29 changed files with 144 additions and 93 deletions

View file

@ -10,6 +10,10 @@ build:windows --build_python_zip=false
# record version/build hash # record version/build hash
build --workspace_status_command='bash ./tools/status.sh' 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 # run clippy when compiling rust in test mode
test --aspects=@rules_rust//rust:defs.bzl%rust_clippy_aspect --output_groups=+clippy_checks test --aspects=@rules_rust//rust:defs.bzl%rust_clippy_aspect --output_groups=+clippy_checks

View file

@ -1 +1 @@
5.0.0 5.1.1

View file

@ -99,6 +99,7 @@ Sachin Govind <sachin.govind.too@gmail.com>
Bruce Harris <github.com/bruceharris> Bruce Harris <github.com/bruceharris>
Patric Cunha <patricc@agap2.pt> Patric Cunha <patricc@agap2.pt>
Brayan Oliveira <github.com/BrayanDSO> Brayan Oliveira <github.com/BrayanDSO>
Luka Warren <github.com/lukawarren>
******************** ********************

View file

@ -10,7 +10,7 @@ load("@io_bazel_rules_sass//:defs.bzl", "sass_repositories")
load("//python/pyqt:defs.bzl", "install_pyqt") load("//python/pyqt:defs.bzl", "install_pyqt")
load("@rules_python//python:pip.bzl", "pip_parse") load("@rules_python//python:pip.bzl", "pip_parse")
anki_version = "2.1.51" anki_version = "2.1.52"
def setup_deps(): def setup_deps():
bazel_skylib_workspace() bazel_skylib_workspace()

View file

@ -8,6 +8,7 @@ cargo_build_script(
srcs = ["build.rs"], srcs = ["build.rs"],
build_script_env = { build_script_env = {
"PYO3_PYTHON": "$(location @python)", "PYO3_PYTHON": "$(location @python)",
"MACOSX_DEPLOYMENT_TARGET": "10.13",
}, },
data = [ data = [
"@python", "@python",

View file

@ -1,5 +1,5 @@
pyqt6==6.2.2 pyqt6==6.3.0
pyqt6-qt6==6.2.2 pyqt6-qt6==6.3.0
pyqt6-webengine==6.2.1 pyqt6-webengine==6.3.0
pyqt6-webengine-qt6==6.2.2 pyqt6-webengine-qt6==6.3.0
pyqt6_sip==13.2.1 pyqt6_sip==13.3.1

View file

@ -1,51 +1,48 @@
pyqt6==6.2.2 \ pyqt6==6.3.0 \
--hash=sha256:6f983a42239a6b7829ceff718ea943b8034f8d9e5d796a0fcfda0bc53fbaf8e9 \ --hash=sha256:20dec18471d3e574e37411618725dc2901443a781f5c6d974b041413141f33e4 \
--hash=sha256:9325a0f51c08e37340d916d2723daf9a25c844dac99d71d6c5aacb4f6ee00171 \ --hash=sha256:4fd85dcb15ea4e734b6e4e216fe9a6246779761edaf2cf7c0cce1a2303a8d31b \
--hash=sha256:e9d56e25d307c50a8f21c2f210ddada661fac6ffd882deea82b50127919823fd \ --hash=sha256:e193f8714ff21cb13fe4a6148b963893ff9ff738676fc9c853333259b9b64425 \
--hash=sha256:eda7a54f47d70377f95d0aafccfca83a7f5a6895f020d38986dfa96aa8b0883c --hash=sha256:ff4244507d1ff54fffa27cdaf4d4df90e4252fd1843ba28d2f32a4c79c3ece4e
# via # via
# -r requirements.in # -r requirements.in
# pyqt6-webengine # pyqt6-webengine
pyqt6-qt6==6.2.2 \ pyqt6-qt6==6.3.0 \
--hash=sha256:47515bf682748a42f41b9df8cc3854d29802199357ea76d39ece55679f390fe6 \ --hash=sha256:0782dc4da4933755182724290b43f52441b1b90c377ee7e6a1b625bb91c09a10 \
--hash=sha256:8cac87d50a8afd0f3af1a3dd7b32e508bb5fd2a7149a272a84f922c63f5af6f3 \ --hash=sha256:7f94dcf6f60e3e395083d87e49f14ccc194cd45d73f301ade39bc6adfdfc765f \
--hash=sha256:b3a3113f3a89d16ea204b99868adc33688559746260e8a2396b2edc9779eb37c \ --hash=sha256:bef30ad651fa5fbc4a5d157862108c6ea270e636cc98b1eafdb93f5c7fef7eff \
--hash=sha256:c26076c16eb1743f10179a62567d47c1cbda43ac45a1759d2e96563c52d8e41c --hash=sha256:e87d192d20c5789aa1271025831881dde071a357a1956c808fb5e1b4603ec832
# via # via
# -r requirements.in # -r requirements.in
# pyqt6 # pyqt6
pyqt6-sip==13.2.0 \ pyqt6-sip==13.3.1 \
--hash=sha256:0baa53e464667b289ee157024e244fd9fa190101ec74eae30d17f0692f6af8cd \ --hash=sha256:0f4d7a0dd9c610ab0b2e7b46cfa45eeecd72a50f9d2a82a883d4fc2a0eabe216 \
--hash=sha256:10e6c5d0004308efcbe8826ff0b8faf6e6f60c9e906cffeb26834e18e6340ed4 \ --hash=sha256:10e54200c619276f4e79c615b0d2f1f612a8b4fe443c119869de62546e0f9f2f \
--hash=sha256:121b887e40daac64f139ce3d7fc7b1d549ce75d423c6c174abbdf6ec7ab5d636 \ --hash=sha256:33c4a39e9fce8448d0abb79f80ecf35b8420ec76fa90b056173ce69a8a2ae769 \
--hash=sha256:12d43e55ec5ce70260097c19ff95942d2d329ba8c81fe140e5e9af08cbcfcb9c \ --hash=sha256:3bffad016f115a003f290bf8e92d2fd38d6b3c832946e7ee98aa9ecc20c431ba \
--hash=sha256:149e91b372037765b18e728e68facdb2f072fbf043f76a5a424d2fa78af13163 \ --hash=sha256:45f23a3de6290ab311d827a371e82c09cd3203d57c74e8aa739280feb0960f80 \
--hash=sha256:5f4b4ce20ff851fbf19cc7777485ac531171308705b1b36305cd71680686941f \ --hash=sha256:77edc790f8bae29729103b378ccd29beba402bcdde5c584bd4aebbf8c8a75caa \
--hash=sha256:66f3bf78d6bb084d10261111c3a7aa7c4125d73006b763544c81278468e9aa9d \ --hash=sha256:7c26dcba7deebb3d001079eda84be911bac55520b5e7b7113fac1859d850b566 \
--hash=sha256:6a12210e8547a8d8f9926fc3773e2c83db4511f9ac8bdec0b11b08a751262474 \ --hash=sha256:98c53675a4fb2507126ff2ea3ec03872d6e59624d45e6ad95053f74002be121f \
--hash=sha256:9b79ca8554e40d70783ab7f7f6e16043ffd6c1d6cc14da5eba6ba474a7002233 \ --hash=sha256:b94dc4ecda4b6a011cdb5fdff0eae27567d30c30bc884c78394b76e02eb3df7a \
--hash=sha256:a0dcd554d00e025c844427860336fab8179173e181717a895af234f0279236f9 \ --hash=sha256:b9506e2ddb126541457db939311432a63d0affc83bf86ff902d1664c7c167db6 \
--hash=sha256:abb26230a444f3b65396c3612af5db4f96a6765a56026078b7b45c8fb8bc1693 \ --hash=sha256:d629c0e39d5ccfdae567b92ba74d92f9180b7c55535f82251f1a12a9076a9e01 \
--hash=sha256:cc92d5fc57a9192f269a622c340551ad9cde6d7a282273ccb3c64cd742f73760 \ --hash=sha256:ef89555ad34ca8b8f6d2e3ccacd4edacebef781404254c2cb17ba1c60cdc57c2 \
--hash=sha256:cda533d545414576e2d88d106c03eaf340bf8897388277838f9f2ffa6e2a7492 \ --hash=sha256:f17c15665a4007cdf167eaeb133408b8e3409203303199a00068da63ea4c41a3
--hash=sha256:d44e69ed4dc208c6cd791be8c54d8d656e1d395fe537bd1320f01b8ab3edec0f \
--hash=sha256:e7c8093a6ef36395353a32df026745254b1ebdfcd490f746307d84ba67bcd93a \
--hash=sha256:f8a65a876b70fc1c4eb95de74e133c81c146f08ad0e04877e16af8e3798b9d50
# via # via
# -r requirements.in # -r requirements.in
# pyqt6 # pyqt6
# pyqt6-webengine # pyqt6-webengine
pyqt6-webengine==6.2.1 \ pyqt6-webengine==6.3.0 \
--hash=sha256:52c65c70050f19e4d38bd9e91667a2d87c595c854b3ba162d863e3c05f696957 \ --hash=sha256:1309c518eb159dcb4a9635a5f42e6d11c141ebb3baaee442cc8edf3893f55361 \
--hash=sha256:6f6d7cb612f20d1f1f8ea1bbe7ebb8bbaa3f7fcd56f0e9c41582851998be20c3 \ --hash=sha256:147e6dab0184071f5febf020e29fde6a8dcced6b56f40bbc2a056fac59e76a98 \
--hash=sha256:97d4b43f0959458adc119511d22a12814416a3bf8bc78f246068fdd54a6fd2b3 \ --hash=sha256:ab2aedbeec54f1bcff872f7dfc236aa0fce4b55cd30f608ca89b408ee9e8147b \
--hash=sha256:aa2c8e93f99756f1ec5a703d3131605294c6603869e50204ae5a5f52ea15ec2f --hash=sha256:bb37a7b2a9c0b2856a16066869389053901c20163ef9637344f0279f9597c57d
# via -r requirements.in # via -r requirements.in
pyqt6-webengine-qt6==6.2.2 \ pyqt6-webengine-qt6==6.3.0 \
--hash=sha256:8449cec1492085bf0e6168c02e9c0255f66c67cdbdbb4c06d9f0c398f44d4b0d \ --hash=sha256:27bd08f6a0eb5508c444e95aa45b1574239faf03d6d45d5fcd6c2c8d2fd21c69 \
--hash=sha256:8f83d93b58ea4f13f03ba2c82820a3ade2942f59240191f8153ee4d1e8221e6b \ --hash=sha256:5401e2a6fa6b13f271af946433f95af06e102613c90344aceb8424514a3f8784 \
--hash=sha256:b88b913a4bff6246cf57ed5b2067d4dd16502502c2c97b9c63257156beeaa39c \ --hash=sha256:c075ed6c73f9d3e14e7ab5a9d1501590104dab7ecb3d57ad41b2d35760237ec5 \
--hash=sha256:f7d42669c484dc65dac7b4833d7638b888603fdd127b7adc2e5c3b0eaa43ff9b --hash=sha256:d9d3f1ad908e8f65088db65f27958904104275c6b32c77fec34f319d98eec9b2
# via # via
# -r requirements.in # -r requirements.in
# pyqt6-webengine # pyqt6-webengine

View file

@ -495,7 +495,7 @@ def _run(argv: Optional[list[str]] = None, exec: bool = True) -> Optional[AnkiAp
if ( if (
getattr(sys, "frozen", False) getattr(sys, "frozen", False)
and os.getenv("QT_QPA_PLATFORM") == "wayland" and (os.getenv("QT_QPA_PLATFORM") == "wayland" or os.getenv("WAYLAND_DISPLAY"))
and not os.getenv("ANKI_WAYLAND") and not os.getenv("ANKI_WAYLAND")
): ):
# users need to opt in to wayland support, given the issues it has # users need to opt in to wayland support, given the issues it has

View file

@ -225,6 +225,7 @@ def show(mw: aqt.AnkiQt) -> QDialog:
"Sergio Quintero", "Sergio Quintero",
"Nicholas Flint", "Nicholas Flint",
"Daniel Vieira Memoria10X", "Daniel Vieira Memoria10X",
"Luka Warren",
) )
) )

View file

@ -897,6 +897,7 @@ class Browser(QMainWindow):
gui_hooks.operation_did_execute.append(self.on_operation_did_execute) gui_hooks.operation_did_execute.append(self.on_operation_did_execute)
gui_hooks.focus_did_change.append(self.on_focus_change) gui_hooks.focus_did_change.append(self.on_focus_change)
gui_hooks.flag_label_did_change.append(self._update_flag_labels) 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: def teardownHooks(self) -> None:
gui_hooks.undo_state_did_change.remove(self.on_undo_state_change) 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.operation_did_execute.remove(self.on_operation_did_execute)
gui_hooks.focus_did_change.remove(self.on_focus_change) gui_hooks.focus_did_change.remove(self.on_focus_change)
gui_hooks.flag_label_did_change.remove(self._update_flag_labels) 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 # Undo
###################################################################### ######################################################################

View file

@ -1331,6 +1331,10 @@ class EditorWebView(AnkiWebView):
def flagAnkiText(self) -> None: def flagAnkiText(self) -> None:
# be ready to adjust when clipboard event fires # be ready to adjust when clipboard event fires
self._markInternal = True self._markInternal = True
# workaround broken QClipboard.dataChanged() on recent Qt6 versions
# https://github.com/ankitects/anki/issues/1793
if is_win and qtmajor == 6:
self.editor.mw.progress.single_shot(300, self._flagAnkiText, True)
def _flagAnkiText(self) -> None: def _flagAnkiText(self) -> None:
# add a comment in the clipboard html so we can tell text is copied # add a comment in the clipboard html so we can tell text is copied
@ -1342,6 +1346,10 @@ class EditorWebView(AnkiWebView):
if not mime.hasHtml(): if not mime.hasHtml():
return return
html = mime.html() html = mime.html()
if is_win and qtmajor == 6:
# workaround Qt including CF_HTML header in clipboard
# FIXME: remove after we switch to Qt 6.2.5/6.3.1+.
html = re.sub(r"^Version:0.9(.|\r|\n)+?SourceURL:.*?\r\n", "", html)
mime.setHtml(f"<!--anki-->{html}") mime.setHtml(f"<!--anki-->{html}")
aqt.mw.progress.timer(10, lambda: clip.setMimeData(mime), False, parent=self) aqt.mw.progress.timer(10, lambda: clip.setMimeData(mime), False, parent=self)

View file

@ -15,6 +15,7 @@ from anki import hooks
from anki.cards import CardId from anki.cards import CardId
from anki.decks import DeckId from anki.decks import DeckId
from anki.exporting import Exporter, exporters from anki.exporting import Exporter, exporters
from aqt import gui_hooks
from aqt.errors import show_exception from aqt.errors import show_exception
from aqt.qt import * from aqt.qt import *
from aqt.utils import ( from aqt.utils import (
@ -181,6 +182,8 @@ class ExportDialog(QDialog):
else: else:
self.on_export_finished() self.on_export_finished()
if self.isVerbatim:
gui_hooks.collection_will_temporarily_close(self.mw.col)
self.mw.progress.start() self.mw.progress.start()
hooks.media_files_did_export.append(exported_media) hooks.media_files_did_export.append(exported_media)

View file

@ -575,8 +575,9 @@ class AnkiQt(QMainWindow):
self.col = Collection(cpath, backend=self.backend) self.col = Collection(cpath, backend=self.backend)
self.setEnabled(True) self.setEnabled(True)
def reopen(self) -> None: def reopen(self, after_full_sync: bool = False) -> None:
self.col.reopen() self.col.reopen(after_full_sync=after_full_sync)
gui_hooks.collection_did_temporarily_close(self.col)
def unloadCollection(self, onsuccess: Callable) -> None: def unloadCollection(self, onsuccess: Callable) -> None:
def after_media_sync() -> None: def after_media_sync() -> None:
@ -629,11 +630,6 @@ class AnkiQt(QMainWindow):
if corrupt: if corrupt:
showWarning(tr.qt_misc_your_collection_file_appears_to_be()) 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: def apply_collection_options(self) -> None:
"Setup audio after collection loaded." "Setup audio after collection loaded."
aqt.sound.av_player.interrupt_current_audio = self.col.get_config_bool( aqt.sound.av_player.interrupt_current_audio = self.col.get_config_bool(

View file

@ -83,8 +83,15 @@ class NewDeckStats(QDialog):
path = self._imagePath() path = self._imagePath()
if not path: if not path:
return return
self.form.web.page().printToPdf(path) # When scrolled down in dark mode, the top of the page in the
tooltip(tr.statistics_saved()) # 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 # legacy add-ons
def changePeriod(self, n: Any) -> None: def changePeriod(self, n: Any) -> None:

View file

@ -14,6 +14,7 @@ from anki.errors import Interrupted, SyncError, SyncErrorKind
from anki.lang import without_unicode_isolation from anki.lang import without_unicode_isolation
from anki.sync import SyncOutput, SyncStatus from anki.sync import SyncOutput, SyncStatus
from anki.utils import plat_desc from anki.utils import plat_desc
from aqt import gui_hooks
from aqt.qt import ( from aqt.qt import (
QDialog, QDialog,
QDialogButtonBox, QDialogButtonBox,
@ -181,13 +182,17 @@ def full_download(mw: aqt.main.AnkiQt, on_done: Callable[[], None]) -> None:
qconnect(timer.timeout, on_timer) qconnect(timer.timeout, on_timer)
timer.start(150) timer.start(150)
# hook needs to be called early, on the main thread
gui_hooks.collection_will_temporarily_close(mw.col)
def download() -> None: 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()) mw.col.full_download(mw.pm.sync_auth())
def on_future_done(fut: Future) -> None: def on_future_done(fut: Future) -> None:
timer.stop() timer.stop()
mw.col.reopen(after_full_sync=True) mw.reopen(after_full_sync=True)
mw.reset() mw.reset()
try: try:
fut.result() 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: 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() mw.col.close_for_full_sync()
def on_timer() -> None: 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: def on_future_done(fut: Future) -> None:
timer.stop() timer.stop()
mw.col.reopen(after_full_sync=True) mw.reopen(after_full_sync=True)
mw.reset() mw.reset()
try: try:
fut.result() fut.result()

View file

@ -499,7 +499,7 @@ html {{ {font} }}
from aqt import mw 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() body_class = theme_manager.body_class()
if theme_manager.night_mode: if theme_manager.night_mode:
@ -520,7 +520,9 @@ html {{ {font} }}
{head} {head}
</head> </head>
<body class="{body_class}">{web_content.body}</body> <body class="{body_class}">
{jstxt}
{web_content.body}</body>
</html>""" </html>"""
# print(html) # print(html)
self.setHtml(html) self.setHtml(html)

View file

@ -389,7 +389,7 @@ if is_win:
build_windows_installers() build_windows_installers()
if is_mac: if is_mac:
print("outputs are in .bazel/out/build/{std,alt}") print("outputs are in .bazel/out/build/app")
print("dmg can be created with mac/finalize.py dmg") print("dmg can be created with mac/finalize.py dmg")
else: else:
print("outputs are in .bazel/out/dist/") print("outputs are in .bazel/out/dist/")

View file

@ -56,8 +56,8 @@ impl Variant {
fn qt_repo(&self) -> &str { fn qt_repo(&self) -> &str {
match self { match self {
Variant::StandardX86 => "pyqt6.2_mac_bundle_amd64", Variant::StandardX86 => "pyqt6.3_mac_bundle_amd64",
Variant::StandardArm => "pyqt6.2_mac_bundle_arm64", Variant::StandardArm => "pyqt6.3_mac_bundle_arm64",
Variant::AlternateX86 => "pyqt5.14_mac_bundle_amd64", Variant::AlternateX86 => "pyqt5.14_mac_bundle_amd64",
} }
} }

View file

@ -688,6 +688,16 @@ gui_hooks.webview_did_inject_style_into_page.append(mytest)
""", """,
), ),
Hook(name="profile_will_close", legacy_hook="unloadProfile"), 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( Hook(
name="collection_did_load", name="collection_did_load",
args=["col: anki.collection.Collection"], args=["col: anki.collection.Collection"],

View file

@ -115,12 +115,12 @@ def register_repos():
################ ################
core_i18n_repo = "anki-core-i18n" core_i18n_repo = "anki-core-i18n"
core_i18n_commit = "d6f01a26657ae78371813c8f54e415dc62032a90" core_i18n_commit = "3fbbbd277adf6a307aad1f1389105740fb14bb52"
core_i18n_zip_csum = "cd74a8f40a6080a144b6f80ad88bd37309ea4f17d4dd3995d154482eafc86f98" core_i18n_zip_csum = "18db75ca57b5068c0c9cb926f19b451185cf04a086004a96fcf8f6e734f51b29"
qtftl_i18n_repo = "anki-desktop-ftl" qtftl_i18n_repo = "anki-desktop-ftl"
qtftl_i18n_commit = "76751e5dcb8bce78cd54b052f1b444531934ca45" qtftl_i18n_commit = "a6811a12eaf86ff02d5ce168ac65022f3e442993"
qtftl_i18n_zip_csum = "ad37c33d3d03a05b1094d6692300401a304da9ba48fb2dcf7ad01242ecc97997" qtftl_i18n_zip_csum = "03b62eb593e4929e502e6ee6d7fd81492d6fe9164aec50573e30028edbc9bf2e"
i18n_build_content = """ i18n_build_content = """
filegroup( filegroup(
@ -226,22 +226,22 @@ exports_files(["l10n.toml"])
maybe( maybe(
http_archive, http_archive,
name = "pyqt6.2_mac_bundle_amd64", name = "pyqt6.3_mac_bundle_amd64",
build_file_content = " ", build_file_content = " ",
urls = [ urls = [
"https://github.com/ankitects/anki-bundle-extras/releases/download/anki-2022-02-09/pyqt6.2-mac-amd64.tar.gz", "https://github.com/ankitects/anki-bundle-extras/releases/download/anki-2022-04-20/pyqt6.3-mac-amd64.tar.gz",
], ],
sha256 = "c7bf899eee33fcb3b5848f5d3e5fc390012efc05c2308e4349b7bbd5939c85f0", sha256 = "949d403c26036cd13a4a0c86c9416f32ac6d2aa58c6946be93ab949ee6cf9b11",
) )
maybe( maybe(
http_archive, http_archive,
name = "pyqt6.2_mac_bundle_arm64", name = "pyqt6.3_mac_bundle_arm64",
build_file_content = " ", build_file_content = " ",
urls = [ urls = [
"https://github.com/ankitects/anki-bundle-extras/releases/download/anki-2022-02-09/pyqt6.2-mac-arm64.tar.gz", "https://github.com/ankitects/anki-bundle-extras/releases/download/anki-2022-04-20/pyqt6.3-mac-arm64.tar.gz",
], ],
sha256 = "7a4b7d5bd65c83fd16cf7e56929183ef0d1d7bb67f9deea8f2482d7378e0ea02", sha256 = "c7df555623cdbd8f6981c15b4bfc58665b8e92d91fb43a7082bcfa1852ede10d",
) )
maybe( maybe(

View file

@ -22,11 +22,11 @@ bazel query @pyoxidizer//:*
if [[ "$OSTYPE" == "darwin"* ]]; then if [[ "$OSTYPE" == "darwin"* ]]; then
if [ $(uname -m) != "arm64" ]; then if [ $(uname -m) != "arm64" ]; then
bazel query @audio_mac_amd64//:* > /dev/null bazel query @audio_mac_amd64//:* > /dev/null
bazel query @pyqt6.2_mac_bundle_amd64//:* > /dev/null bazel query @pyqt6.3_mac_bundle_amd64//:* > /dev/null
bazel query @pyqt5.14_mac_bundle_amd64//:* > /dev/null bazel query @pyqt5.14_mac_bundle_amd64//:* > /dev/null
else else
bazel query @audio_mac_arm64//:* > /dev/null bazel query @audio_mac_arm64//:* > /dev/null
bazel query @pyqt6.2_mac_bundle_arm64//:* > /dev/null bazel query @pyqt6.3_mac_bundle_arm64//:* > /dev/null
bazel query @protobuf_wheel_mac_arm64//:* > /dev/null bazel query @protobuf_wheel_mac_arm64//:* > /dev/null
fi fi

View file

@ -18,6 +18,10 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
} }
</script> </script>
<!--
@component
Allows "focusing" an EditingArea, even though it has no open editing inputs.
-->
<input bind:this={input} class="focus-trap" readonly tabindex="-1" on:focus /> <input bind:this={input} class="focus-trap" readonly tabindex="-1" on:focus />
<style lang="scss"> <style lang="scss">

View file

@ -273,6 +273,14 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
setupLifecycleHooks(api); setupLifecycleHooks(api);
</script> </script>
<!--
@component
Serves as a pre-slotted convenience component which combines all the common
components and functionality for general note editing.
Functionality exclusive to specifc note-editing views (e.g. in the browser or
the AddCards dialog) should be implemented in the user of this component.
-->
<div class="note-editor"> <div class="note-editor">
<FieldsEditor> <FieldsEditor>
<EditorToolbar {size} {wrap} api={toolbar}> <EditorToolbar {size} {wrap} api={toolbar}>

View file

@ -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 { context as editorToolbarContext } from "./EditorToolbar.svelte";
import { boldIcon } from "./icons"; import { boldIcon } from "./icons";
const surroundElement = document.createElement("strong"); const surroundElement = document.createElement("b");
function matcher(element: HTMLElement | SVGElement, match: MatchType): void { function matcher(element: HTMLElement | SVGElement, match: MatchType): void {
if (element.tagName === "B" || element.tagName === "STRONG") { if (element.tagName === "B" || element.tagName === "STRONG") {

View file

@ -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 { context as editorToolbarContext } from "./EditorToolbar.svelte";
import { italicIcon } from "./icons"; import { italicIcon } from "./icons";
const surroundElement = document.createElement("em"); const surroundElement = document.createElement("i");
function matcher(element: HTMLElement | SVGElement, match: MatchType): void { function matcher(element: HTMLElement | SVGElement, match: MatchType): void {
if (element.tagName === "I" || element.tagName === "EM") { if (element.tagName === "I" || element.tagName === "EM") {

View file

@ -16,11 +16,16 @@ function translateModifierToPlatform(modifier: Modifier): string {
return platformModifiers[allModifiers.indexOf(modifier)]; return platformModifiers[allModifiers.indexOf(modifier)];
} }
const GENERAL_KEY = 0; export function checkIfModifierKey(event: KeyboardEvent): boolean {
const NUMPAD_KEY = 3; // 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 { for (const modifier of allModifiers) {
return event.location === GENERAL_KEY || event.location === NUMPAD_KEY; isInputKey ||= event.code.startsWith(modifier);
}
return isInputKey;
} }
export function keyboardEventIsPrintableKey(event: KeyboardEvent): boolean { export function keyboardEventIsPrintableKey(event: KeyboardEvent): boolean {

View file

@ -4,7 +4,7 @@
import { on } from "./events"; import { on } from "./events";
import type { Modifier } from "./keys"; import type { Modifier } from "./keys";
import { import {
checkIfInputKey, checkIfModifierKey,
checkModifiers, checkModifiers,
keyToPlatformString, keyToPlatformString,
modifiersToPlatformString, modifiersToPlatformString,
@ -135,7 +135,7 @@ function innerShortcut(
function handler(event: KeyboardEvent): void { function handler(event: KeyboardEvent): void {
if (nextCheck(event)) { if (nextCheck(event)) {
innerShortcut(target, event, callback, ...restChecks); innerShortcut(target, event, callback, ...restChecks);
} else if (checkIfInputKey(event)) { } else if (!checkIfModifierKey(event)) {
// Any non-modifier key will cancel the shortcut sequence // Any non-modifier key will cancel the shortcut sequence
remove(); remove();
} }

View file

@ -4,7 +4,7 @@
<meta charset="utf-8" /> <meta charset="utf-8" />
<meta name="viewport" id="viewport" content="width=device-width" /> <meta name="viewport" id="viewport" content="width=device-width" />
<link href="{PAGE}.css" rel="stylesheet" /> <link href="{PAGE}.css" rel="stylesheet" />
<script src="{PAGE}.js"></script> <script src="{PAGE}.js" defer></script>
</head> </head>
<body></body> <body></body>
</html> </html>

View file

@ -3,7 +3,6 @@
import { getRange, getSelection } from "../lib/cross-browser"; import { getRange, getSelection } from "../lib/cross-browser";
import { on } from "../lib/events"; import { on } from "../lib/events";
import { keyboardEventIsPrintableKey } from "../lib/keys";
import { HandlerList } from "./handler-list"; import { HandlerList } from "./handler-list";
const nbsp = "\xa0"; const nbsp = "\xa0";
@ -62,13 +61,6 @@ function useInputHandler(): [InputHandlerAPI, SetupInputHandlerAction] {
insertText.clear(); insertText.clear();
} }
function onKeydown(event: KeyboardEvent): void {
/* using arrow keys should cancel */
if (!keyboardEventIsPrintableKey(event)) {
clearInsertText();
}
}
function onInput(this: HTMLElement, event: InputEvent): void { function onInput(this: HTMLElement, event: InputEvent): void {
// prevent unwanted <div> from being left behind when clearing field contents // prevent unwanted <div> from being left behind when clearing field contents
if ( if (
@ -87,7 +79,7 @@ function useInputHandler(): [InputHandlerAPI, SetupInputHandlerAction] {
const blurOff = on(element, "blur", clearInsertText); const blurOff = on(element, "blur", clearInsertText);
const pointerDownOff = on(element, "pointerdown", clearInsertText); const pointerDownOff = on(element, "pointerdown", clearInsertText);
const keyDownOff = on(element, "keydown", onKeydown); const selectionChangeOff = on(document, "selectionchange", clearInsertText);
return { return {
destroy() { destroy() {
@ -95,7 +87,7 @@ function useInputHandler(): [InputHandlerAPI, SetupInputHandlerAction] {
inputOff(); inputOff();
blurOff(); blurOff();
pointerDownOff(); pointerDownOff();
keyDownOff(); selectionChangeOff();
}, },
}; };
} }