mirror of
https://github.com/ankitects/anki.git
synced 2025-09-21 07:22:23 -04:00
Merge branch 'main' into svelte5
This commit is contained in:
commit
add1248cd8
156 changed files with 813 additions and 731 deletions
|
@ -22,7 +22,7 @@ echo "--- Ensure libs importable"
|
|||
SKIP_RUN=1 ./run
|
||||
|
||||
echo "--- Check Rust libs"
|
||||
cargo install cargo-deny --version 0.14.12
|
||||
cargo install cargo-deny --version 0.14.24
|
||||
cargo deny check
|
||||
|
||||
echo "--- Cleanup"
|
||||
|
|
|
@ -5,8 +5,8 @@
|
|||
db-path = "~/.cargo/advisory-db"
|
||||
db-urls = ["https://github.com/rustsec/advisory-db"]
|
||||
ignore = [
|
||||
# safemem only used by makeapp
|
||||
"RUSTSEC-2023-0081",
|
||||
# gix only used by burn-train
|
||||
"RUSTSEC-2024-0350",
|
||||
]
|
||||
|
||||
[licenses]
|
||||
|
@ -23,6 +23,7 @@ allow = [
|
|||
"CC0-1.0",
|
||||
"Unlicense",
|
||||
"Zlib",
|
||||
"Unicode-3.0",
|
||||
]
|
||||
confidence-threshold = 0.8
|
||||
# eg { allow = ["Zlib"], name = "adler32", version = "*" },
|
||||
|
|
|
@ -1,10 +1,5 @@
|
|||
[settings]
|
||||
ensure_newline_before_comments=true
|
||||
force_grid_wrap=0
|
||||
include_trailing_comma=True
|
||||
py_version=39
|
||||
known_first_party=anki,aqt,tests
|
||||
line_length=88
|
||||
multi_line_output=3
|
||||
profile=black
|
||||
skip=
|
||||
use_parentheses=True
|
||||
extend_skip=qt/bundle
|
||||
|
|
2
.ruff.toml
Normal file
2
.ruff.toml
Normal file
|
@ -0,0 +1,2 @@
|
|||
target-version = "py39"
|
||||
extend-exclude = ["qt/bundle"]
|
10
CONTRIBUTORS
10
CONTRIBUTORS
|
@ -148,6 +148,7 @@ user1823 <92206575+user1823@users.noreply.github.com>
|
|||
Gustaf Carefall <https://github.com/Gustaf-C>
|
||||
virinci <github.com/virinci>
|
||||
snowtimeglass <snowtimeglass@gmail.com>
|
||||
brishtibheja <sorata225yume@gmail.com>
|
||||
Ben Olson <github.com/grepgrok>
|
||||
Akash Reddy <akashreddy2003@gmail.com>
|
||||
Lucio Sauer <watermanpaint@posteo.net>
|
||||
|
@ -177,7 +178,14 @@ RRomeroJr <117.rromero@gmail.com>
|
|||
Xidorn Quan <me@upsuper.org>
|
||||
Alexander Bocken <alexander@bocken.org>
|
||||
James Elmore <email@jameselmore.org>
|
||||
|
||||
Ian Samir Yep Manzano <https://github.com/isym444>
|
||||
David Culley <6276049+davidculley@users.noreply.github.com>
|
||||
Rastislav Kish <rastislav.kish@protonmail.com>
|
||||
Expertium <https://github.com/Expertium>
|
||||
Christian Donat <https://github.com/cdonat2>
|
||||
Asuka Minato <https://asukaminato.eu.org>
|
||||
Dillon Baldwin <https://github.com/DillBal>
|
||||
Voczi <https://github.com/voczi>
|
||||
********************
|
||||
|
||||
The text of the 3 clause BSD license follows:
|
||||
|
|
12
Cargo.lock
generated
12
Cargo.lock
generated
|
@ -1901,9 +1901,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "fsrs"
|
||||
version = "0.6.4"
|
||||
version = "1.1.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "70cec685337af48789e58cea6ef59ee9f01289d1083428b03fe14e76b98c817c"
|
||||
checksum = "285d9b275f7d5a276f17006e9d92ea67fa9991187ae88760fa96705fba1f97aa"
|
||||
dependencies = [
|
||||
"burn",
|
||||
"itertools 0.12.1",
|
||||
|
@ -3859,9 +3859,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "openssl"
|
||||
version = "0.10.64"
|
||||
version = "0.10.66"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "95a0481286a310808298130d22dd1fef0fa571e05a8f44ec801801e84b216b1f"
|
||||
checksum = "9529f4786b70a3e8c61e11179af17ab6188ad8d0ded78c5529441ed39d4bd9c1"
|
||||
dependencies = [
|
||||
"bitflags 2.6.0",
|
||||
"cfg-if",
|
||||
|
@ -3891,9 +3891,9 @@ checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf"
|
|||
|
||||
[[package]]
|
||||
name = "openssl-sys"
|
||||
version = "0.9.102"
|
||||
version = "0.9.103"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c597637d56fbc83893a35eb0dd04b2b8e7a50c91e64e9493e398b5df4fb45fa2"
|
||||
checksum = "7f9e8deee91df40a943c71b917e5874b951d32a802526c85721ce3b776c929d6"
|
||||
dependencies = [
|
||||
"cc",
|
||||
"libc",
|
||||
|
|
|
@ -35,7 +35,7 @@ git = "https://github.com/ankitects/linkcheck.git"
|
|||
rev = "184b2ca50ed39ca43da13f0b830a463861adb9ca"
|
||||
|
||||
[workspace.dependencies.fsrs]
|
||||
version = "0.6.4"
|
||||
version = "1.1.4"
|
||||
# git = "https://github.com/open-spaced-repetition/fsrs-rs.git"
|
||||
# rev = "58ca25ed2bc4bb1dc376208bbcaed7f5a501b941"
|
||||
# path = "../open-spaced-repetition/fsrs-rs"
|
||||
|
|
|
@ -368,8 +368,8 @@ pub trait FilesHandle {
|
|||
/// different variables. This is a shortcut for calling .expand_inputs()
|
||||
/// and then .add_inputs_vec()
|
||||
/// - If the variable name is non-empty, a variable of the same name will be
|
||||
/// created so the file list can be accessed in the command. By convention,
|
||||
/// this is often `in`.
|
||||
/// created so the file list can be accessed in the command. By
|
||||
/// convention, this is often `in`.
|
||||
fn add_inputs(&mut self, variable: &'static str, inputs: impl AsRef<BuildInput>);
|
||||
fn add_inputs_vec(&mut self, variable: &'static str, inputs: Vec<String>);
|
||||
fn add_order_only_inputs(&mut self, variable: &'static str, inputs: impl AsRef<BuildInput>);
|
||||
|
@ -394,10 +394,10 @@ pub trait FilesHandle {
|
|||
/// - Each output automatically has $builddir/ prefixed to it if it does not
|
||||
/// already start with it.
|
||||
/// - If the variable name is non-empty, a variable of the same name will be
|
||||
/// created so the file list can be accessed in the command. By convention,
|
||||
/// this is often `out`.
|
||||
/// - If subgroup is true, the files are also placed in a subgroup. Eg
|
||||
/// if a rule `foo` exists and subgroup `bar` is provided, the files are
|
||||
/// created so the file list can be accessed in the command. By
|
||||
/// convention, this is often `out`.
|
||||
/// - If subgroup is true, the files are also placed in a subgroup. Eg if a
|
||||
/// rule `foo` exists and subgroup `bar` is provided, the files are
|
||||
/// accessible via `:foo:bar`. The variable name must not be empty, or
|
||||
/// called `out`.
|
||||
fn add_outputs_ext(
|
||||
|
|
|
@ -223,18 +223,6 @@ impl BuildAction for SvelteCheck {
|
|||
build.add_inputs("yarn", inputs![":yarn:bin"]);
|
||||
build.add_inputs("", &self.inputs);
|
||||
build.add_inputs("", inputs!["yarn.lock"]);
|
||||
build.add_variable(
|
||||
"compiler_warnings",
|
||||
[
|
||||
"a11y-click-events-have-key-events",
|
||||
"a11y-no-noninteractive-tabindex",
|
||||
"a11y-no-static-element-interactions",
|
||||
]
|
||||
.iter()
|
||||
.map(|warning| format!("{}$:ignore", warning))
|
||||
.collect::<Vec<_>>()
|
||||
.join(","),
|
||||
);
|
||||
let hash = simple_hash(&self.tsconfig);
|
||||
build.add_output_stamp(format!("tests/svelte-check.{hash}"));
|
||||
}
|
||||
|
|
|
@ -251,7 +251,7 @@ impl BuildAction for PythonTest {
|
|||
build.add_variable("folder", self.folder);
|
||||
build.add_variable(
|
||||
"pythonpath",
|
||||
&self.python_path.join(if cfg!(windows) { ";" } else { ":" }),
|
||||
self.python_path.join(if cfg!(windows) { ";" } else { ":" }),
|
||||
);
|
||||
build.add_env_var("PYTHONPATH", "$pythonpath");
|
||||
build.add_env_var("ANKI_TEST_MODE", "1");
|
||||
|
|
|
@ -1252,7 +1252,7 @@
|
|||
},
|
||||
{
|
||||
"name": "fsrs",
|
||||
"version": "0.6.4",
|
||||
"version": "1.1.4",
|
||||
"authors": "Open Spaced Repetition",
|
||||
"repository": "https://github.com/open-spaced-repetition/fsrs-rs",
|
||||
"license": "BSD-3-Clause",
|
||||
|
@ -2521,7 +2521,7 @@
|
|||
},
|
||||
{
|
||||
"name": "openssl",
|
||||
"version": "0.10.64",
|
||||
"version": "0.10.66",
|
||||
"authors": "Steven Fackler <sfackler@gmail.com>",
|
||||
"repository": "https://github.com/sfackler/rust-openssl",
|
||||
"license": "Apache-2.0",
|
||||
|
@ -2548,7 +2548,7 @@
|
|||
},
|
||||
{
|
||||
"name": "openssl-sys",
|
||||
"version": "0.9.102",
|
||||
"version": "0.9.103",
|
||||
"authors": "Alex Crichton <alex@alexcrichton.com>|Steven Fackler <sfackler@gmail.com>",
|
||||
"repository": "https://github.com/sfackler/rust-openssl",
|
||||
"license": "MIT",
|
||||
|
|
|
@ -3,32 +3,29 @@
|
|||
For info on contributing things other than code, such as translations, decks
|
||||
and add-ons, please see https://docs.ankiweb.net/contrib
|
||||
|
||||
With most users now on 2.1, the past 2 years have been focused on paying down some
|
||||
of the technical debt that Anki's codebase has built up over the years, and making
|
||||
changes that will make future maintenance and refactoring easier. A lot of Anki's
|
||||
"business logic" has been migrated to Rust, which AnkiMobile and AnkiDroid
|
||||
can also take advantage of - previously a lot of effort was wasted writing the same
|
||||
code for each platform, and then debugging differences in the implementations.
|
||||
Considerable effort has also been put into improving the Python side of things,
|
||||
with type hints added to the majority of the codebase, automatic linting/formatting,
|
||||
and refactoring of parts of the code.
|
||||
|
||||
The import/export code remains to be done, and this will likely
|
||||
take a number of months to work through. Until that is complete, new features
|
||||
will not be the top priority, unless they are easy wins as part of the refactoring
|
||||
process.
|
||||
|
||||
If you are planning to contribute any non-trivial changes, please reach out
|
||||
on the support site before you begin work. Some areas (primarily pylib/) are
|
||||
likely to change/conflict with other work, and larger changes will likely need
|
||||
to wait until the refactoring process is done.
|
||||
|
||||
## Help wanted
|
||||
|
||||
If you'd like to contribute but don't know what to work on, please take a look
|
||||
at the [issues tab](https://github.com/ankitects/anki/issues) of the Anki repo
|
||||
on GitHub.
|
||||
|
||||
## Larger changes
|
||||
|
||||
Before starting work on larger changes, especially ones that aren't listed on the
|
||||
issue tracker, please reach out on the forums before you begin work, so we can let
|
||||
you know whether they're likely to be accepted or not. When you spent a bunch of time
|
||||
on a PR that ends up getting rejected, it's no fun for either you or us.
|
||||
|
||||
## Refactoring
|
||||
|
||||
Please avoid PRs that focus on refactoring. Every PR has a cost to review, and a chance
|
||||
of introducing accidental regressions, and often these costs are not worth it for
|
||||
slightly more elegant code.
|
||||
|
||||
That's not to say there's no value in refactoring. But such changes are usually better done
|
||||
in a PR that happens to be working in the same area - for example, making small changes
|
||||
to the code as part of fixing a bug, or a larger refactor when introducing a new feature.
|
||||
|
||||
## Type hints
|
||||
|
||||
Most of Anki's Python code now has type hints, which improve code completion,
|
||||
|
|
|
@ -1,14 +1,14 @@
|
|||
FROM rust:1.76-alpine3.19 AS builder
|
||||
FROM rust:1.79-alpine3.20 AS builder
|
||||
|
||||
ARG ANKI_VERSION
|
||||
|
||||
RUN apk update && apk add --no-cache build-base protobuf && rm -rf /var/cache/apk/*
|
||||
RUN cargo install --git https://github.com/ankitects/anki.git \
|
||||
--tag ${ANKI_VERSION} \
|
||||
--root /anki-server \
|
||||
anki-sync-server
|
||||
--tag ${ANKI_VERSION} \
|
||||
--root /anki-server \
|
||||
anki-sync-server
|
||||
|
||||
FROM alpine:3.19.1
|
||||
FROM alpine:3.20
|
||||
|
||||
RUN adduser -D -h /home/anki anki
|
||||
|
||||
|
@ -25,8 +25,9 @@ EXPOSE ${SYNC_PORT}
|
|||
|
||||
CMD ["anki-sync-server"]
|
||||
|
||||
# TODO - consider exposing endpoint /health to check on health cause currently it will return 404 error
|
||||
# HEALTHCHECK --interval=30s --timeout=10s --start-period=5s --retries=3 \
|
||||
# CMD wget -qO- http://localhost:${SYNC_PORT} || exit 1
|
||||
# This health check will work for Anki versions 24.06.3 and newer.
|
||||
# For older versions, it may incorrectly report an unhealthy status, which should not be the case.
|
||||
HEALTHCHECK --interval=30s --timeout=10s --start-period=5s --retries=3 \
|
||||
CMD wget -qO- http://localhost:${SYNC_PORT}/health || exit 1
|
||||
|
||||
LABEL maintainer="Jean Khawand <jk@jeankhawand.com>"
|
|
@ -1 +1 @@
|
|||
Subproject commit 173f043875bb38607d7d31bfcb0eb9874b8e4f3a
|
||||
Subproject commit ababc05c46db93309ac50e7f80c10d6559314aa1
|
|
@ -61,7 +61,7 @@ deck-config-today-only = Today only
|
|||
|
||||
deck-config-learning-steps = Learning steps
|
||||
# Please don't translate `1m`, `2d`
|
||||
-deck-config-delay-hint = Delays are typically minutes (eg `1m`) or days (eg `2d`), but hours (eg `1h`) and seconds (eg `30s`) are also supported.
|
||||
-deck-config-delay-hint = Delays are typically minutes (e.g. `1m`) or days (e.g. `2d`), but hours (e.g. `1h`) and seconds (e.g. `30s`) are also supported.
|
||||
deck-config-learning-steps-tooltip =
|
||||
One or more delays, separated by spaces. The first delay will be used
|
||||
when you press the `Again` button on a new card, and is 1 minute by default.
|
||||
|
@ -100,7 +100,7 @@ deck-config-leech-threshold-tooltip =
|
|||
think of a mnemonic to help you remember it.
|
||||
# See actions-suspend-card and scheduling-tag-only for the wording
|
||||
deck-config-leech-action-tooltip =
|
||||
`Tag Only`: Add a "leech" tag to the note, and display a pop-up.
|
||||
`Tag Only`: Add a 'leech' tag to the note, and display a pop-up.
|
||||
|
||||
`Suspend Card`: In addition to tagging the note, hide the card until it is
|
||||
manually unsuspended.
|
||||
|
@ -148,8 +148,8 @@ deck-config-new-gather-priority-tooltip-2 =
|
|||
the latest-added first.
|
||||
|
||||
`Random notes`: gathers cards of randomly selected notes. When sibling burying is
|
||||
disabled, this allows all cards of a note to be seen in a session (eg. both a front->back
|
||||
and back->front card)
|
||||
disabled, this allows all cards of a note to be seen in a session (e.g. both a front→back
|
||||
and back→front card).
|
||||
|
||||
`Random cards`: gathers cards completely randomly.
|
||||
deck-config-new-gather-priority-deck = Deck
|
||||
|
@ -234,7 +234,7 @@ deck-config-stop-timer-on-answer-tooltip =
|
|||
## Auto Advance section
|
||||
|
||||
deck-config-seconds-to-show-question = Seconds to show question for
|
||||
deck-config-seconds-to-show-question-tooltip-2 = When auto advance is activated, the number of seconds to wait before revealing the answer. Set to 0 to disable.
|
||||
deck-config-seconds-to-show-question-tooltip-3 = When auto advance is activated, the number of seconds to wait before applying the question action. Set to 0 to disable.
|
||||
deck-config-seconds-to-show-answer = Seconds to show answer for
|
||||
deck-config-seconds-to-show-answer-tooltip-2 = When auto advance is activated, the number of seconds to wait before applying the answer action. Set to 0 to disable.
|
||||
deck-config-question-action-show-answer = Show Answer
|
||||
|
@ -242,8 +242,8 @@ deck-config-question-action-show-reminder = Show Reminder
|
|||
deck-config-question-action = Question action
|
||||
deck-config-question-action-tool-tip = The action to perform after the question is shown, and time has elapsed.
|
||||
deck-config-answer-action = Answer action
|
||||
deck-config-answer-action-tooltip = The action to perform on the current card before automatically advancing to the next one.
|
||||
deck-config-wait-for-audio-tooltip = Wait for audio to finish before automatically revealing answer or next question.
|
||||
deck-config-answer-action-tooltip-2 = The action to perform after the answer is shown, and time has elapsed.
|
||||
deck-config-wait-for-audio-tooltip-2 = Wait for audio to finish before automatically applying the question action or answer action.
|
||||
|
||||
## Audio section
|
||||
|
||||
|
@ -251,7 +251,7 @@ deck-config-audio-title = Audio
|
|||
deck-config-disable-autoplay = Don't play audio automatically
|
||||
deck-config-disable-autoplay-tooltip =
|
||||
When enabled, Anki will not play audio automatically.
|
||||
It can be played manually by clicking/tapping on an audio icon, or by using the replay audio action.
|
||||
It can be played manually by clicking/tapping on an audio icon, or by using the Replay action.
|
||||
deck-config-skip-question-when-replaying = Skip question when replaying answer
|
||||
deck-config-always-include-question-audio-tooltip =
|
||||
Whether the question audio should be included when the Replay action is
|
||||
|
@ -350,7 +350,7 @@ deck-config-compute-optimal-weights = Optimize FSRS parameters
|
|||
deck-config-compute-minimum-recommended-retention = Minimum recommended retention
|
||||
deck-config-optimize-button = Optimize
|
||||
deck-config-compute-button = Compute
|
||||
deck-config-ignore-before = Ignore reviews before
|
||||
deck-config-ignore-before = Ignore cards reviewed before
|
||||
deck-config-optimize-all-tip = You can optimize all presets at once by using the dropdown button next to "Save".
|
||||
deck-config-evaluate-button = Evaluate
|
||||
deck-config-desired-retention = Desired retention
|
||||
|
@ -368,7 +368,7 @@ deck-config-reschedule-cards-on-change = Reschedule cards on change
|
|||
deck-config-fsrs-tooltip =
|
||||
Affects the entire collection.
|
||||
|
||||
The Free Spaced Repetition Scheduler (FSRS) is an alternative to Anki's legacy SuperMemo 2 (SM2) scheduler.
|
||||
The Free Spaced Repetition Scheduler (FSRS) is an alternative to Anki's legacy SuperMemo 2 (SM-2) scheduler.
|
||||
By more accurately determining when you are likely to forget, it can help you remember
|
||||
more material in the same amount of time. This setting is shared by all deck presets.
|
||||
|
||||
|
@ -388,11 +388,11 @@ deck-config-historical-retention-tooltip =
|
|||
the missing reviews.
|
||||
|
||||
Your review history may be incomplete for two reasons:
|
||||
1. Because you've used the 'ignore reviews before' option.
|
||||
1. Because you're using the 'ignore cards reviewed before' option.
|
||||
2. Because you previously deleted review logs to free up space, or imported material from a different
|
||||
SRS program.
|
||||
|
||||
The latter is quite rare, so unless you've used the former option, you probably don't need to adjust
|
||||
The latter is quite rare, so unless you're using the former option, you probably don't need to adjust
|
||||
this setting.
|
||||
deck-config-weights-tooltip2 =
|
||||
FSRS parameters affect how cards are scheduled. Anki will start with default parameters. You can use
|
||||
|
@ -406,12 +406,12 @@ deck-config-reschedule-cards-on-change-tooltip =
|
|||
will be changed.
|
||||
deck-config-reschedule-cards-warning =
|
||||
Depending on your desired retention, this can result in a large number of cards becoming
|
||||
due, so is not recommended when first switching from SM2.
|
||||
due, so is not recommended when first switching from SM-2.
|
||||
|
||||
Use this option sparingly, as it will add a review entry to each of your cards, and
|
||||
increase the size of your collection.
|
||||
deck-config-ignore-before-tooltip =
|
||||
If set, reviews before the provided date will be ignored when optimizing & evaluating FSRS parameters.
|
||||
deck-config-ignore-before-tooltip-2 =
|
||||
If set, cards reviewed before the provided date will be ignored when optimizing FSRS parameters.
|
||||
This can be useful if you imported someone else's scheduling data, or have changed the way you use the answer buttons.
|
||||
deck-config-compute-optimal-weights-tooltip2 =
|
||||
When you click the Optimize button, FSRS will analyze your review history, and generate parameters that are
|
||||
|
@ -469,6 +469,12 @@ deck-config-bury-tooltip =
|
|||
|
||||
When using the V3 scheduler, interday learning cards can also be buried. Interday
|
||||
learning cards are cards with a current learning step of one or more days.
|
||||
deck-config-seconds-to-show-question-tooltip = When auto advance is activated, the number of seconds to wait before revealing the answer. Set to 0 to disable.
|
||||
deck-config-answer-action-tooltip = The action to perform on the current card before automatically advancing to the next one.
|
||||
deck-config-wait-for-audio-tooltip = Wait for audio to finish before automatically revealing answer or next question.
|
||||
deck-config-ignore-before-tooltip =
|
||||
If set, reviews before the provided date will be ignored when optimizing & evaluating FSRS parameters.
|
||||
This can be useful if you imported someone else's scheduling data, or have changed the way you use the answer buttons.
|
||||
deck-config-compute-optimal-retention-tooltip =
|
||||
This tool assumes you're starting with 0 cards, and will attempt to calculate the amount of material you'll
|
||||
be able to retain in the given time frame. The estimated retention will greatly depend on your inputs, and
|
||||
|
@ -505,3 +511,4 @@ deck-config-compute-optimal-retention-tooltip3 =
|
|||
set your desired retention to. You may wish to choose a higher desired retention, if you’re willing to trade more study
|
||||
time for a greater recall rate. Setting your desired retention lower than the minimum is not recommended, as it will
|
||||
lead to a higher workload, because of the high forgetting rate.
|
||||
deck-config-seconds-to-show-question-tooltip-2 = When auto advance is activated, the number of seconds to wait before revealing the answer. Set to 0 to disable.
|
||||
|
|
|
@ -41,6 +41,7 @@ preferences-theme-follow-system = Follow System
|
|||
preferences-theme-light = Light
|
||||
preferences-theme-dark = Dark
|
||||
preferences-v3-scheduler = V3 scheduler
|
||||
preferences-check-for-updates = Check for program updates
|
||||
preferences-ignore-accents-in-search = Ignore accents in search (slower)
|
||||
preferences-backup-explanation =
|
||||
Anki periodically backs up your collection. After backups are more than 2 days old,
|
||||
|
|
|
@ -152,7 +152,7 @@ statistics-cards-due =
|
|||
}
|
||||
statistics-backlog-checkbox = Backlog
|
||||
statistics-intervals-title = Review Intervals
|
||||
statistics-intervals-subtitle = Delays until reviews are shown again.
|
||||
statistics-intervals-subtitle = Delays until review cards are shown again.
|
||||
statistics-intervals-day-range =
|
||||
{ $cards ->
|
||||
[one] { $cards } card with a { $daysStart }~{ $daysEnd } day interval
|
||||
|
|
|
@ -1 +1 @@
|
|||
Subproject commit 1e8db8f33c0da127e67312aeb77f1c3aa901af77
|
||||
Subproject commit 8cc8e3447cca79a7a8720e118388f6383c830eee
|
|
@ -9,8 +9,8 @@
|
|||
"dev": "cd ts && vite dev",
|
||||
"build": "cd ts && vite build",
|
||||
"preview": "cd ts && vite preview",
|
||||
"svelte-check:once": "cd ts && svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --fail-on-warnings --threshold warning --compiler-warnings a11y-click-events-have-key-events:ignore,a11y-no-noninteractive-tabindex:ignore,a11y-no-static-element-interactions:ignore",
|
||||
"svelte-check": "cd ts && svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --watch --compiler-warnings a11y-click-events-have-key-events:ignore,a11y-no-noninteractive-tabindex:ignore,a11y-no-static-element-interactions:ignore",
|
||||
"svelte-check:once": "cd ts && svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --fail-on-warnings --threshold warning",
|
||||
"svelte-check": "cd ts && svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --watch",
|
||||
"vitest:once": "cd ts && vitest run",
|
||||
"vitest": "cd ts && vitest"
|
||||
},
|
||||
|
|
|
@ -402,27 +402,26 @@ message ComputeOptimalRetentionResponse {
|
|||
float optimal_retention = 1;
|
||||
}
|
||||
|
||||
message OptimalRetentionParameters {
|
||||
double recall_secs_hard = 1;
|
||||
double recall_secs_good = 2;
|
||||
double recall_secs_easy = 3;
|
||||
double forget_secs = 4;
|
||||
double learn_secs = 5;
|
||||
double first_rating_probability_again = 6;
|
||||
double first_rating_probability_hard = 7;
|
||||
double first_rating_probability_good = 8;
|
||||
double first_rating_probability_easy = 9;
|
||||
double review_rating_probability_hard = 10;
|
||||
double review_rating_probability_good = 11;
|
||||
double review_rating_probability_easy = 12;
|
||||
}
|
||||
|
||||
message GetOptimalRetentionParametersRequest {
|
||||
string search = 1;
|
||||
}
|
||||
|
||||
message GetOptimalRetentionParametersResponse {
|
||||
OptimalRetentionParameters params = 1;
|
||||
uint32 deck_size = 1;
|
||||
uint32 learn_span = 2;
|
||||
float max_cost_perday = 3;
|
||||
float max_ivl = 4;
|
||||
repeated float learn_costs = 5;
|
||||
repeated float review_costs = 6;
|
||||
repeated float first_rating_prob = 7;
|
||||
repeated float review_rating_prob = 8;
|
||||
repeated float first_rating_offsets = 9;
|
||||
repeated float first_session_lens = 10;
|
||||
float forget_rating_offset = 11;
|
||||
float forget_session_len = 12;
|
||||
float loss_aversion = 13;
|
||||
uint32 learn_limit = 14;
|
||||
uint32 review_limit = 15;
|
||||
}
|
||||
|
||||
message EvaluateWeightsRequest {
|
||||
|
|
|
@ -6,8 +6,9 @@ from __future__ import annotations
|
|||
import sys
|
||||
import time
|
||||
import traceback
|
||||
from collections.abc import Iterable, Sequence
|
||||
from threading import current_thread, main_thread
|
||||
from typing import TYPE_CHECKING, Any, Iterable, Sequence
|
||||
from typing import TYPE_CHECKING, Any
|
||||
from weakref import ref
|
||||
|
||||
from markdown import markdown
|
||||
|
|
|
@ -8,7 +8,8 @@ import os
|
|||
import pathlib
|
||||
import sys
|
||||
import traceback
|
||||
from typing import TYPE_CHECKING, Any, Callable, Union
|
||||
from collections.abc import Callable
|
||||
from typing import TYPE_CHECKING, Any, Union
|
||||
|
||||
from anki._vendor import stringcase # type: ignore
|
||||
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
from typing import Union
|
||||
|
||||
|
||||
class Backend:
|
||||
@classmethod
|
||||
def command(cls, service: int, method: int, data: bytes) -> bytes: ...
|
||||
|
|
|
@ -5,6 +5,7 @@ from __future__ import annotations
|
|||
|
||||
import pprint
|
||||
import time
|
||||
from typing import NewType
|
||||
|
||||
import anki # pylint: disable=unused-import
|
||||
import anki.collection
|
||||
|
|
|
@ -3,7 +3,8 @@
|
|||
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Any, Generator, Iterable, Literal, Sequence, Union, cast
|
||||
from collections.abc import Generator, Iterable, Sequence
|
||||
from typing import Any, Literal, Union, cast
|
||||
|
||||
from anki import (
|
||||
ankiweb_pb2,
|
||||
|
|
|
@ -4,8 +4,9 @@
|
|||
from __future__ import annotations
|
||||
|
||||
import re
|
||||
from collections.abc import Callable, Iterable, Sequence
|
||||
from re import Match
|
||||
from typing import TYPE_CHECKING, Any, Callable, Iterable, Sequence, Union
|
||||
from typing import TYPE_CHECKING, Any, Union
|
||||
|
||||
if TYPE_CHECKING:
|
||||
import anki._backend
|
||||
|
|
|
@ -4,7 +4,8 @@
|
|||
from __future__ import annotations
|
||||
|
||||
import copy
|
||||
from typing import TYPE_CHECKING, Any, Iterable, NewType, Sequence
|
||||
from collections.abc import Iterable, Sequence
|
||||
from typing import TYPE_CHECKING, Any, NewType
|
||||
|
||||
if TYPE_CHECKING:
|
||||
import anki
|
||||
|
|
|
@ -13,8 +13,9 @@ import threading
|
|||
import time
|
||||
import unicodedata
|
||||
import zipfile
|
||||
from collections.abc import Sequence
|
||||
from io import BufferedWriter
|
||||
from typing import Any, Optional, Sequence
|
||||
from typing import Any
|
||||
from zipfile import ZipFile
|
||||
|
||||
from anki import hooks
|
||||
|
@ -26,16 +27,16 @@ from anki.utils import ids2str, namedtmp, split_fields, strip_html
|
|||
|
||||
class Exporter:
|
||||
includeHTML: bool | None = None
|
||||
ext: Optional[str] = None
|
||||
includeTags: Optional[bool] = None
|
||||
includeSched: Optional[bool] = None
|
||||
includeMedia: Optional[bool] = None
|
||||
ext: str | None = None
|
||||
includeTags: bool | None = None
|
||||
includeSched: bool | None = None
|
||||
includeMedia: bool | None = None
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
col: Collection,
|
||||
did: Optional[DeckId] = None,
|
||||
cids: Optional[list[CardId]] = None,
|
||||
did: DeckId | None = None,
|
||||
cids: list[CardId] | None = None,
|
||||
) -> None:
|
||||
self.col = col.weakref()
|
||||
self.did = did
|
||||
|
|
|
@ -94,8 +94,8 @@ class ForeignCard:
|
|||
class ForeignNote:
|
||||
fields: list[str] = field(default_factory=list)
|
||||
tags: list[str] = field(default_factory=list)
|
||||
notetype: Union[str, NotetypeId] = ""
|
||||
deck: Union[str, DeckId] = ""
|
||||
notetype: str | NotetypeId = ""
|
||||
deck: str | DeckId = ""
|
||||
cards: list[ForeignCard] = field(default_factory=list)
|
||||
|
||||
|
||||
|
@ -103,7 +103,7 @@ class ForeignNote:
|
|||
class ForeignData:
|
||||
notes: list[ForeignNote] = field(default_factory=list)
|
||||
notetypes: list[ForeignNotetype] = field(default_factory=list)
|
||||
default_deck: Union[str, DeckId] = ""
|
||||
default_deck: str | DeckId = ""
|
||||
|
||||
def serialize(self) -> str:
|
||||
return json.dumps(self, cls=ForeignDataEncoder, separators=(",", ":"))
|
||||
|
|
|
@ -17,7 +17,6 @@ Notetype | Card Type
|
|||
import re
|
||||
from abc import ABC, abstractmethod
|
||||
from dataclasses import dataclass, field
|
||||
from typing import Tuple, Type
|
||||
|
||||
from anki.db import DB
|
||||
from anki.decks import DeckId
|
||||
|
@ -38,7 +37,7 @@ def serialize(db_path: str, deck_id: DeckId) -> str:
|
|||
def gather_data(db: DB, deck_id: DeckId) -> ForeignData:
|
||||
facts = gather_facts(db)
|
||||
gather_cards_into_facts(db, facts)
|
||||
used_fact_views: dict[Type[MnemoFactView], bool] = {}
|
||||
used_fact_views: dict[type[MnemoFactView], bool] = {}
|
||||
notes = [fact.foreign_note(used_fact_views) for fact in facts.values()]
|
||||
notetypes = [fact_view.foreign_notetype() for fact_view in used_fact_views]
|
||||
return ForeignData(notes, notetypes, deck_id)
|
||||
|
@ -54,7 +53,7 @@ def open_mnemosyne_db(db_path: str) -> DB:
|
|||
|
||||
class MnemoFactView(ABC):
|
||||
notetype: str
|
||||
field_keys: Tuple[str, ...]
|
||||
field_keys: tuple[str, ...]
|
||||
|
||||
@classmethod
|
||||
@abstractmethod
|
||||
|
@ -162,7 +161,7 @@ class MnemoFact:
|
|||
cards: list[MnemoCard] = field(default_factory=list)
|
||||
|
||||
def foreign_note(
|
||||
self, used_fact_views: dict[Type[MnemoFactView], bool]
|
||||
self, used_fact_views: dict[type[MnemoFactView], bool]
|
||||
) -> ForeignNote:
|
||||
fact_view = self.fact_view()
|
||||
used_fact_views[fact_view] = True
|
||||
|
@ -173,7 +172,7 @@ class MnemoFact:
|
|||
cards=self.foreign_cards(),
|
||||
)
|
||||
|
||||
def fact_view(self) -> Type[MnemoFactView]:
|
||||
def fact_view(self) -> type[MnemoFactView]:
|
||||
try:
|
||||
fact_view = self.cards[0].fact_view_id
|
||||
except IndexError as err:
|
||||
|
@ -190,7 +189,7 @@ class MnemoFact:
|
|||
|
||||
raise Exception(f"Fact {id} has unknown fact view: {fact_view}")
|
||||
|
||||
def anki_fields(self, fact_view: Type[MnemoFactView]) -> list[str]:
|
||||
def anki_fields(self, fact_view: type[MnemoFactView]) -> list[str]:
|
||||
return [munge_field(self.fields.get(k, "")) for k in fact_view.field_keys]
|
||||
|
||||
def anki_tags(self) -> list[str]:
|
||||
|
|
|
@ -14,6 +14,9 @@ modifying it.
|
|||
|
||||
from __future__ import annotations
|
||||
|
||||
from collections.abc import Callable
|
||||
from typing import Any
|
||||
|
||||
import decorator
|
||||
|
||||
# You can find the definitions in ../tools/genhooks.py
|
||||
|
@ -32,7 +35,7 @@ def runHook(hook: str, *args: Any) -> None:
|
|||
for func in hookFuncs:
|
||||
try:
|
||||
func(*args)
|
||||
except:
|
||||
except Exception:
|
||||
hookFuncs.remove(func)
|
||||
raise
|
||||
|
||||
|
@ -43,7 +46,7 @@ def runFilter(hook: str, arg: Any, *args: Any) -> Any:
|
|||
for func in hookFuncs:
|
||||
try:
|
||||
arg = func(arg, *args)
|
||||
except:
|
||||
except Exception:
|
||||
hookFuncs.remove(func)
|
||||
raise
|
||||
return arg
|
||||
|
|
|
@ -9,7 +9,8 @@ from __future__ import annotations
|
|||
|
||||
import io
|
||||
import os
|
||||
from typing import Any, Callable
|
||||
from collections.abc import Callable
|
||||
from typing import Any
|
||||
|
||||
import requests
|
||||
from requests import Response
|
||||
|
|
|
@ -1,7 +1,8 @@
|
|||
# Copyright: Ankitects Pty Ltd and contributors
|
||||
# License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
||||
|
||||
from typing import Any, Callable, Sequence, Type, Union
|
||||
from collections.abc import Callable, Sequence
|
||||
from typing import Any, Type, Union
|
||||
|
||||
import anki
|
||||
from anki.collection import Collection
|
||||
|
|
|
@ -2,10 +2,11 @@
|
|||
# License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
||||
|
||||
# pylint: disable=invalid-name
|
||||
from __future__ import annotations
|
||||
|
||||
import os
|
||||
import unicodedata
|
||||
from typing import Optional
|
||||
from typing import Any
|
||||
|
||||
from anki.cards import CardId
|
||||
from anki.collection import Collection
|
||||
|
@ -31,7 +32,7 @@ class MediaMapInvalid(Exception):
|
|||
|
||||
class Anki2Importer(Importer):
|
||||
needMapper = False
|
||||
deckPrefix: Optional[str] = None
|
||||
deckPrefix: str | None = None
|
||||
allowUpdate = True
|
||||
src: Collection
|
||||
dst: Collection
|
||||
|
@ -409,7 +410,7 @@ insert or ignore into revlog values (?,?,?,?,?,?,?,?,?)""",
|
|||
if fname.startswith("_") and not self.dst.media.have(fname):
|
||||
self._writeDstMedia(fname, self._srcMediaData(fname))
|
||||
|
||||
def _mediaData(self, fname: str, dir: Optional[str] = None) -> bytes:
|
||||
def _mediaData(self, fname: str, dir: str | None = None) -> bytes:
|
||||
if not dir:
|
||||
dir = self.src.media.dir()
|
||||
path = os.path.join(dir, fname)
|
||||
|
|
|
@ -2,12 +2,13 @@
|
|||
# License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
||||
|
||||
# pylint: disable=invalid-name
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
import os
|
||||
import unicodedata
|
||||
import zipfile
|
||||
from typing import Any, Optional
|
||||
from typing import Any
|
||||
|
||||
from anki.importing.anki2 import Anki2Importer, MediaMapInvalid
|
||||
from anki.utils import tmpfile
|
||||
|
@ -15,7 +16,7 @@ from anki.utils import tmpfile
|
|||
|
||||
class AnkiPackageImporter(Anki2Importer):
|
||||
nameToNum: dict[str, str]
|
||||
zip: Optional[zipfile.ZipFile]
|
||||
zip: zipfile.ZipFile | None
|
||||
|
||||
def run(self) -> None: # type: ignore
|
||||
# extract the deck from the zip file
|
||||
|
|
|
@ -2,8 +2,9 @@
|
|||
# License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
||||
|
||||
# pylint: disable=invalid-name
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Any, Optional
|
||||
from typing import Any
|
||||
|
||||
from anki.collection import Collection
|
||||
from anki.utils import max_id
|
||||
|
@ -15,7 +16,7 @@ from anki.utils import max_id
|
|||
class Importer:
|
||||
needMapper = False
|
||||
needDelimiter = False
|
||||
dst: Optional[Collection]
|
||||
dst: Collection | None
|
||||
|
||||
def __init__(self, col: Collection, file: str) -> None:
|
||||
self.file = file
|
||||
|
|
|
@ -7,7 +7,7 @@ from __future__ import annotations
|
|||
|
||||
import csv
|
||||
import re
|
||||
from typing import Any, Optional, TextIO
|
||||
from typing import Any, TextIO
|
||||
|
||||
from anki.collection import Collection
|
||||
from anki.importing.noteimp import ForeignNote, NoteImporter
|
||||
|
@ -20,12 +20,12 @@ class TextImporter(NoteImporter):
|
|||
def __init__(self, col: Collection, file: str) -> None:
|
||||
NoteImporter.__init__(self, col, file)
|
||||
self.lines = None
|
||||
self.fileobj: Optional[TextIO] = None
|
||||
self.delimiter: Optional[str] = None
|
||||
self.fileobj: TextIO | None = None
|
||||
self.delimiter: str | None = None
|
||||
self.tagsToAdd: list[str] = []
|
||||
self.numFields = 0
|
||||
self.dialect: Optional[Any]
|
||||
self.data: Optional[str | list[str]]
|
||||
self.dialect: Any | None
|
||||
self.data: str | list[str] | None
|
||||
|
||||
def foreignNotes(self) -> list[ForeignNote]:
|
||||
self.open()
|
||||
|
@ -99,15 +99,15 @@ class TextImporter(NoteImporter):
|
|||
if not self.delimiter:
|
||||
try:
|
||||
self.dialect = sniffer.sniff("\n".join(self.data[:10]), self.patterns)
|
||||
except:
|
||||
except Exception:
|
||||
try:
|
||||
self.dialect = sniffer.sniff(self.data[0], self.patterns)
|
||||
except:
|
||||
except Exception:
|
||||
pass
|
||||
if self.dialect:
|
||||
try:
|
||||
reader = csv.reader(self.data, self.dialect, doublequote=True)
|
||||
except:
|
||||
except Exception:
|
||||
err()
|
||||
else:
|
||||
if not self.delimiter:
|
||||
|
@ -126,7 +126,7 @@ class TextImporter(NoteImporter):
|
|||
if row:
|
||||
self.numFields = len(row)
|
||||
break
|
||||
except:
|
||||
except Exception:
|
||||
err()
|
||||
self.initMapping()
|
||||
|
||||
|
|
|
@ -7,7 +7,7 @@ from __future__ import annotations
|
|||
|
||||
import html
|
||||
import unicodedata
|
||||
from typing import Optional, Union
|
||||
from typing import Union
|
||||
|
||||
from anki.collection import Collection
|
||||
from anki.config import Config
|
||||
|
@ -76,8 +76,8 @@ class NoteImporter(Importer):
|
|||
needDelimiter = False
|
||||
allowHTML = False
|
||||
importMode = UPDATE_MODE
|
||||
mapping: Optional[list[str]]
|
||||
tagModified: Optional[str]
|
||||
mapping: list[str] | None
|
||||
tagModified: str | None
|
||||
|
||||
def __init__(self, col: Collection, file: str) -> None:
|
||||
Importer.__init__(self, col, file)
|
||||
|
@ -268,7 +268,7 @@ class NoteImporter(Importer):
|
|||
|
||||
def updateData(
|
||||
self, n: ForeignNote, id: NoteId, sflds: list[str]
|
||||
) -> Optional[Updates]:
|
||||
) -> Updates | None:
|
||||
self._ids.append(id)
|
||||
self.processFields(n, sflds)
|
||||
if self._tagsMapped:
|
||||
|
@ -316,9 +316,7 @@ where id = ? and flds != ?""",
|
|||
changes2 = self.col.db.scalar("select total_changes()")
|
||||
self.updateCount = changes2 - changes
|
||||
|
||||
def processFields(
|
||||
self, note: ForeignNote, fields: Optional[list[str]] = None
|
||||
) -> None:
|
||||
def processFields(self, note: ForeignNote, fields: list[str] | None = None) -> None:
|
||||
if not fields:
|
||||
fields = [""] * len(self.model["flds"])
|
||||
for c, f in enumerate(self.mapping):
|
||||
|
|
|
@ -3,13 +3,13 @@
|
|||
# pytype: disable=attribute-error
|
||||
# type: ignore
|
||||
# pylint: disable=C
|
||||
from __future__ import annotations
|
||||
|
||||
import re
|
||||
import sys
|
||||
import time
|
||||
import unicodedata
|
||||
from string import capwords
|
||||
from typing import Optional, Union
|
||||
from xml.dom import minidom
|
||||
from xml.dom.minidom import Element, Text
|
||||
|
||||
|
@ -329,7 +329,7 @@ class SupermemoXmlImporter(NoteImporter):
|
|||
self.logger("Load done.")
|
||||
|
||||
# PARSE
|
||||
def parse(self, node: Optional[Union[Text, Element]] = None) -> None:
|
||||
def parse(self, node: Text | Element | None = None) -> None:
|
||||
"Parse method - parses document elements"
|
||||
|
||||
if node is None and self.xmldoc is not None:
|
||||
|
|
|
@ -180,9 +180,16 @@ def set_lang(lang: str) -> None:
|
|||
tr_legacyglobal.backend = weakref.ref(current_i18n)
|
||||
|
||||
|
||||
def get_def_lang(lang: str | None = None) -> tuple[int, str]:
|
||||
"""Return lang converted to name used on disk and its index, defaulting to system language
|
||||
def get_def_lang(user_lang: str | None = None) -> tuple[int, str]:
|
||||
"""Return user_lang converted to name used on disk and its index, defaulting to system language
|
||||
or English if not available."""
|
||||
|
||||
def get_index_of_language(wanted_locale: str) -> int | None:
|
||||
for i, (_, locale_) in enumerate(langs):
|
||||
if locale_ == wanted_locale:
|
||||
return i
|
||||
return None
|
||||
|
||||
try:
|
||||
# getdefaultlocale() is deprecated since Python 3.11, but we need to keep using it as getlocale() behaves differently: https://bugs.python.org/issue38805
|
||||
with warnings.catch_warnings():
|
||||
|
@ -192,28 +199,30 @@ def get_def_lang(lang: str | None = None) -> tuple[int, str]:
|
|||
# this will return a different format on Windows (e.g. Italian_Italy), resulting in us falling back to en_US
|
||||
# further below
|
||||
(sys_lang, enc) = locale.getlocale()
|
||||
except:
|
||||
except Exception:
|
||||
# fails on osx
|
||||
sys_lang = "en_US"
|
||||
user_lang = lang
|
||||
if user_lang in compatMap:
|
||||
user_lang = compatMap[user_lang]
|
||||
|
||||
idx = None
|
||||
lang = None
|
||||
en_idx = None
|
||||
for preferred_lang in (user_lang, sys_lang):
|
||||
for lang_idx, (name, code) in enumerate(langs):
|
||||
if code == "en_US":
|
||||
en_idx = lang_idx
|
||||
if code == preferred_lang:
|
||||
idx = lang_idx
|
||||
idx = get_index_of_language(preferred_lang)
|
||||
is_language_supported = idx is not None
|
||||
if is_language_supported:
|
||||
assert preferred_lang is not None
|
||||
lang = preferred_lang
|
||||
if idx is not None:
|
||||
break
|
||||
# if the specified language and the system language aren't available, revert to english
|
||||
if idx is None:
|
||||
idx = en_idx
|
||||
is_preferred_language_supported = idx is not None
|
||||
if not is_preferred_language_supported:
|
||||
lang = "en_US"
|
||||
idx = get_index_of_language(lang)
|
||||
is_english_supported = idx is not None
|
||||
if not is_english_supported:
|
||||
raise AssertionError("English is supposed to be a supported language.")
|
||||
assert idx is not None and lang is not None
|
||||
return (idx, lang)
|
||||
|
||||
|
||||
|
|
|
@ -170,7 +170,7 @@ def _err_msg(col: anki.collection.Collection, type: str, texpath: str) -> str:
|
|||
if not log:
|
||||
raise Exception()
|
||||
msg += f"<small><pre>{html.escape(log)}</pre></small>"
|
||||
except:
|
||||
except Exception:
|
||||
msg += col.tr.media_have_you_installed_latex_and_dvipngdvisvgm()
|
||||
return msg
|
||||
|
||||
|
|
|
@ -8,7 +8,7 @@ import pprint
|
|||
import re
|
||||
import sys
|
||||
import time
|
||||
from typing import Callable, Sequence
|
||||
from collections.abc import Callable, Sequence
|
||||
|
||||
from anki import media_pb2
|
||||
from anki._legacy import DeprecatedNamesMixin, deprecated_keywords
|
||||
|
|
|
@ -7,7 +7,8 @@ import copy
|
|||
import pprint
|
||||
import sys
|
||||
import time
|
||||
from typing import Any, NewType, Sequence, Union
|
||||
from collections.abc import Sequence
|
||||
from typing import Any, NewType, Union
|
||||
|
||||
import anki # pylint: disable=unused-import
|
||||
import anki.collection
|
||||
|
@ -312,7 +313,7 @@ class ModelManager(DeprecatedNamesMixin):
|
|||
def rename_field(
|
||||
self, notetype: NotetypeDict, field: FieldDict, new_name: str
|
||||
) -> None:
|
||||
if not field in notetype["flds"]:
|
||||
if field not in notetype["flds"]:
|
||||
raise Exception("invalid field")
|
||||
field["name"] = new_name
|
||||
|
||||
|
@ -388,8 +389,8 @@ and notes.mid = ? and cards.ord = ?""",
|
|||
|
||||
To get defaults, use
|
||||
|
||||
input = ChangeNotetypeRequest()
|
||||
input.ParseFromString(col.models.change_notetype_info(...))
|
||||
info = col.models.change_notetype_info(...)
|
||||
input = info.input
|
||||
input.note_ids.extend([...])
|
||||
|
||||
The new_fields and new_templates lists are relative to the new notetype's
|
||||
|
|
|
@ -4,7 +4,8 @@
|
|||
from __future__ import annotations
|
||||
|
||||
import copy
|
||||
from typing import NewType, Sequence
|
||||
from collections.abc import Sequence
|
||||
from typing import NewType
|
||||
|
||||
import anki # pylint: disable=unused-import
|
||||
import anki.cards
|
||||
|
@ -94,21 +95,28 @@ class Note(DeprecatedNamesMixin):
|
|||
self,
|
||||
ord: int = 0,
|
||||
*,
|
||||
custom_note_type: NotetypeDict = None,
|
||||
custom_template: TemplateDict = None,
|
||||
custom_note_type: NotetypeDict | None = None,
|
||||
custom_template: TemplateDict | None = None,
|
||||
fill_empty: bool = False,
|
||||
) -> anki.cards.Card:
|
||||
card = anki.cards.Card(self.col)
|
||||
card.ord = ord
|
||||
card.did = anki.decks.DEFAULT_DECK_ID
|
||||
|
||||
model = custom_note_type or self.note_type()
|
||||
template = copy.copy(
|
||||
custom_template
|
||||
or (
|
||||
model["tmpls"][ord] if model["type"] == MODEL_STD else model["tmpls"][0]
|
||||
)
|
||||
)
|
||||
if custom_note_type is None:
|
||||
model = self.note_type()
|
||||
else:
|
||||
model = custom_note_type
|
||||
if model is None:
|
||||
raise NotImplementedError
|
||||
|
||||
if custom_template is not None:
|
||||
template = custom_template
|
||||
elif model["type"] == MODEL_STD:
|
||||
template = model["tmpls"][ord]
|
||||
else:
|
||||
template = model["tmpls"][0]
|
||||
template = copy.copy(template)
|
||||
# may differ in cloze case
|
||||
template["ord"] = card.ord
|
||||
|
||||
|
@ -171,10 +179,7 @@ class Note(DeprecatedNamesMixin):
|
|||
return self.col.tags.in_list(tag, self.tags)
|
||||
|
||||
def remove_tag(self, tag: str) -> None:
|
||||
rem = []
|
||||
for tag_ in self.tags:
|
||||
if tag_.lower() == tag.lower():
|
||||
rem.append(tag_)
|
||||
rem = [tag_ for tag_ in self.tags if tag_.lower() == tag.lower()]
|
||||
for tag_ in rem:
|
||||
self.tags.remove(tag_)
|
||||
|
||||
|
|
|
@ -22,7 +22,8 @@ ScheduleCardsAsNewDefaults = scheduler_pb2.ScheduleCardsAsNewDefaultsResponse
|
|||
FilteredDeckForUpdate = decks_pb2.FilteredDeckForUpdate
|
||||
RepositionDefaults = scheduler_pb2.RepositionDefaultsResponse
|
||||
|
||||
from typing import Sequence, overload
|
||||
from collections.abc import Sequence
|
||||
from typing import overload
|
||||
|
||||
from anki import config_pb2
|
||||
from anki.cards import CardId
|
||||
|
|
|
@ -5,8 +5,6 @@
|
|||
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Optional
|
||||
|
||||
from anki._legacy import deprecated
|
||||
from anki.cards import Card, CardId
|
||||
from anki.consts import (
|
||||
|
@ -54,7 +52,7 @@ class SchedulerBaseWithLegacy(SchedulerBase):
|
|||
print("_nextDueMsg() is obsolete")
|
||||
return ""
|
||||
|
||||
def rebuildDyn(self, did: Optional[DeckId] = None) -> Optional[int]:
|
||||
def rebuildDyn(self, did: DeckId | None = None) -> int | None:
|
||||
did = did or self.col.decks.selected()
|
||||
count = self.rebuild_filtered_deck(did).count or None
|
||||
if not count:
|
||||
|
@ -63,7 +61,7 @@ class SchedulerBaseWithLegacy(SchedulerBase):
|
|||
self.col.decks.select(did)
|
||||
return count
|
||||
|
||||
def emptyDyn(self, did: Optional[DeckId], lim: Optional[str] = None) -> None:
|
||||
def emptyDyn(self, did: DeckId | None, lim: str | None = None) -> None:
|
||||
if lim is None:
|
||||
self.empty_filtered_deck(did)
|
||||
return
|
||||
|
|
|
@ -14,7 +14,8 @@ as '2' internally.
|
|||
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Literal, Optional, Sequence
|
||||
from collections.abc import Sequence
|
||||
from typing import Any, Literal
|
||||
|
||||
from anki import frontend_pb2, scheduler_pb2
|
||||
from anki._legacy import deprecated
|
||||
|
@ -109,7 +110,7 @@ class Scheduler(SchedulerBaseWithLegacy):
|
|||
# backend automatically resets queues as operations are performed
|
||||
pass
|
||||
|
||||
def getCard(self) -> Optional[Card]:
|
||||
def getCard(self) -> Card | None:
|
||||
"""Fetch the next card from the queue. None if finished."""
|
||||
try:
|
||||
queued_card = self.get_queued_cards().cards[0]
|
||||
|
@ -125,7 +126,7 @@ class Scheduler(SchedulerBaseWithLegacy):
|
|||
"Don't use this, it is a stop-gap until this code is refactored."
|
||||
return not self.get_queued_cards().cards
|
||||
|
||||
def counts(self, card: Optional[Card] = None) -> tuple[int, int, int]:
|
||||
def counts(self, card: Card | None = None) -> tuple[int, int, int]:
|
||||
info = self.get_queued_cards()
|
||||
return (info.new_count, info.learning_count, info.review_count)
|
||||
|
||||
|
|
|
@ -8,7 +8,8 @@ from __future__ import annotations
|
|||
import json
|
||||
import random
|
||||
import time
|
||||
from typing import Sequence
|
||||
from collections.abc import Sequence
|
||||
from typing import Any
|
||||
|
||||
import anki.cards
|
||||
import anki.collection
|
||||
|
@ -721,7 +722,7 @@ select count(), avg(ivl), max(ivl) from cards where did in %s and queue = {QUEUE
|
|||
tot = bad + good
|
||||
try:
|
||||
pct = good / float(tot) * 100
|
||||
except:
|
||||
except Exception:
|
||||
pct = 0
|
||||
i.append(
|
||||
"Correct: <b>%(pct)0.2f%%</b><br>(%(good)d of %(tot)d)"
|
||||
|
@ -973,7 +974,7 @@ from cards where did in %s"""
|
|||
else:
|
||||
conf["legend"] = {"container": "#%sLegend" % id, "noColumns": 10}
|
||||
conf["series"] = dict(stack=True)
|
||||
if not "yaxis" in conf:
|
||||
if "yaxis" not in conf:
|
||||
conf["yaxis"] = {}
|
||||
conf["yaxis"]["labelWidth"] = 40
|
||||
if "xaxis" not in conf:
|
||||
|
|
|
@ -3,7 +3,8 @@
|
|||
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import TYPE_CHECKING, Any, Callable
|
||||
from collections.abc import Callable
|
||||
from typing import TYPE_CHECKING, Any
|
||||
|
||||
import anki.collection
|
||||
import anki.models
|
||||
|
|
|
@ -13,7 +13,8 @@ from __future__ import annotations
|
|||
|
||||
import pprint
|
||||
import re
|
||||
from typing import Collection, Match, Sequence
|
||||
from collections.abc import Collection, Sequence
|
||||
from typing import Match
|
||||
|
||||
import anki # pylint: disable=unused-import
|
||||
import anki.collection
|
||||
|
|
|
@ -28,8 +28,9 @@ template_legacy.py file, using the legacy addHook() system.
|
|||
|
||||
from __future__ import annotations
|
||||
|
||||
from collections.abc import Sequence
|
||||
from dataclasses import dataclass
|
||||
from typing import Any, Sequence, Union
|
||||
from typing import Any, Union
|
||||
|
||||
import anki
|
||||
import anki.cards
|
||||
|
|
|
@ -13,9 +13,10 @@ import subprocess
|
|||
import sys
|
||||
import tempfile
|
||||
import time
|
||||
from collections.abc import Callable, Iterable, Iterator
|
||||
from contextlib import contextmanager
|
||||
from hashlib import sha1
|
||||
from typing import TYPE_CHECKING, Any, Callable, Iterable, Iterator
|
||||
from typing import TYPE_CHECKING, Any
|
||||
|
||||
from anki._legacy import DeprecatedNamesMixinForModule
|
||||
from anki.dbproxy import DBProxy
|
||||
|
@ -28,7 +29,7 @@ try:
|
|||
|
||||
to_json_bytes: Callable[[Any], bytes] = orjson.dumps
|
||||
from_json_bytes = orjson.loads
|
||||
except:
|
||||
except Exception:
|
||||
print("orjson is missing; DB operations will be slower")
|
||||
|
||||
def to_json_bytes(obj: Any) -> bytes:
|
||||
|
@ -214,7 +215,7 @@ def call(argv: list[str], wait: bool = True, **kwargs: Any) -> int:
|
|||
info = subprocess.STARTUPINFO() # type: ignore
|
||||
try:
|
||||
info.dwFlags |= subprocess.STARTF_USESHOWWINDOW # type: ignore
|
||||
except:
|
||||
except Exception:
|
||||
# pylint: disable=no-member
|
||||
info.dwFlags |= subprocess._subprocess.STARTF_USESHOWWINDOW # type: ignore
|
||||
else:
|
||||
|
@ -285,7 +286,7 @@ def plat_desc() -> str:
|
|||
else:
|
||||
theos = system
|
||||
break
|
||||
except:
|
||||
except Exception:
|
||||
continue
|
||||
return theos
|
||||
|
||||
|
|
|
@ -26,7 +26,7 @@ def clear_tempfile(tf):
|
|||
try:
|
||||
tf.close()
|
||||
os.unlink(tf.name)
|
||||
except:
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
|
||||
|
|
|
@ -4,7 +4,8 @@
|
|||
import copy
|
||||
import os
|
||||
import time
|
||||
from typing import Callable, Dict
|
||||
from collections.abc import Callable
|
||||
from typing import Dict
|
||||
|
||||
import pytest
|
||||
|
||||
|
@ -385,7 +386,7 @@ def test_reviews():
|
|||
assert "leech" in c.note().tags
|
||||
|
||||
|
||||
def review_limits_setup() -> tuple[anki.collection.Collection, Dict]:
|
||||
def review_limits_setup() -> tuple[anki.collection.Collection, dict]:
|
||||
col = getEmptyCol()
|
||||
|
||||
parent = col.decks.get(col.decks.id("parent"))
|
||||
|
|
|
@ -137,7 +137,9 @@ prefix = """\
|
|||
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Any, Callable, Sequence
|
||||
from collections.abc import Callable, Sequence
|
||||
from typing import Any
|
||||
|
||||
import anki
|
||||
import anki.hooks
|
||||
from anki.cards import Card
|
||||
|
|
|
@ -5,12 +5,13 @@
|
|||
Code for generating hooks.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import os
|
||||
import subprocess
|
||||
import sys
|
||||
from dataclasses import dataclass
|
||||
from operator import attrgetter
|
||||
from typing import Optional
|
||||
|
||||
sys.path.append("pylib/anki/_vendor")
|
||||
|
||||
|
@ -23,19 +24,19 @@ class Hook:
|
|||
name: str
|
||||
# string of the typed arguments passed to the callback, eg
|
||||
# ["kind: str", "val: int"]
|
||||
args: list[str] = None
|
||||
args: list[str] | None = None
|
||||
# string of the return type. if set, hook is a filter.
|
||||
return_type: Optional[str] = None
|
||||
return_type: str | None = None
|
||||
# if add-ons may be relying on the legacy hook name, add it here
|
||||
legacy_hook: Optional[str] = None
|
||||
legacy_hook: str | None = None
|
||||
# if legacy hook takes no arguments but the new hook does, set this
|
||||
legacy_no_args: bool = False
|
||||
# if the hook replaces a deprecated one, add its name here
|
||||
replaces: Optional[str] = None
|
||||
replaces: str | None = None
|
||||
# arguments that the hook being replaced took
|
||||
replaced_hook_args: Optional[list[str]] = None
|
||||
replaced_hook_args: list[str] | None = None
|
||||
# docstring to add to hook class
|
||||
doc: Optional[str] = None
|
||||
doc: str | None = None
|
||||
|
||||
def callable(self) -> str:
|
||||
"Convert args into a Callable."
|
||||
|
@ -47,7 +48,7 @@ class Hook:
|
|||
types_str = ", ".join(types)
|
||||
return f"Callable[[{types_str}], {self.return_type or 'None'}]"
|
||||
|
||||
def arg_names(self, args: Optional[list[str]]) -> list[str]:
|
||||
def arg_names(self, args: list[str] | None) -> list[str]:
|
||||
names = []
|
||||
for arg in args or []:
|
||||
if not arg:
|
||||
|
@ -126,7 +127,7 @@ class {self.classname()}:
|
|||
for hook in self._hooks:
|
||||
try:
|
||||
hook({", ".join(arg_names)})
|
||||
except:
|
||||
except Exception:
|
||||
# if the hook fails, remove it
|
||||
self._hooks.remove(hook)
|
||||
raise
|
||||
|
@ -162,7 +163,7 @@ class {self.classname()}:
|
|||
for filter in self._hooks:
|
||||
try:
|
||||
{arg_names[0]} = filter({", ".join(arg_names)})
|
||||
except:
|
||||
except Exception:
|
||||
# if the hook fails, remove it
|
||||
self._hooks.remove(filter)
|
||||
raise
|
||||
|
|
3
pyproject.toml
Normal file
3
pyproject.toml
Normal file
|
@ -0,0 +1,3 @@
|
|||
[tool.black]
|
||||
target-version = ["py39", "py310", "py311", "py312"]
|
||||
extend-exclude = "qt/bundle"
|
|
@ -1,9 +1,5 @@
|
|||
[settings]
|
||||
ensure_newline_before_comments=true
|
||||
force_grid_wrap=0
|
||||
include_trailing_comma=True
|
||||
py_version=39
|
||||
profile=black
|
||||
known_first_party=anki,aqt
|
||||
line_length=88
|
||||
multi_line_output=3
|
||||
skip=aqt/forms,hooks_gen.py
|
||||
use_parentheses=True
|
||||
extend_skip=aqt/forms,hooks_gen.py
|
||||
|
|
|
@ -5,6 +5,8 @@ from __future__ import annotations
|
|||
|
||||
import logging
|
||||
import sys
|
||||
from collections.abc import Callable
|
||||
from typing import TYPE_CHECKING, Any, Union, cast
|
||||
|
||||
try:
|
||||
import pip_system_certs.wrapt_requests
|
||||
|
@ -50,7 +52,6 @@ import os
|
|||
import tempfile
|
||||
import traceback
|
||||
from pathlib import Path
|
||||
from typing import TYPE_CHECKING, Any, Callable, Optional, cast
|
||||
|
||||
import anki.lang
|
||||
from anki._backend import RustBackend
|
||||
|
@ -61,6 +62,7 @@ from anki.utils import checksum, is_lin, is_mac
|
|||
from aqt import gui_hooks
|
||||
from aqt.log import setup_logging
|
||||
from aqt.qt import *
|
||||
from aqt.qt import sip
|
||||
from aqt.utils import TR, tr
|
||||
|
||||
if TYPE_CHECKING:
|
||||
|
@ -92,8 +94,8 @@ appHelpSite = HELP_SITE
|
|||
from aqt.main import AnkiQt # isort:skip
|
||||
from aqt.profiles import ProfileManager, VideoDriver # isort:skip
|
||||
|
||||
profiler: Optional[cProfile.Profile] = None
|
||||
mw: Optional[AnkiQt] = None # set on init
|
||||
profiler: cProfile.Profile | None = None
|
||||
mw: AnkiQt | None = None # set on init
|
||||
|
||||
import aqt.forms
|
||||
|
||||
|
@ -154,7 +156,7 @@ class DialogManager:
|
|||
def allClosed(self) -> bool:
|
||||
return not any(x[1] for x in self._dialogs.values())
|
||||
|
||||
def closeAll(self, onsuccess: Callable[[], None]) -> Optional[bool]:
|
||||
def closeAll(self, onsuccess: Callable[[], None]) -> bool | None:
|
||||
# can we close immediately?
|
||||
if self.allClosed():
|
||||
onsuccess()
|
||||
|
@ -181,7 +183,7 @@ class DialogManager:
|
|||
return True
|
||||
|
||||
def register_dialog(
|
||||
self, name: str, creator: Union[Callable, type], instance: Optional[Any] = None
|
||||
self, name: str, creator: Callable | type, instance: Any | None = None
|
||||
) -> None:
|
||||
"""Allows add-ons to register a custom dialog to be managed by Anki's dialog
|
||||
manager, which ensures that only one copy of the window is open at once,
|
||||
|
@ -219,19 +221,19 @@ dialogs = DialogManager()
|
|||
|
||||
# A reference to the Qt translator needs to be held to prevent it from
|
||||
# being immediately deallocated.
|
||||
_qtrans: Optional[QTranslator] = None
|
||||
_qtrans: QTranslator | None = None
|
||||
|
||||
|
||||
def setupLangAndBackend(
|
||||
pm: ProfileManager,
|
||||
app: QApplication,
|
||||
force: Optional[str] = None,
|
||||
force: str | None = None,
|
||||
firstTime: bool = False,
|
||||
) -> RustBackend:
|
||||
global _qtrans
|
||||
try:
|
||||
locale.setlocale(locale.LC_ALL, "")
|
||||
except:
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
# add _ and ngettext globals used by legacy code
|
||||
|
@ -288,7 +290,7 @@ def setupLangAndBackend(
|
|||
class NativeEventFilter(QAbstractNativeEventFilter):
|
||||
def nativeEventFilter(
|
||||
self, eventType: Any, message: Any
|
||||
) -> tuple[bool, Optional[sip.voidptr]]:
|
||||
) -> tuple[bool, sip.voidptr | None]:
|
||||
if eventType == "windows_generic_MSG":
|
||||
import ctypes
|
||||
|
||||
|
@ -563,7 +565,7 @@ def run() -> None:
|
|||
)
|
||||
|
||||
|
||||
def _run(argv: Optional[list[str]] = None, exec: bool = True) -> Optional[AnkiApp]:
|
||||
def _run(argv: list[str] | None = None, exec: bool = True) -> AnkiApp | None:
|
||||
"""Start AnkiQt application or reuse an existing instance if one exists.
|
||||
|
||||
If the function is invoked with exec=False, the AnkiQt will not enter
|
||||
|
@ -629,7 +631,7 @@ def _run(argv: Optional[list[str]] = None, exec: bool = True) -> Optional[AnkiAp
|
|||
pmLoadResult = pm.setupMeta()
|
||||
|
||||
Collection.initialize_backend_logging()
|
||||
except:
|
||||
except Exception:
|
||||
# will handle below
|
||||
traceback.print_exc()
|
||||
pm = None
|
||||
|
@ -671,11 +673,6 @@ def _run(argv: Optional[list[str]] = None, exec: bool = True) -> Optional[AnkiAp
|
|||
# we've signaled the primary instance, so we should close
|
||||
return None
|
||||
|
||||
setup_logging(
|
||||
pm.addon_logs(),
|
||||
level=logging.DEBUG if int(os.getenv("ANKIDEV", "0")) else logging.INFO,
|
||||
)
|
||||
|
||||
if not pm:
|
||||
if i18n_setup:
|
||||
QMessageBox.critical(
|
||||
|
@ -687,6 +684,11 @@ def _run(argv: Optional[list[str]] = None, exec: bool = True) -> Optional[AnkiAp
|
|||
QMessageBox.critical(None, "Startup Failed", "Unable to create data folder")
|
||||
return None
|
||||
|
||||
setup_logging(
|
||||
pm.addon_logs(),
|
||||
level=logging.DEBUG if int(os.getenv("ANKIDEV", "0")) else logging.INFO,
|
||||
)
|
||||
|
||||
# disable icons on mac; this must be done before window created
|
||||
if is_mac:
|
||||
app.setAttribute(Qt.ApplicationAttribute.AA_DontShowIconsInMenus)
|
||||
|
@ -719,7 +721,7 @@ def _run(argv: Optional[list[str]] = None, exec: bool = True) -> Optional[AnkiAp
|
|||
# we must have a usable temp dir
|
||||
try:
|
||||
tempfile.gettempdir()
|
||||
except:
|
||||
except Exception:
|
||||
QMessageBox.critical(
|
||||
None,
|
||||
tr.qt_misc_error(),
|
||||
|
|
|
@ -5,8 +5,8 @@ from __future__ import annotations
|
|||
|
||||
import os
|
||||
import sys
|
||||
from collections.abc import Callable
|
||||
from ctypes import CDLL, CFUNCTYPE, c_bool, c_char_p
|
||||
from typing import Callable
|
||||
|
||||
import aqt
|
||||
import aqt.utils
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
# License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
||||
|
||||
import platform
|
||||
from collections.abc import Callable
|
||||
|
||||
import aqt.forms
|
||||
from anki.lang import without_unicode_isolation
|
||||
|
@ -86,6 +87,7 @@ def show(mw: aqt.AnkiQt) -> QDialog:
|
|||
"Christian Krause",
|
||||
"Christian Rusche",
|
||||
"Dave Druelinger",
|
||||
"David Culley",
|
||||
"David Smith",
|
||||
"Dmitry Mikheev",
|
||||
"Dotan Cohen",
|
||||
|
@ -194,6 +196,8 @@ def show(mw: aqt.AnkiQt) -> QDialog:
|
|||
"Marko Sisovic",
|
||||
"Lucas Scharenbroch",
|
||||
"Antoine Q.",
|
||||
"Ian Samir Yep Manzano",
|
||||
"Asuka Minato",
|
||||
)
|
||||
)
|
||||
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Callable, Optional
|
||||
from collections.abc import Callable
|
||||
|
||||
import aqt.editor
|
||||
import aqt.forms
|
||||
|
@ -54,7 +54,7 @@ class AddCards(QMainWindow):
|
|||
self.setupButtons()
|
||||
self.col.add_image_occlusion_notetype()
|
||||
self.history: list[NoteId] = []
|
||||
self._last_added_note: Optional[Note] = None
|
||||
self._last_added_note: Note | None = None
|
||||
gui_hooks.operation_did_execute.append(self.on_operation_did_execute)
|
||||
restoreGeom(self, "add")
|
||||
gui_hooks.add_cards_did_init(self)
|
||||
|
@ -178,7 +178,7 @@ class AddCards(QMainWindow):
|
|||
break
|
||||
# copy non-empty old fields
|
||||
if (
|
||||
not old_field_value in copied_field_names
|
||||
old_field_value not in copied_field_names
|
||||
and old_note.fields[old_idx]
|
||||
):
|
||||
new_note.fields[new_idx] = old_note.fields[old_idx]
|
||||
|
@ -195,7 +195,7 @@ class AddCards(QMainWindow):
|
|||
self, old_note.note_type(), new_note.note_type()
|
||||
)
|
||||
|
||||
def _load_new_note(self, sticky_fields_from: Optional[Note] = None) -> None:
|
||||
def _load_new_note(self, sticky_fields_from: Note | None = None) -> None:
|
||||
note = self._new_note()
|
||||
if old_note := sticky_fields_from:
|
||||
flds = note.note_type()["flds"]
|
||||
|
@ -209,7 +209,7 @@ class AddCards(QMainWindow):
|
|||
self.setAndFocusNote(note)
|
||||
|
||||
def on_operation_did_execute(
|
||||
self, changes: OpChanges, handler: Optional[object]
|
||||
self, changes: OpChanges, handler: object | None
|
||||
) -> None:
|
||||
if (changes.notetype or changes.deck) and handler is not self.editor:
|
||||
self.on_notetype_change(
|
||||
|
|
|
@ -9,13 +9,16 @@ import json
|
|||
import logging
|
||||
import os
|
||||
import re
|
||||
import sys
|
||||
import traceback
|
||||
import zipfile
|
||||
from collections import defaultdict
|
||||
from collections.abc import Callable, Iterable, Sequence
|
||||
from concurrent.futures import Future
|
||||
from dataclasses import dataclass
|
||||
from datetime import datetime
|
||||
from pathlib import Path
|
||||
from typing import IO, Any, Callable, Iterable, Sequence, Union
|
||||
from typing import IO, Any, Union
|
||||
from urllib.parse import parse_qs, urlparse
|
||||
from zipfile import ZipFile
|
||||
|
||||
|
@ -247,7 +250,7 @@ class AddonManager:
|
|||
__import__(addon.dir_name)
|
||||
except AbortAddonImport:
|
||||
pass
|
||||
except:
|
||||
except Exception:
|
||||
name = html.escape(addon.human_name())
|
||||
page = addon.page()
|
||||
if page:
|
||||
|
@ -340,7 +343,7 @@ class AddonManager:
|
|||
except json.JSONDecodeError as e:
|
||||
print(f"json error in add-on {module}:\n{e}")
|
||||
return dict()
|
||||
except:
|
||||
except Exception:
|
||||
# missing meta file, etc
|
||||
return dict()
|
||||
|
||||
|
@ -643,7 +646,7 @@ class AddonManager:
|
|||
try:
|
||||
with open(path, encoding="utf8") as f:
|
||||
return json.load(f)
|
||||
except:
|
||||
except Exception:
|
||||
return None
|
||||
|
||||
def set_config_help_action(self, module: str, action: Callable[[], str]) -> None:
|
||||
|
@ -1083,9 +1086,11 @@ def download_addon(client: HttpClient, id: int) -> DownloadOk | DownloadError:
|
|||
|
||||
data = client.stream_content(resp)
|
||||
|
||||
fname = re.match(
|
||||
match = re.match(
|
||||
"attachment; filename=(.+)", resp.headers["content-disposition"]
|
||||
).group(1)
|
||||
)
|
||||
assert match is not None
|
||||
fname = match.group(1)
|
||||
|
||||
meta = extract_meta_from_download_url(resp.url)
|
||||
|
||||
|
@ -1113,11 +1118,14 @@ def extract_meta_from_download_url(url: str) -> ExtractedDownloadMeta:
|
|||
urlobj = urlparse(url)
|
||||
query = parse_qs(urlobj.query)
|
||||
|
||||
def get_first_element(elements: list[str]) -> int:
|
||||
return int(elements[0])
|
||||
|
||||
meta = ExtractedDownloadMeta(
|
||||
mod_time=int(query.get("t")[0]),
|
||||
min_point_version=int(query.get("minpt")[0]),
|
||||
max_point_version=int(query.get("maxpt")[0]),
|
||||
branch_index=int(query.get("bidx")[0]),
|
||||
mod_time=get_first_element(query["t"]),
|
||||
min_point_version=get_first_element(query["minpt"]),
|
||||
max_point_version=get_first_element(query["maxpt"]),
|
||||
branch_index=get_first_element(query["bidx"]),
|
||||
)
|
||||
|
||||
return meta
|
||||
|
|
|
@ -6,7 +6,8 @@ from __future__ import annotations
|
|||
import json
|
||||
import math
|
||||
import re
|
||||
from typing import Callable, Sequence
|
||||
from collections.abc import Callable, Sequence
|
||||
from typing import Any
|
||||
|
||||
import aqt
|
||||
import aqt.browser
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Callable
|
||||
from collections.abc import Callable
|
||||
|
||||
import aqt
|
||||
from anki.cards import Card, CardId
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Sequence
|
||||
from collections.abc import Sequence
|
||||
|
||||
import aqt
|
||||
import aqt.forms
|
||||
|
|
|
@ -13,6 +13,7 @@ import aqt.forms
|
|||
from anki.collection import SearchNode
|
||||
from anki.notes import NoteId
|
||||
from aqt.qt import *
|
||||
from aqt.qt import sip
|
||||
from aqt.webview import AnkiWebViewKind
|
||||
|
||||
from ..operations import QueryOp
|
||||
|
|
|
@ -6,7 +6,8 @@ from __future__ import annotations
|
|||
import json
|
||||
import re
|
||||
import time
|
||||
from typing import Any, Callable
|
||||
from collections.abc import Callable
|
||||
from typing import Any
|
||||
|
||||
import aqt.browser
|
||||
from anki.cards import Card
|
||||
|
|
|
@ -2,8 +2,8 @@
|
|||
# License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
||||
from __future__ import annotations
|
||||
|
||||
from collections.abc import Callable, Iterable
|
||||
from enum import Enum, auto
|
||||
from typing import Callable, Iterable
|
||||
|
||||
from anki.collection import SearchNode
|
||||
from aqt.theme import ColoredIcon
|
||||
|
|
|
@ -3,6 +3,7 @@
|
|||
|
||||
from __future__ import annotations
|
||||
|
||||
from collections.abc import Callable
|
||||
from enum import Enum, auto
|
||||
|
||||
import aqt
|
||||
|
|
|
@ -2,8 +2,9 @@
|
|||
# License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
||||
from __future__ import annotations
|
||||
|
||||
from collections.abc import Callable, Iterable
|
||||
from enum import Enum, auto
|
||||
from typing import Iterable, cast
|
||||
from typing import cast
|
||||
|
||||
import aqt
|
||||
import aqt.browser
|
||||
|
@ -43,6 +44,7 @@ from aqt.operations.tag import (
|
|||
set_tag_collapsed,
|
||||
)
|
||||
from aqt.qt import *
|
||||
from aqt.qt import sip
|
||||
from aqt.theme import ColoredIcon, theme_manager
|
||||
from aqt.utils import (
|
||||
KeyboardModifiersPressed,
|
||||
|
@ -147,7 +149,7 @@ class SidebarTreeView(QTreeView):
|
|||
def op_executed(
|
||||
self, changes: OpChanges, handler: object | None, focused: bool
|
||||
) -> None:
|
||||
if changes.browser_sidebar and not handler is self:
|
||||
if changes.browser_sidebar and handler is not self:
|
||||
self._refresh_needed = True
|
||||
if focused:
|
||||
self.refresh_if_needed()
|
||||
|
@ -266,7 +268,7 @@ class SidebarTreeView(QTreeView):
|
|||
|
||||
def update_search(
|
||||
self,
|
||||
*terms: Union[str, SearchNode],
|
||||
*terms: str | SearchNode,
|
||||
joiner: SearchJoiner = "AND",
|
||||
) -> None:
|
||||
"""Modify the current search string based on modifier keys, then refresh."""
|
||||
|
@ -524,7 +526,7 @@ class SidebarTreeView(QTreeView):
|
|||
*,
|
||||
root: SidebarItem,
|
||||
name: str,
|
||||
icon: Union[str, ColoredIcon],
|
||||
icon: str | ColoredIcon,
|
||||
collapse_key: Config.Bool.V,
|
||||
type: SidebarItemType | None = None,
|
||||
) -> SidebarItem:
|
||||
|
|
|
@ -4,8 +4,9 @@ from __future__ import annotations
|
|||
|
||||
import copy
|
||||
import time
|
||||
from collections.abc import Generator, Sequence
|
||||
from dataclasses import dataclass
|
||||
from typing import TYPE_CHECKING, Generator, Sequence, Union
|
||||
from typing import TYPE_CHECKING, Union
|
||||
|
||||
import aqt
|
||||
import aqt.browser
|
||||
|
|
|
@ -3,7 +3,8 @@
|
|||
from __future__ import annotations
|
||||
|
||||
import time
|
||||
from typing import Any, Callable, Sequence
|
||||
from collections.abc import Callable, Sequence
|
||||
from typing import Any
|
||||
|
||||
import aqt
|
||||
import aqt.browser
|
||||
|
@ -242,7 +243,7 @@ class DataModel(QAbstractTableModel):
|
|||
self._state = self._state.toggle_state()
|
||||
try:
|
||||
self._search_inner(context)
|
||||
except:
|
||||
except Exception:
|
||||
# rollback to prevent inconsistent state
|
||||
self._state = self._state.toggle_state()
|
||||
raise
|
||||
|
|
|
@ -3,7 +3,8 @@
|
|||
from __future__ import annotations
|
||||
|
||||
from abc import ABC, abstractmethod, abstractproperty
|
||||
from typing import Sequence, cast
|
||||
from collections.abc import Sequence
|
||||
from typing import cast
|
||||
|
||||
from anki.browser import BrowserConfig
|
||||
from anki.cards import Card, CardId
|
||||
|
|
|
@ -2,7 +2,8 @@
|
|||
# License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Any, Callable, Sequence
|
||||
from collections.abc import Callable, Sequence
|
||||
from typing import Any
|
||||
|
||||
import aqt
|
||||
import aqt.browser
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Sequence
|
||||
from collections.abc import Sequence
|
||||
|
||||
import aqt
|
||||
import aqt.deckconf
|
||||
|
|
|
@ -5,8 +5,9 @@ from __future__ import annotations
|
|||
|
||||
import json
|
||||
import re
|
||||
from collections.abc import Callable
|
||||
from concurrent.futures import Future
|
||||
from typing import Any, Match, Optional, cast
|
||||
from typing import Any, Match, cast
|
||||
|
||||
import aqt
|
||||
import aqt.forms
|
||||
|
@ -50,7 +51,7 @@ class CardLayout(QDialog):
|
|||
mw: AnkiQt,
|
||||
note: Note,
|
||||
ord: int = 0,
|
||||
parent: Optional[QWidget] = None,
|
||||
parent: QWidget | None = None,
|
||||
fill_empty: bool = False,
|
||||
) -> None:
|
||||
QDialog.__init__(self, parent or mw, Qt.WindowType.Window)
|
||||
|
@ -509,7 +510,7 @@ class CardLayout(QDialog):
|
|||
# Preview
|
||||
##########################################################################
|
||||
|
||||
_previewTimer: Optional[QTimer] = None
|
||||
_previewTimer: QTimer | None = None
|
||||
|
||||
def renderPreview(self) -> None:
|
||||
# schedule a preview when timing stops
|
||||
|
@ -590,7 +591,7 @@ class CardLayout(QDialog):
|
|||
return res
|
||||
|
||||
type_filter = r"\[\[type:.+?\]\]"
|
||||
repl: Union[str, Callable]
|
||||
repl: str | Callable
|
||||
|
||||
if type == "q":
|
||||
repl = "<input id='typeans' type=text value='example' readonly='readonly'>"
|
||||
|
|
|
@ -3,8 +3,6 @@
|
|||
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Tuple
|
||||
|
||||
import aqt
|
||||
import aqt.forms
|
||||
import aqt.operations
|
||||
|
@ -37,12 +35,12 @@ class CustomStudy(QDialog):
|
|||
def fetch_data_and_show(mw: aqt.AnkiQt) -> None:
|
||||
def fetch_data(
|
||||
col: Collection,
|
||||
) -> Tuple[DeckId, CustomStudyDefaults]:
|
||||
) -> tuple[DeckId, CustomStudyDefaults]:
|
||||
deck_id = mw.col.decks.get_current_id()
|
||||
defaults = col.sched.custom_study_defaults(deck_id)
|
||||
return (deck_id, defaults)
|
||||
|
||||
def show_dialog(data: Tuple[DeckId, CustomStudyDefaults]) -> None:
|
||||
def show_dialog(data: tuple[DeckId, CustomStudyDefaults]) -> None:
|
||||
deck_id, defaults = data
|
||||
CustomStudy(mw=mw, deck_id=deck_id, defaults=defaults)
|
||||
|
||||
|
|
|
@ -4,6 +4,8 @@
|
|||
from __future__ import annotations
|
||||
|
||||
import os
|
||||
import sys
|
||||
from collections.abc import Callable
|
||||
from dataclasses import dataclass
|
||||
from functools import partial
|
||||
from pathlib import Path
|
||||
|
@ -292,7 +294,7 @@ class DebugConsole(QDialog):
|
|||
try:
|
||||
# pylint: disable=exec-used
|
||||
exec(text, vars)
|
||||
except:
|
||||
except Exception:
|
||||
self._output += traceback.format_exc()
|
||||
self._captureOutput(False)
|
||||
buf = ""
|
||||
|
|
|
@ -3,10 +3,13 @@
|
|||
|
||||
from __future__ import annotations
|
||||
|
||||
from collections.abc import Callable
|
||||
|
||||
from anki.collection import OpChanges
|
||||
from anki.decks import DEFAULT_DECK_ID, DeckId
|
||||
from aqt import AnkiQt, gui_hooks
|
||||
from aqt.qt import *
|
||||
from aqt.qt import sip
|
||||
from aqt.utils import HelpPage, shortcut, tr
|
||||
|
||||
|
||||
|
|
|
@ -173,8 +173,8 @@ class DeckConf(QDialog):
|
|||
# Loading
|
||||
##################################################
|
||||
|
||||
def listToUser(self, l: list[Union[int, float]]) -> str:
|
||||
def num_to_user(n: Union[int, float]) -> str:
|
||||
def listToUser(self, l: list[int | float]) -> str:
|
||||
def num_to_user(n: int | float) -> str:
|
||||
if n == round(n):
|
||||
return str(int(n))
|
||||
else:
|
||||
|
@ -267,7 +267,7 @@ class DeckConf(QDialog):
|
|||
if i == int(i):
|
||||
i = int(i)
|
||||
ret.append(i)
|
||||
except:
|
||||
except Exception:
|
||||
# invalid, don't update
|
||||
showWarning(tr.scheduling_steps_must_be_numbers())
|
||||
return
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
# Copyright: Ankitects Pty Ltd and contributors
|
||||
# License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
||||
from typing import Optional
|
||||
from __future__ import annotations
|
||||
|
||||
from collections.abc import Callable
|
||||
|
||||
import aqt.editor
|
||||
from anki.collection import OpChanges
|
||||
|
@ -37,7 +39,7 @@ class EditCurrent(QMainWindow):
|
|||
self.show()
|
||||
|
||||
def on_operation_did_execute(
|
||||
self, changes: OpChanges, handler: Optional[object]
|
||||
self, changes: OpChanges, handler: object | None
|
||||
) -> None:
|
||||
if changes.note_text and handler is not self.editor:
|
||||
# reload note
|
||||
|
|
|
@ -9,14 +9,16 @@ import html
|
|||
import itertools
|
||||
import json
|
||||
import mimetypes
|
||||
import os
|
||||
import re
|
||||
import urllib.error
|
||||
import urllib.parse
|
||||
import urllib.request
|
||||
import warnings
|
||||
from collections.abc import Callable
|
||||
from enum import Enum
|
||||
from random import randrange
|
||||
from typing import Any, Callable, Match, cast
|
||||
from typing import Any, Match, cast
|
||||
|
||||
import bs4
|
||||
import requests
|
||||
|
@ -59,7 +61,7 @@ from aqt.utils import (
|
|||
)
|
||||
from aqt.webview import AnkiWebView, AnkiWebViewKind
|
||||
|
||||
pics = ("jpg", "jpeg", "png", "tif", "tiff", "gif", "svg", "webp", "ico", "avif")
|
||||
pics = ("jpg", "jpeg", "png", "gif", "svg", "webp", "ico", "avif")
|
||||
audio = (
|
||||
"3gp",
|
||||
"aac",
|
||||
|
@ -294,6 +296,8 @@ require("anki/ui").loaded.then(() => require("anki/NoteEditor").instances[0].too
|
|||
disables: bool = True,
|
||||
rightside: bool = True,
|
||||
) -> str:
|
||||
title_attribute = tip
|
||||
|
||||
if icon:
|
||||
if icon.startswith("qrc:/"):
|
||||
iconstr = icon
|
||||
|
@ -301,47 +305,35 @@ require("anki/ui").loaded.then(() => require("anki/NoteEditor").instances[0].too
|
|||
iconstr = self.resourceToData(icon)
|
||||
else:
|
||||
iconstr = f"/_anki/imgs/{icon}.png"
|
||||
imgelm = f"""<img class="topbut" src="{iconstr}">"""
|
||||
image_element = f'<img class="topbut" src="{iconstr}">'
|
||||
else:
|
||||
imgelm = ""
|
||||
if label or not imgelm:
|
||||
labelelm = label or cmd
|
||||
image_element = ""
|
||||
|
||||
if not label and icon:
|
||||
label_element = ""
|
||||
elif label:
|
||||
label_element = label
|
||||
else:
|
||||
labelelm = ""
|
||||
if id:
|
||||
idstr = f"id={id}"
|
||||
else:
|
||||
idstr = ""
|
||||
if toggleable:
|
||||
toggleScript = "toggleEditorButton(this);"
|
||||
else:
|
||||
toggleScript = ""
|
||||
tip = shortcut(tip)
|
||||
if rightside:
|
||||
class_ = "linkb"
|
||||
else:
|
||||
class_ = "rounded"
|
||||
label_element = cmd
|
||||
|
||||
title_attribute = shortcut(title_attribute)
|
||||
cmd_to_toggle_button = "toggleEditorButton(this);" if toggleable else ""
|
||||
id_attribute_assignment = f"id={id}" if id else ""
|
||||
class_attribute = "linkb" if rightside else "rounded"
|
||||
if not disables:
|
||||
class_ += " perm"
|
||||
return """<button tabindex=-1
|
||||
{id}
|
||||
class="{class_}"
|
||||
class_attribute += " perm"
|
||||
|
||||
return f"""<button tabindex=-1
|
||||
{id_attribute_assignment}
|
||||
class="{class_attribute}"
|
||||
type="button"
|
||||
title="{tip}"
|
||||
onclick="pycmd('{cmd}');{togglesc}return false;"
|
||||
title="{title_attribute}"
|
||||
onclick="pycmd('{cmd}');{cmd_to_toggle_button}return false;"
|
||||
onmousedown="window.event.preventDefault();"
|
||||
>
|
||||
{imgelm}
|
||||
{labelelm}
|
||||
</button>""".format(
|
||||
imgelm=imgelm,
|
||||
cmd=cmd,
|
||||
tip=tip,
|
||||
labelelm=labelelm,
|
||||
id=idstr,
|
||||
togglesc=toggleScript,
|
||||
class_=class_,
|
||||
)
|
||||
{image_element}
|
||||
{label_element}
|
||||
</button>"""
|
||||
|
||||
def setupShortcuts(self) -> None:
|
||||
# if a third element is provided, enable shortcut even when no field selected
|
||||
|
|
|
@ -3,15 +3,19 @@
|
|||
|
||||
from __future__ import annotations
|
||||
|
||||
import os
|
||||
import re
|
||||
import sys
|
||||
import time
|
||||
from typing import TYPE_CHECKING, Optional, TextIO, cast
|
||||
import traceback
|
||||
from typing import TYPE_CHECKING, TextIO, cast
|
||||
|
||||
from markdown import markdown
|
||||
|
||||
import aqt
|
||||
from anki.collection import HelpPage
|
||||
from anki.errors import BackendError, Interrupted
|
||||
from anki.utils import is_win
|
||||
from aqt.addons import AddonManager, AddonMeta
|
||||
from aqt.qt import *
|
||||
from aqt.utils import openHelp, showWarning, supportText, tooltip, tr
|
||||
|
@ -169,7 +173,7 @@ class ErrorHandler(QObject):
|
|||
def __init__(self, mw: AnkiQt) -> None:
|
||||
QObject.__init__(self, mw)
|
||||
self.mw = mw
|
||||
self.timer: Optional[QTimer] = None
|
||||
self.timer: QTimer | None = None
|
||||
qconnect(self.errorTimer, self._setTimer)
|
||||
self.pool = ""
|
||||
self._oldstderr = sys.stderr
|
||||
|
|
|
@ -7,7 +7,6 @@ import os
|
|||
import re
|
||||
import time
|
||||
from concurrent.futures import Future
|
||||
from typing import Optional
|
||||
|
||||
import aqt
|
||||
import aqt.forms
|
||||
|
@ -35,7 +34,7 @@ class ExportDialog(QDialog):
|
|||
mw: aqt.main.AnkiQt,
|
||||
did: DeckId | None = None,
|
||||
cids: list[CardId] | None = None,
|
||||
parent: Optional[QWidget] = None,
|
||||
parent: QWidget | None = None,
|
||||
):
|
||||
QDialog.__init__(self, parent or mw, Qt.WindowType.Window)
|
||||
self.mw = mw
|
||||
|
|
|
@ -3,8 +3,6 @@
|
|||
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Optional
|
||||
|
||||
import aqt
|
||||
import aqt.forms
|
||||
import aqt.operations
|
||||
|
@ -32,7 +30,7 @@ class FieldDialog(QDialog):
|
|||
self,
|
||||
mw: AnkiQt,
|
||||
nt: NotetypeDict,
|
||||
parent: Optional[QWidget] = None,
|
||||
parent: QWidget | None = None,
|
||||
open_at: int = 0,
|
||||
) -> None:
|
||||
QDialog.__init__(self, parent or mw)
|
||||
|
@ -62,7 +60,7 @@ class FieldDialog(QDialog):
|
|||
self.form.buttonBox.button(QDialogButtonBox.StandardButton.Save).setAutoDefault(
|
||||
False
|
||||
)
|
||||
self.currentIdx: Optional[int] = None
|
||||
self.currentIdx: int | None = None
|
||||
self.fillFields()
|
||||
self.setupSignals()
|
||||
self.form.fieldList.setDragDropMode(QAbstractItemView.DragDropMode.InternalMove)
|
||||
|
@ -125,8 +123,8 @@ class FieldDialog(QDialog):
|
|||
self.loadField(idx)
|
||||
|
||||
def _uniqueName(
|
||||
self, prompt: str, ignoreOrd: Optional[int] = None, old: str = ""
|
||||
) -> Optional[str]:
|
||||
self, prompt: str, ignoreOrd: int | None = None, old: str = ""
|
||||
) -> str | None:
|
||||
txt = getOnlyText(prompt, default=old).replace('"', "").strip()
|
||||
if not txt:
|
||||
return None
|
||||
|
|
|
@ -44,6 +44,9 @@
|
|||
<property name="text">
|
||||
<string>preferences_language</string>
|
||||
</property>
|
||||
<property name="buddy">
|
||||
<cstring>lang</cstring>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="1">
|
||||
|
@ -64,6 +67,9 @@
|
|||
<property name="text">
|
||||
<string>preferences_video_driver</string>
|
||||
</property>
|
||||
<property name="buddy">
|
||||
<cstring>video_driver</cstring>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="1">
|
||||
|
@ -76,6 +82,19 @@
|
|||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QCheckBox" name="check_for_updates">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Fixed" vsizetype="Fixed">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>preferences_check_for_updates</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
|
@ -116,6 +135,9 @@
|
|||
<property name="text">
|
||||
<string>preferences_style</string>
|
||||
</property>
|
||||
<property name="buddy">
|
||||
<cstring>styleComboBox</cstring>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="0">
|
||||
|
@ -123,6 +145,9 @@
|
|||
<property name="text">
|
||||
<string>preferences_theme</string>
|
||||
</property>
|
||||
<property name="buddy">
|
||||
<cstring>theme</cstring>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="0">
|
||||
|
@ -130,6 +155,9 @@
|
|||
<property name="text">
|
||||
<string>preferences_user_interface_size</string>
|
||||
</property>
|
||||
<property name="buddy">
|
||||
<cstring>uiScale</cstring>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="3" column="0" colspan="2">
|
||||
|
@ -264,33 +292,25 @@
|
|||
<widget class="QSpinBox" name="lrnCutoff">
|
||||
<property name="maximumSize">
|
||||
<size>
|
||||
<width>60</width>
|
||||
<width>16777215</width>
|
||||
<height>16777215</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="suffix">
|
||||
<string>preferences_mins</string>
|
||||
</property>
|
||||
<property name="maximum">
|
||||
<number>999</number>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="2">
|
||||
<widget class="QLabel" name="label_29">
|
||||
<property name="text">
|
||||
<string>preferences_mins</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="0">
|
||||
<widget class="QLabel" name="label_30">
|
||||
<property name="text">
|
||||
<string>preferences_timebox_time_limit</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="2">
|
||||
<widget class="QLabel" name="label_40">
|
||||
<property name="text">
|
||||
<string>preferences_hours_past_midnight</string>
|
||||
<property name="buddy">
|
||||
<cstring>timeLimit</cstring>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
|
@ -298,10 +318,13 @@
|
|||
<widget class="QSpinBox" name="dayOffset">
|
||||
<property name="maximumSize">
|
||||
<size>
|
||||
<width>60</width>
|
||||
<width>16777215</width>
|
||||
<height>16777215</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="suffix">
|
||||
<string>preferences_hours_past_midnight</string>
|
||||
</property>
|
||||
<property name="maximum">
|
||||
<number>23</number>
|
||||
</property>
|
||||
|
@ -312,6 +335,9 @@
|
|||
<property name="text">
|
||||
<string>preferences_learn_ahead_limit</string>
|
||||
</property>
|
||||
<property name="buddy">
|
||||
<cstring>lrnCutoff</cstring>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="0">
|
||||
|
@ -319,17 +345,16 @@
|
|||
<property name="text">
|
||||
<string>preferences_next_day_starts_at</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="2">
|
||||
<widget class="QLabel" name="label_39">
|
||||
<property name="text">
|
||||
<string>preferences_mins</string>
|
||||
<property name="buddy">
|
||||
<cstring>dayOffset</cstring>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="1">
|
||||
<widget class="QSpinBox" name="timeLimit">
|
||||
<property name="suffix">
|
||||
<string>preferences_mins</string>
|
||||
</property>
|
||||
<property name="maximum">
|
||||
<number>9999</number>
|
||||
</property>
|
||||
|
@ -513,6 +538,9 @@
|
|||
<property name="text">
|
||||
<string>preferences_default_deck</string>
|
||||
</property>
|
||||
<property name="buddy">
|
||||
<cstring>useCurrent</cstring>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
|
@ -553,6 +581,9 @@
|
|||
<property name="text">
|
||||
<string>preferences_default_search_text</string>
|
||||
</property>
|
||||
<property name="buddy">
|
||||
<cstring>default_search_text</cstring>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
|
@ -705,10 +736,16 @@
|
|||
<property name="text">
|
||||
<string>preferences_network_timeout</string>
|
||||
</property>
|
||||
<property name="buddy">
|
||||
<cstring>network_timeout</cstring>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QSpinBox" name="network_timeout">
|
||||
<property name="suffix">
|
||||
<string>scheduling_seconds</string>
|
||||
</property>
|
||||
<property name="minimum">
|
||||
<number>30</number>
|
||||
</property>
|
||||
|
@ -717,26 +754,6 @@
|
|||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLabel" name="label_8">
|
||||
<property name="text">
|
||||
<string>scheduling_seconds</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<spacer name="horizontalSpacer_4">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>40</width>
|
||||
<height>20</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item>
|
||||
|
@ -853,6 +870,9 @@
|
|||
<property name="text">
|
||||
<string>preferences_custom_sync_url</string>
|
||||
</property>
|
||||
<property name="buddy">
|
||||
<cstring>custom_sync_url</cstring>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
|
@ -954,6 +974,9 @@
|
|||
<property name="text">
|
||||
<string>preferences_daily_backups</string>
|
||||
</property>
|
||||
<property name="buddy">
|
||||
<cstring>daily_backups</cstring>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="4" column="2">
|
||||
|
@ -968,6 +991,9 @@
|
|||
<property name="text">
|
||||
<string>preferences_monthly_backups</string>
|
||||
</property>
|
||||
<property name="buddy">
|
||||
<cstring>monthly_backups</cstring>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="4" column="0">
|
||||
|
@ -975,6 +1001,9 @@
|
|||
<property name="text">
|
||||
<string>preferences_weekly_backups</string>
|
||||
</property>
|
||||
<property name="buddy">
|
||||
<cstring>weekly_backups</cstring>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="0">
|
||||
|
@ -982,6 +1011,9 @@
|
|||
<property name="text">
|
||||
<string>preferences_minutes_between_backups</string>
|
||||
</property>
|
||||
<property name="buddy">
|
||||
<cstring>minutes_between_backups</cstring>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="2">
|
||||
|
|
|
@ -7,8 +7,8 @@ import os
|
|||
import re
|
||||
import time
|
||||
from abc import ABC, abstractmethod
|
||||
from collections.abc import Sequence
|
||||
from dataclasses import dataclass
|
||||
from typing import Optional, Sequence, Type
|
||||
|
||||
import aqt.forms
|
||||
import aqt.main
|
||||
|
@ -42,7 +42,7 @@ class ExportDialog(QDialog):
|
|||
mw: aqt.main.AnkiQt,
|
||||
did: DeckId | None = None,
|
||||
nids: Sequence[NoteId] | None = None,
|
||||
parent: Optional[QWidget] = None,
|
||||
parent: QWidget | None = None,
|
||||
):
|
||||
QDialog.__init__(self, parent or mw, Qt.WindowType.Window)
|
||||
self.mw = mw
|
||||
|
@ -56,7 +56,7 @@ class ExportDialog(QDialog):
|
|||
self.open()
|
||||
|
||||
def setup(self, did: DeckId | None) -> None:
|
||||
self.exporter_classes: list[Type[Exporter]] = [
|
||||
self.exporter_classes: list[type[Exporter]] = [
|
||||
ApkgExporter,
|
||||
ColpkgExporter,
|
||||
NoteCsvExporter,
|
||||
|
|
|
@ -3,10 +3,11 @@
|
|||
|
||||
from __future__ import annotations
|
||||
|
||||
import os
|
||||
import re
|
||||
from abc import ABC, abstractmethod
|
||||
from collections.abc import Callable
|
||||
from itertools import chain
|
||||
from typing import Type
|
||||
|
||||
import aqt.main
|
||||
from anki.collection import Collection, Progress
|
||||
|
@ -124,7 +125,7 @@ class JsonImporter(Importer):
|
|||
ImportDialog(mw, JsonFileArgs(path=path))
|
||||
|
||||
|
||||
IMPORTERS: list[Type[Importer]] = [
|
||||
IMPORTERS: list[type[Importer]] = [
|
||||
ColpkgImporter,
|
||||
ApkgImporter,
|
||||
MnemosyneImporter,
|
||||
|
|
|
@ -1,11 +1,15 @@
|
|||
# Copyright: Ankitects Pty Ltd and contributors
|
||||
# License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
||||
from __future__ import annotations
|
||||
|
||||
import os
|
||||
import re
|
||||
import sys
|
||||
import traceback
|
||||
import zipfile
|
||||
from collections.abc import Callable
|
||||
from concurrent.futures import Future
|
||||
from typing import Any, Optional
|
||||
from typing import Any
|
||||
|
||||
import anki.importing as importing
|
||||
import aqt.deckchooser
|
||||
|
@ -54,7 +58,7 @@ class ChangeMap(QDialog):
|
|||
self.frm.fields.setCurrentRow(n)
|
||||
else:
|
||||
self.frm.fields.setCurrentRow(n + 1)
|
||||
self.field: Optional[str] = None
|
||||
self.field: str | None = None
|
||||
|
||||
def getField(self) -> str:
|
||||
self.exec()
|
||||
|
@ -230,13 +234,13 @@ class ImportDialog(QDialog):
|
|||
self.frm.mappingArea.setWidget(self.frame)
|
||||
self.mapbox = QVBoxLayout(self.frame)
|
||||
self.mapbox.setContentsMargins(0, 0, 0, 0)
|
||||
self.mapwidget: Optional[QWidget] = None
|
||||
self.mapwidget: QWidget | None = None
|
||||
|
||||
def hideMapping(self) -> None:
|
||||
self.frm.mappingGroup.hide()
|
||||
|
||||
def showMapping(
|
||||
self, keepMapping: bool = False, hook: Optional[Callable] = None
|
||||
self, keepMapping: bool = False, hook: Callable | None = None
|
||||
) -> None:
|
||||
if hook:
|
||||
hook()
|
||||
|
|
|
@ -8,10 +8,13 @@ import gc
|
|||
import os
|
||||
import re
|
||||
import signal
|
||||
import sys
|
||||
import traceback
|
||||
import weakref
|
||||
from argparse import Namespace
|
||||
from collections.abc import Callable, Sequence
|
||||
from concurrent.futures import Future
|
||||
from typing import Any, Literal, Sequence, TypeVar, cast
|
||||
from typing import Any, Literal, TypeVar, cast
|
||||
|
||||
import anki
|
||||
import anki.cards
|
||||
|
@ -199,7 +202,7 @@ class AnkiQt(QMainWindow):
|
|||
self.setupUI()
|
||||
self.setupAddons(args)
|
||||
self.finish_ui_setup()
|
||||
except:
|
||||
except Exception:
|
||||
showInfo(tr.qt_misc_error_during_startup(val=traceback.format_exc()))
|
||||
sys.exit(1)
|
||||
# must call this after ui set up
|
||||
|
@ -350,7 +353,7 @@ class AnkiQt(QMainWindow):
|
|||
f.profiles.addItems(profs)
|
||||
try:
|
||||
idx = profs.index(self.pm.name)
|
||||
except:
|
||||
except Exception:
|
||||
idx = 0
|
||||
f.profiles.setCurrentRow(idx)
|
||||
|
||||
|
@ -680,7 +683,7 @@ class AnkiQt(QMainWindow):
|
|||
self.maybeOptimize()
|
||||
if not dev_mode:
|
||||
corrupt = self.col.db.scalar("pragma quick_check") != "ok"
|
||||
except:
|
||||
except Exception:
|
||||
corrupt = True
|
||||
|
||||
try:
|
||||
|
@ -692,7 +695,7 @@ class AnkiQt(QMainWindow):
|
|||
force=False,
|
||||
wait_for_completion=False,
|
||||
)
|
||||
except:
|
||||
except Exception:
|
||||
print("backup on close failed")
|
||||
self.col.close(downgrade=False)
|
||||
except Exception as e:
|
||||
|
@ -1428,6 +1431,7 @@ title="{}" {}>{}</button>""".format(
|
|||
def setup_auto_update(self, _log: list[DownloadLogEntry]) -> None:
|
||||
from aqt.update import check_for_update
|
||||
|
||||
if aqt.mw.pm.check_for_updates():
|
||||
check_for_update()
|
||||
|
||||
# Timers
|
||||
|
|
|
@ -5,8 +5,9 @@ from __future__ import annotations
|
|||
|
||||
import itertools
|
||||
import time
|
||||
from collections.abc import Iterable, Sequence
|
||||
from concurrent.futures import Future
|
||||
from typing import Iterable, Sequence, TypeVar
|
||||
from typing import TypeVar
|
||||
|
||||
import aqt
|
||||
import aqt.progress
|
||||
|
|
|
@ -11,10 +11,10 @@ import re
|
|||
import sys
|
||||
import threading
|
||||
import traceback
|
||||
from collections.abc import Callable
|
||||
from dataclasses import dataclass
|
||||
from errno import EPROTOTYPE
|
||||
from http import HTTPStatus
|
||||
from typing import Callable
|
||||
|
||||
import flask
|
||||
import flask_cors
|
||||
|
|
|
@ -4,9 +4,10 @@
|
|||
from __future__ import annotations
|
||||
|
||||
import time
|
||||
from collections.abc import Callable
|
||||
from concurrent.futures import Future
|
||||
from datetime import datetime
|
||||
from typing import Any, Callable
|
||||
from typing import Any
|
||||
|
||||
import aqt
|
||||
import aqt.forms
|
||||
|
|
|
@ -1,7 +1,8 @@
|
|||
# Copyright: Ankitects Pty Ltd and contributors
|
||||
# License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Optional
|
||||
from collections.abc import Callable
|
||||
|
||||
from aqt import AnkiQt, gui_hooks
|
||||
from aqt.qt import *
|
||||
|
@ -16,7 +17,7 @@ class ModelChooser(QHBoxLayout):
|
|||
mw: AnkiQt,
|
||||
widget: QWidget,
|
||||
label: bool = True,
|
||||
on_activated: Optional[Callable[[], None]] = None,
|
||||
on_activated: Callable[[], None] | None = None,
|
||||
) -> None:
|
||||
"""If provided, on_activated() will be called when the button is clicked,
|
||||
and the caller can call .onModelChange() to pull up the dialog when they
|
||||
|
|
|
@ -3,9 +3,10 @@
|
|||
|
||||
from __future__ import annotations
|
||||
|
||||
from collections.abc import Callable, Sequence
|
||||
from concurrent.futures import Future
|
||||
from operator import itemgetter
|
||||
from typing import Any, Optional, Sequence
|
||||
from typing import Any
|
||||
|
||||
import aqt.clayout
|
||||
from anki import stdmodels
|
||||
|
@ -40,9 +41,9 @@ class Models(QDialog):
|
|||
def __init__(
|
||||
self,
|
||||
mw: AnkiQt,
|
||||
parent: Optional[QWidget] = None,
|
||||
parent: QWidget | None = None,
|
||||
fromMain: bool = False,
|
||||
selected_notetype_id: Optional[NotetypeId] = None,
|
||||
selected_notetype_id: NotetypeId | None = None,
|
||||
):
|
||||
self.mw = mw
|
||||
parent = parent or mw
|
||||
|
@ -61,6 +62,13 @@ class Models(QDialog):
|
|||
self.models: Sequence[NotetypeNameIdUseCount] = []
|
||||
self.setupModels()
|
||||
restoreGeom(self, "models")
|
||||
|
||||
self.setWindowFlags(
|
||||
self.windowFlags()
|
||||
| Qt.WindowType.WindowMaximizeButtonHint
|
||||
| Qt.WindowType.WindowMinimizeButtonHint
|
||||
)
|
||||
|
||||
self.show()
|
||||
|
||||
# Models
|
||||
|
@ -231,13 +239,13 @@ class Models(QDialog):
|
|||
|
||||
|
||||
class AddModel(QDialog):
|
||||
model: Optional[NotetypeDict]
|
||||
model: NotetypeDict | None
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
mw: AnkiQt,
|
||||
on_success: Callable[[NotetypeDict], None],
|
||||
parent: Optional[QWidget] = None,
|
||||
parent: QWidget | None = None,
|
||||
) -> None:
|
||||
self.parent_ = parent or mw
|
||||
self.mw = mw
|
||||
|
@ -249,9 +257,7 @@ class AddModel(QDialog):
|
|||
self.setWindowModality(Qt.WindowModality.ApplicationModal)
|
||||
disable_help_button(self)
|
||||
# standard models
|
||||
self.notetypes: list[
|
||||
Union[NotetypeDict, Callable[[Collection], NotetypeDict]]
|
||||
] = []
|
||||
self.notetypes: list[NotetypeDict | Callable[[Collection], NotetypeDict]] = []
|
||||
for name, func in stdmodels.get_stock_notetypes(self.col):
|
||||
item = QListWidgetItem(tr.notetypes_add(val=name))
|
||||
self.dialog.models.addItem(item)
|
||||
|
|
|
@ -25,6 +25,7 @@
|
|||
# ------------------------------------------------------------------------------
|
||||
|
||||
# pylint: disable=raise-missing-from
|
||||
from __future__ import annotations
|
||||
|
||||
import inspect
|
||||
import json
|
||||
|
@ -38,7 +39,6 @@ import threading
|
|||
import time
|
||||
from queue import Empty, Full, Queue
|
||||
from shutil import which
|
||||
from typing import Optional
|
||||
|
||||
from anki.utils import is_win
|
||||
|
||||
|
@ -77,7 +77,7 @@ class MPVBase:
|
|||
"""
|
||||
|
||||
executable = which("mpv")
|
||||
popenEnv: Optional[dict[str, str]] = None
|
||||
popenEnv: dict[str, str] | None = None
|
||||
|
||||
default_argv = [
|
||||
"--idle",
|
||||
|
|
|
@ -3,6 +3,8 @@
|
|||
|
||||
from __future__ import annotations
|
||||
|
||||
from collections.abc import Callable
|
||||
|
||||
from anki.collection import OpChanges
|
||||
from anki.models import NotetypeId
|
||||
from aqt import AnkiQt, gui_hooks
|
||||
|
|
|
@ -3,8 +3,9 @@
|
|||
|
||||
from __future__ import annotations
|
||||
|
||||
from collections.abc import Callable
|
||||
from concurrent.futures._base import Future
|
||||
from typing import Any, Callable, Generic, Protocol, TypeVar, Union
|
||||
from typing import Any, Generic, Protocol, TypeVar, Union
|
||||
|
||||
import aqt
|
||||
import aqt.gui_hooks
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Sequence
|
||||
from collections.abc import Sequence
|
||||
|
||||
from anki.cards import CardId
|
||||
from anki.collection import OpChangesWithCount
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue