mirror of
https://github.com/ankitects/anki.git
synced 2026-01-13 22:13:58 -05:00
Merge remote-tracking branch 'upstream/main' into cmmr-uses-simulate-config
This commit is contained in:
commit
f5e0935ade
95 changed files with 674 additions and 339 deletions
2
.version
2
.version
|
|
@ -1 +1 @@
|
|||
25.02
|
||||
25.05
|
||||
|
|
|
|||
|
|
@ -222,6 +222,7 @@ ikkz <ylei.mk@gmail.com>
|
|||
derivativeoflog7 <https://github.com/derivativeoflog7>
|
||||
rreemmii-dev <https://github.com/rreemmii-dev>
|
||||
babofitos <https://github.com/babofitos>
|
||||
Jonathan Schoreels <https://github.com/JSchoreels>
|
||||
|
||||
********************
|
||||
|
||||
|
|
|
|||
157
Cargo.lock
generated
157
Cargo.lock
generated
|
|
@ -142,7 +142,7 @@ dependencies = [
|
|||
"serde_tuple",
|
||||
"sha1",
|
||||
"snafu",
|
||||
"strum",
|
||||
"strum 0.26.3",
|
||||
"syn 2.0.96",
|
||||
"tempfile",
|
||||
"tokio",
|
||||
|
|
@ -218,7 +218,7 @@ dependencies = [
|
|||
"prost-types",
|
||||
"serde",
|
||||
"snafu",
|
||||
"strum",
|
||||
"strum 0.26.3",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
@ -574,11 +574,12 @@ checksum = "8c3c1a368f70d6cf7302d78f8f7093da241fb8e8807c05cc9e51a125895a6d5b"
|
|||
|
||||
[[package]]
|
||||
name = "bincode"
|
||||
version = "2.0.0-rc.3"
|
||||
version = "2.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f11ea1a0346b94ef188834a65c068a03aec181c94896d481d7a0a40d85b0ce95"
|
||||
checksum = "36eaf5d7b090263e8150820482d5d93cd964a81e4019913c972f4edcc6edb740"
|
||||
dependencies = [
|
||||
"serde",
|
||||
"unty",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
@ -664,9 +665,9 @@ checksum = "79296716171880943b8470b5f8d03aa55eb2e645a4874bdbb28adb49162e012c"
|
|||
|
||||
[[package]]
|
||||
name = "burn"
|
||||
version = "0.16.0"
|
||||
version = "0.16.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "55af4c56b540bcf00cf1c7e13b1c60644734906495048afbd4a79aabd0a6efbe"
|
||||
checksum = "22149f3b5ab6628e9e9c0b29156b906d32d36bbf76f2c34ad5ce1801f5b4486e"
|
||||
dependencies = [
|
||||
"burn-core",
|
||||
"burn-train",
|
||||
|
|
@ -674,9 +675,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "burn-autodiff"
|
||||
version = "0.16.0"
|
||||
version = "0.16.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0fa53181463ef16220438e240f10e1e8cb2fcf1824dbc33b8f259a454ff5f46f"
|
||||
checksum = "f2167ab07f9be5f2a027accba92d8dde02ea905f35844f8529bb2533b4fc8646"
|
||||
dependencies = [
|
||||
"burn-common",
|
||||
"burn-tensor",
|
||||
|
|
@ -687,9 +688,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "burn-candle"
|
||||
version = "0.16.0"
|
||||
version = "0.16.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0b49a6da72c10ac552b3c023d74dade9714c10aac0fc5f33cfc4ca389463b99e"
|
||||
checksum = "aeef1204c4d33dd71a9628a311178eb149131c65234eb64e8201e27cf1ee1ba0"
|
||||
dependencies = [
|
||||
"burn-tensor",
|
||||
"candle-core",
|
||||
|
|
@ -699,9 +700,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "burn-common"
|
||||
version = "0.16.0"
|
||||
version = "0.16.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8a1471949b06002c984df9d753a084a79149841dd7935911d9e432b8478f9fd5"
|
||||
checksum = "fb516d1faa50628828b3c2b79db3e483f20d62966f7dae68c6f21743f5f7e8ef"
|
||||
dependencies = [
|
||||
"cubecl-common",
|
||||
"getrandom 0.2.15",
|
||||
|
|
@ -712,9 +713,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "burn-core"
|
||||
version = "0.16.0"
|
||||
version = "0.16.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9f8ebbf7d5c8bdc269260bd8e7ce08e488e6625da19b3d80ca34a729d78a77ab"
|
||||
checksum = "594c44ac9f2996c2c0b92f5a44a1287d41fca3954182601a4a29b628a5973357"
|
||||
dependencies = [
|
||||
"ahash",
|
||||
"bincode",
|
||||
|
|
@ -747,9 +748,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "burn-cuda"
|
||||
version = "0.16.0"
|
||||
version = "0.16.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f90534d6c7f909a8cad49470921dc3eb2b118f7a3c8bde313defba3f4cae3ac3"
|
||||
checksum = "08fe1e5f285214d16cfd298453b807675c9a4ed742a35c8807be42af69e8ee97"
|
||||
dependencies = [
|
||||
"burn-jit",
|
||||
"burn-tensor",
|
||||
|
|
@ -762,9 +763,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "burn-dataset"
|
||||
version = "0.16.0"
|
||||
version = "0.16.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5b851cb5165da57871bed2c48a29673dde0ddbd198a39b2a37411b6adf6df6ad"
|
||||
checksum = "92a5cde6c09c751fb6aafca10d8e18faa42fe18eef44b4768851575c20db6904"
|
||||
dependencies = [
|
||||
"csv",
|
||||
"derive-new 0.7.0",
|
||||
|
|
@ -774,17 +775,17 @@ dependencies = [
|
|||
"sanitize-filename 0.6.0",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"strum",
|
||||
"strum_macros",
|
||||
"strum 0.26.3",
|
||||
"strum_macros 0.26.4",
|
||||
"tempfile",
|
||||
"thiserror 2.0.11",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "burn-derive"
|
||||
version = "0.16.0"
|
||||
version = "0.16.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4f784ffe0df57848ba232e5f40a1c1f5df3571df59bec99ba32bc7610fd9e811"
|
||||
checksum = "2bb8f828a681946b07a87750ed0593d885e7b101653bd6a3bb1942976156bb48"
|
||||
dependencies = [
|
||||
"derive-new 0.7.0",
|
||||
"proc-macro2",
|
||||
|
|
@ -794,9 +795,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "burn-hip"
|
||||
version = "0.16.0"
|
||||
version = "0.16.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "dd9fbfee77b3d2b67bf434b883ec6ed73f4f9bf1fd8d59f9dde217c7a4b5285d"
|
||||
checksum = "84191ed69af8c48a133c05e0ee4dfe73d7a3b8f96e3ceec899b4f85b19072232"
|
||||
dependencies = [
|
||||
"burn-jit",
|
||||
"burn-tensor",
|
||||
|
|
@ -809,9 +810,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "burn-jit"
|
||||
version = "0.16.0"
|
||||
version = "0.16.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cb6b06689c4e8d6cfdcaf0b0e168e58a931c3935414e48f4e3e3e85e8d7a77a0"
|
||||
checksum = "6b723ddb46032953c4fb908feca57b470b0c9839f808fcd52de04a8510b88a23"
|
||||
dependencies = [
|
||||
"burn-common",
|
||||
"burn-tensor",
|
||||
|
|
@ -831,9 +832,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "burn-ndarray"
|
||||
version = "0.16.0"
|
||||
version = "0.16.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "419fa3eda8cf9fddce0d156946b3d46642c10a41569b23e7855f775f862d310a"
|
||||
checksum = "1b8ce3bd0f1e792b53610d291eb463d9790449688c4455a496c46266e46a179f"
|
||||
dependencies = [
|
||||
"atomic_float",
|
||||
"burn-autodiff",
|
||||
|
|
@ -842,7 +843,7 @@ dependencies = [
|
|||
"derive-new 0.7.0",
|
||||
"libm",
|
||||
"matrixmultiply",
|
||||
"ndarray 0.16.1",
|
||||
"ndarray",
|
||||
"num-traits",
|
||||
"portable-atomic-util",
|
||||
"rand",
|
||||
|
|
@ -851,9 +852,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "burn-router"
|
||||
version = "0.16.0"
|
||||
version = "0.16.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c39bdb6d5c749221741a362da9b3ea3157304f831ab4b4a6902725a1efaea159"
|
||||
checksum = "b6a1a6cb08eccb65b112bc5853ac35c88faa8b04b03270bcfb1ac8a253a066bb"
|
||||
dependencies = [
|
||||
"burn-common",
|
||||
"burn-tensor",
|
||||
|
|
@ -864,9 +865,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "burn-tensor"
|
||||
version = "0.16.0"
|
||||
version = "0.16.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "24db20273a636d5340e5a29af142722e0a657491e6b3cfcceb1e62eb862b3b37"
|
||||
checksum = "ab959e7da2e7514b959d841c93e8e026233aa77284f5d976099a8f5251e3ba99"
|
||||
dependencies = [
|
||||
"burn-common",
|
||||
"bytemuck",
|
||||
|
|
@ -885,9 +886,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "burn-train"
|
||||
version = "0.16.0"
|
||||
version = "0.16.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "714298cbc0c41f48d53cb1e6aeb6203b49b6110620517f69fbcc37a9b41cb6c8"
|
||||
checksum = "c004e8c761ad50c568739581a2dab38aa2f4db723183fb189f130e6d2b4e0d1c"
|
||||
dependencies = [
|
||||
"async-channel",
|
||||
"burn-core",
|
||||
|
|
@ -906,9 +907,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "burn-wgpu"
|
||||
version = "0.16.0"
|
||||
version = "0.16.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9ef5b6c56da563a708b2da16f0559a061e7b93f3acae63903734ee978c9b9f93"
|
||||
checksum = "5f80a3413527087e73042c807d41d6fd3a1e8a28eb519e3ad5e91f6354cbfb9d"
|
||||
dependencies = [
|
||||
"burn-jit",
|
||||
"burn-tensor",
|
||||
|
|
@ -2099,19 +2100,20 @@ dependencies = [
|
|||
[[package]]
|
||||
name = "fsrs"
|
||||
version = "3.0.0"
|
||||
source = "git+https://github.com/open-spaced-repetition/fsrs-rs.git?rev=08d90d1363b0c4722422bf0ef71ed8fd7d053f8a#08d90d1363b0c4722422bf0ef71ed8fd7d053f8a"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e7e39017be91629761c3a2802032eb38d226227e1a21433381da4310612199ee"
|
||||
dependencies = [
|
||||
"burn",
|
||||
"itertools 0.12.1",
|
||||
"itertools 0.14.0",
|
||||
"log",
|
||||
"ndarray 0.15.6",
|
||||
"ndarray",
|
||||
"ndarray-rand",
|
||||
"priority-queue",
|
||||
"rand",
|
||||
"rayon",
|
||||
"serde",
|
||||
"snafu",
|
||||
"strum",
|
||||
"strum 0.27.1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
@ -3254,18 +3256,18 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "itertools"
|
||||
version = "0.12.1"
|
||||
version = "0.13.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ba291022dbbd398a455acf126c1e341954079855bc60dfdda641363bd6922569"
|
||||
checksum = "413ee7dfc52ee1a4949ceeb7dbc8a33f2d6c088194d9f922fb8318faf1f01186"
|
||||
dependencies = [
|
||||
"either",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "itertools"
|
||||
version = "0.13.0"
|
||||
version = "0.14.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "413ee7dfc52ee1a4949ceeb7dbc8a33f2d6c088194d9f922fb8318faf1f01186"
|
||||
checksum = "2b192c782037fadd9cfa75548310488aabdbf3d2da73885b31bd0abd03351285"
|
||||
dependencies = [
|
||||
"either",
|
||||
]
|
||||
|
|
@ -3376,7 +3378,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||
checksum = "4979f22fdb869068da03c9f7528f8297c6fd2606bc3a4affe42e6a823fdb8da4"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"windows-targets 0.48.5",
|
||||
"windows-targets 0.52.6",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
@ -3441,7 +3443,7 @@ dependencies = [
|
|||
"linkcheck",
|
||||
"regex",
|
||||
"reqwest 0.12.8",
|
||||
"strum",
|
||||
"strum 0.26.3",
|
||||
"tokio",
|
||||
]
|
||||
|
||||
|
|
@ -3831,19 +3833,6 @@ dependencies = [
|
|||
"tempfile",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ndarray"
|
||||
version = "0.15.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "adb12d4e967ec485a5f71c6311fe28158e9d6f4bc4a447b474184d0f91a8fa32"
|
||||
dependencies = [
|
||||
"matrixmultiply",
|
||||
"num-complex",
|
||||
"num-integer",
|
||||
"num-traits",
|
||||
"rawpointer",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ndarray"
|
||||
version = "0.16.1"
|
||||
|
|
@ -3862,11 +3851,11 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "ndarray-rand"
|
||||
version = "0.14.0"
|
||||
version = "0.15.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "65608f937acc725f5b164dcf40f4f0bc5d67dc268ab8a649d3002606718c4588"
|
||||
checksum = "f093b3db6fd194718dcdeea6bd8c829417deae904e3fcc7732dabcd4416d25d8"
|
||||
dependencies = [
|
||||
"ndarray 0.15.6",
|
||||
"ndarray",
|
||||
"rand",
|
||||
"rand_distr",
|
||||
]
|
||||
|
|
@ -4608,9 +4597,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "priority-queue"
|
||||
version = "2.1.1"
|
||||
version = "2.3.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "714c75db297bc88a63783ffc6ab9f830698a6705aa0201416931759ef4c8183d"
|
||||
checksum = "ef08705fa1589a1a59aa924ad77d14722cb0cd97b67dd5004ed5f4a4873fce8d"
|
||||
dependencies = [
|
||||
"autocfg",
|
||||
"equivalent",
|
||||
|
|
@ -5561,9 +5550,9 @@ checksum = "a3f0bf26fd526d2a95683cd0f87bf103b8539e2ca1ef48ce002d67aad59aa0b4"
|
|||
|
||||
[[package]]
|
||||
name = "serde"
|
||||
version = "1.0.217"
|
||||
version = "1.0.219"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "02fc4265df13d6fa1d00ecff087228cc0a2b5f3c0e87e258d8b94a156e984c70"
|
||||
checksum = "5f0e2c6ed6606019b4e29e69dbaba95b11854410e5347d525002456dbbb786b6"
|
||||
dependencies = [
|
||||
"serde_derive",
|
||||
]
|
||||
|
|
@ -5590,9 +5579,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "serde_derive"
|
||||
version = "1.0.217"
|
||||
version = "1.0.219"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5a9bf7cf98d04a2b28aead066b7496853d4779c9cc183c440dbac457641e19a0"
|
||||
checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
|
|
@ -5869,7 +5858,16 @@ version = "0.26.3"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8fec0f0aef304996cf250b31b5a10dee7980c85da9d759361292b8bca5a18f06"
|
||||
dependencies = [
|
||||
"strum_macros",
|
||||
"strum_macros 0.26.4",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "strum"
|
||||
version = "0.27.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f64def088c51c9510a8579e3c5d67c65349dcf755e5479ad3d010aa6454e2c32"
|
||||
dependencies = [
|
||||
"strum_macros 0.27.1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
@ -5885,6 +5883,19 @@ dependencies = [
|
|||
"syn 2.0.96",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "strum_macros"
|
||||
version = "0.27.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c77a8c5abcaf0f9ce05d62342b7d298c346515365c36b673df4ebe3ced01fde8"
|
||||
dependencies = [
|
||||
"heck",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"rustversion",
|
||||
"syn 2.0.96",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "subtle"
|
||||
version = "2.6.1"
|
||||
|
|
@ -6702,6 +6713,12 @@ version = "0.9.0"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1"
|
||||
|
||||
[[package]]
|
||||
name = "unty"
|
||||
version = "0.0.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6d49784317cd0d1ee7ec5c716dd598ec5b4483ea832a2dced265471cc0f690ae"
|
||||
|
||||
[[package]]
|
||||
name = "url"
|
||||
version = "2.5.4"
|
||||
|
|
|
|||
|
|
@ -35,9 +35,9 @@ git = "https://github.com/ankitects/linkcheck.git"
|
|||
rev = "184b2ca50ed39ca43da13f0b830a463861adb9ca"
|
||||
|
||||
[workspace.dependencies.fsrs]
|
||||
# version = "=2.0.3"
|
||||
git = "https://github.com/open-spaced-repetition/fsrs-rs.git"
|
||||
rev = "08d90d1363b0c4722422bf0ef71ed8fd7d053f8a"
|
||||
version = "3.0.0"
|
||||
# git = "https://github.com/open-spaced-repetition/fsrs-rs.git"
|
||||
# rev = "c7717682997a8a6d53d97c7196281e745c5b3c8e"
|
||||
# path = "../open-spaced-repetition/fsrs-rs"
|
||||
|
||||
[workspace.dependencies]
|
||||
|
|
|
|||
|
|
@ -334,7 +334,7 @@
|
|||
},
|
||||
{
|
||||
"name": "bincode",
|
||||
"version": "2.0.0-rc.3",
|
||||
"version": "2.0.1",
|
||||
"authors": "Ty Overby <ty@pre-alpha.com>|Zoey Riordan <zoey@dos.cafe>|Victor Koenders <bincode@trangar.com>",
|
||||
"repository": "https://github.com/bincode-org/bincode",
|
||||
"license": "MIT",
|
||||
|
|
@ -415,7 +415,7 @@
|
|||
},
|
||||
{
|
||||
"name": "burn",
|
||||
"version": "0.16.0",
|
||||
"version": "0.16.1",
|
||||
"authors": "nathanielsimard <nathaniel.simard.42@gmail.com>",
|
||||
"repository": "https://github.com/tracel-ai/burn",
|
||||
"license": "Apache-2.0 OR MIT",
|
||||
|
|
@ -424,7 +424,7 @@
|
|||
},
|
||||
{
|
||||
"name": "burn-autodiff",
|
||||
"version": "0.16.0",
|
||||
"version": "0.16.1",
|
||||
"authors": "nathanielsimard <nathaniel.simard.42@gmail.com>",
|
||||
"repository": "https://github.com/tracel-ai/burn/tree/main/crates/burn-autodiff",
|
||||
"license": "Apache-2.0 OR MIT",
|
||||
|
|
@ -433,7 +433,7 @@
|
|||
},
|
||||
{
|
||||
"name": "burn-candle",
|
||||
"version": "0.16.0",
|
||||
"version": "0.16.1",
|
||||
"authors": "louisfd <louisfd94@gmail.com>",
|
||||
"repository": "https://github.com/tracel-ai/burn/tree/main/crates/burn-candle",
|
||||
"license": "Apache-2.0 OR MIT",
|
||||
|
|
@ -442,7 +442,7 @@
|
|||
},
|
||||
{
|
||||
"name": "burn-common",
|
||||
"version": "0.16.0",
|
||||
"version": "0.16.1",
|
||||
"authors": "Dilshod Tadjibaev (@antimora)",
|
||||
"repository": "https://github.com/tracel-ai/burn/tree/main/crates/burn-common",
|
||||
"license": "Apache-2.0 OR MIT",
|
||||
|
|
@ -451,7 +451,7 @@
|
|||
},
|
||||
{
|
||||
"name": "burn-core",
|
||||
"version": "0.16.0",
|
||||
"version": "0.16.1",
|
||||
"authors": "nathanielsimard <nathaniel.simard.42@gmail.com>",
|
||||
"repository": "https://github.com/tracel-ai/burn/tree/main/crates/burn-core",
|
||||
"license": "Apache-2.0 OR MIT",
|
||||
|
|
@ -460,7 +460,7 @@
|
|||
},
|
||||
{
|
||||
"name": "burn-cuda",
|
||||
"version": "0.16.0",
|
||||
"version": "0.16.1",
|
||||
"authors": "nathanielsimard <nathaniel.simard.42@gmail.com>",
|
||||
"repository": "https://github.com/tracel-ai/burn/tree/main/crates/burn-cuda",
|
||||
"license": "Apache-2.0 OR MIT",
|
||||
|
|
@ -469,7 +469,7 @@
|
|||
},
|
||||
{
|
||||
"name": "burn-dataset",
|
||||
"version": "0.16.0",
|
||||
"version": "0.16.1",
|
||||
"authors": "nathanielsimard <nathaniel.simard.42@gmail.com>",
|
||||
"repository": "https://github.com/tracel-ai/burn/tree/main/crates/burn-dataset",
|
||||
"license": "Apache-2.0 OR MIT",
|
||||
|
|
@ -478,7 +478,7 @@
|
|||
},
|
||||
{
|
||||
"name": "burn-derive",
|
||||
"version": "0.16.0",
|
||||
"version": "0.16.1",
|
||||
"authors": "nathanielsimard <nathaniel.simard.42@gmail.com>",
|
||||
"repository": "https://github.com/tracel-ai/burn/tree/main/crates/burn-derive",
|
||||
"license": "Apache-2.0 OR MIT",
|
||||
|
|
@ -487,7 +487,7 @@
|
|||
},
|
||||
{
|
||||
"name": "burn-hip",
|
||||
"version": "0.16.0",
|
||||
"version": "0.16.1",
|
||||
"authors": "nathanielsimard <nathaniel.simard.42@gmail.com>",
|
||||
"repository": "https://github.com/tracel-ai/burn/tree/main/crates/burn-hip",
|
||||
"license": "Apache-2.0 OR MIT",
|
||||
|
|
@ -496,7 +496,7 @@
|
|||
},
|
||||
{
|
||||
"name": "burn-jit",
|
||||
"version": "0.16.0",
|
||||
"version": "0.16.1",
|
||||
"authors": "nathanielsimard <nathaniel.simard.42@gmail.com>",
|
||||
"repository": "https://github.com/tracel-ai/burn/tree/main/crates/burn-jit",
|
||||
"license": "Apache-2.0 OR MIT",
|
||||
|
|
@ -505,7 +505,7 @@
|
|||
},
|
||||
{
|
||||
"name": "burn-ndarray",
|
||||
"version": "0.16.0",
|
||||
"version": "0.16.1",
|
||||
"authors": "nathanielsimard <nathaniel.simard.42@gmail.com>",
|
||||
"repository": "https://github.com/tracel-ai/burn/tree/main/crates/burn-ndarray",
|
||||
"license": "Apache-2.0 OR MIT",
|
||||
|
|
@ -514,7 +514,7 @@
|
|||
},
|
||||
{
|
||||
"name": "burn-router",
|
||||
"version": "0.16.0",
|
||||
"version": "0.16.1",
|
||||
"authors": "guillaumelagrange <lagrange.guillaume.1@gmail.com>|nathanielsimard <nathaniel.simard.42@gmail.com>",
|
||||
"repository": "https://github.com/tracel-ai/burn/tree/main/crates/burn-router",
|
||||
"license": "Apache-2.0 OR MIT",
|
||||
|
|
@ -523,7 +523,7 @@
|
|||
},
|
||||
{
|
||||
"name": "burn-tensor",
|
||||
"version": "0.16.0",
|
||||
"version": "0.16.1",
|
||||
"authors": "nathanielsimard <nathaniel.simard.42@gmail.com>",
|
||||
"repository": "https://github.com/tracel-ai/burn/tree/main/crates/burn-tensor",
|
||||
"license": "Apache-2.0 OR MIT",
|
||||
|
|
@ -532,7 +532,7 @@
|
|||
},
|
||||
{
|
||||
"name": "burn-train",
|
||||
"version": "0.16.0",
|
||||
"version": "0.16.1",
|
||||
"authors": "nathanielsimard <nathaniel.simard.42@gmail.com>",
|
||||
"repository": "https://github.com/tracel-ai/burn/tree/main/crates/burn-train",
|
||||
"license": "Apache-2.0 OR MIT",
|
||||
|
|
@ -541,7 +541,7 @@
|
|||
},
|
||||
{
|
||||
"name": "burn-wgpu",
|
||||
"version": "0.16.0",
|
||||
"version": "0.16.1",
|
||||
"authors": "nathanielsimard <nathaniel.simard.42@gmail.com>",
|
||||
"repository": "https://github.com/tracel-ai/burn/tree/main/crates/burn-wgpu",
|
||||
"license": "Apache-2.0 OR MIT",
|
||||
|
|
@ -2071,7 +2071,7 @@
|
|||
},
|
||||
{
|
||||
"name": "itertools",
|
||||
"version": "0.12.1",
|
||||
"version": "0.13.0",
|
||||
"authors": "bluss",
|
||||
"repository": "https://github.com/rust-itertools/itertools",
|
||||
"license": "Apache-2.0 OR MIT",
|
||||
|
|
@ -2080,7 +2080,7 @@
|
|||
},
|
||||
{
|
||||
"name": "itertools",
|
||||
"version": "0.13.0",
|
||||
"version": "0.14.0",
|
||||
"authors": "bluss",
|
||||
"repository": "https://github.com/rust-itertools/itertools",
|
||||
"license": "Apache-2.0 OR MIT",
|
||||
|
|
@ -2429,15 +2429,6 @@
|
|||
"license_file": null,
|
||||
"description": "A wrapper over a platform's native TLS implementation"
|
||||
},
|
||||
{
|
||||
"name": "ndarray",
|
||||
"version": "0.15.6",
|
||||
"authors": "Ulrik Sverdrup \"bluss\"|Jim Turner",
|
||||
"repository": "https://github.com/rust-ndarray/ndarray",
|
||||
"license": "Apache-2.0 OR MIT",
|
||||
"license_file": null,
|
||||
"description": "An n-dimensional array for general elements and for numerics. Lightweight array views and slicing; views support chunking and splitting."
|
||||
},
|
||||
{
|
||||
"name": "ndarray",
|
||||
"version": "0.16.1",
|
||||
|
|
@ -2449,7 +2440,7 @@
|
|||
},
|
||||
{
|
||||
"name": "ndarray-rand",
|
||||
"version": "0.14.0",
|
||||
"version": "0.15.0",
|
||||
"authors": "bluss",
|
||||
"repository": "https://github.com/rust-ndarray/ndarray",
|
||||
"license": "Apache-2.0 OR MIT",
|
||||
|
|
@ -2971,7 +2962,7 @@
|
|||
},
|
||||
{
|
||||
"name": "priority-queue",
|
||||
"version": "2.1.1",
|
||||
"version": "2.3.1",
|
||||
"authors": "Gianmarco Garrisi <gianmarcogarrisi@tutanota.com>",
|
||||
"repository": "https://github.com/garro95/priority-queue",
|
||||
"license": "LGPL-3.0-or-later OR MPL-2.0",
|
||||
|
|
@ -3574,7 +3565,7 @@
|
|||
},
|
||||
{
|
||||
"name": "serde",
|
||||
"version": "1.0.217",
|
||||
"version": "1.0.219",
|
||||
"authors": "Erick Tryzelaar <erick.tryzelaar@gmail.com>|David Tolnay <dtolnay@gmail.com>",
|
||||
"repository": "https://github.com/serde-rs/serde",
|
||||
"license": "Apache-2.0 OR MIT",
|
||||
|
|
@ -3601,7 +3592,7 @@
|
|||
},
|
||||
{
|
||||
"name": "serde_derive",
|
||||
"version": "1.0.217",
|
||||
"version": "1.0.219",
|
||||
"authors": "Erick Tryzelaar <erick.tryzelaar@gmail.com>|David Tolnay <dtolnay@gmail.com>",
|
||||
"repository": "https://github.com/serde-rs/serde",
|
||||
"license": "Apache-2.0 OR MIT",
|
||||
|
|
@ -3851,6 +3842,15 @@
|
|||
"license_file": null,
|
||||
"description": "Helpful macros for working with enums and strings"
|
||||
},
|
||||
{
|
||||
"name": "strum",
|
||||
"version": "0.27.1",
|
||||
"authors": "Peter Glotfelty <peter.glotfelty@microsoft.com>",
|
||||
"repository": "https://github.com/Peternator7/strum",
|
||||
"license": "MIT",
|
||||
"license_file": null,
|
||||
"description": "Helpful macros for working with enums and strings"
|
||||
},
|
||||
{
|
||||
"name": "strum_macros",
|
||||
"version": "0.26.4",
|
||||
|
|
@ -3860,6 +3860,15 @@
|
|||
"license_file": null,
|
||||
"description": "Helpful macros for working with enums and strings"
|
||||
},
|
||||
{
|
||||
"name": "strum_macros",
|
||||
"version": "0.27.1",
|
||||
"authors": "Peter Glotfelty <peter.glotfelty@microsoft.com>",
|
||||
"repository": "https://github.com/Peternator7/strum",
|
||||
"license": "MIT",
|
||||
"license_file": null,
|
||||
"description": "Helpful macros for working with enums and strings"
|
||||
},
|
||||
{
|
||||
"name": "subtle",
|
||||
"version": "2.6.1",
|
||||
|
|
@ -4427,6 +4436,15 @@
|
|||
"license_file": null,
|
||||
"description": "Safe, fast, zero-panic, zero-crashing, zero-allocation parsing of untrusted inputs in Rust."
|
||||
},
|
||||
{
|
||||
"name": "unty",
|
||||
"version": "0.0.4",
|
||||
"authors": "Victor Koenders <bincode@trang.ar>",
|
||||
"repository": "https://github.com/bincode-org/unty",
|
||||
"license": "Apache-2.0 OR MIT",
|
||||
"license_file": null,
|
||||
"description": "Explicitly types your generics"
|
||||
},
|
||||
{
|
||||
"name": "url",
|
||||
"version": "2.5.4",
|
||||
|
|
|
|||
|
|
@ -1 +1 @@
|
|||
Subproject commit b295a4c06eb07c9d588581ca1cbd9f7ec7ba27c8
|
||||
Subproject commit 647e3cb0e697c51248201c4fb0df36514e765aa5
|
||||
|
|
@ -499,8 +499,12 @@ deck-config-desired-retention-below-optimal = Your desired retention is below op
|
|||
deck-config-fsrs-simulator-experimental = FSRS simulator (experimental)
|
||||
deck-config-additional-new-cards-to-simulate = Additional new cards to simulate
|
||||
deck-config-simulate = Simulate
|
||||
deck-config-clear-last-simulate = Clear last simulation
|
||||
deck-config-clear-last-simulate = Clear Last Simulation
|
||||
deck-config-fsrs-simulator-radio-count = Reviews
|
||||
deck-config-advanced-settings = Advanced Settings
|
||||
deck-config-smooth-graph = Smooth graph
|
||||
deck-config-suspend-leeches = Suspend leeches
|
||||
deck-config-save-options-to-preset = Save Changes to Preset
|
||||
# Radio button in the FSRS simulation diagram (Deck options -> FSRS) selecting
|
||||
# to show the total number of cards that can be recalled or retrieved on a
|
||||
# specific date.
|
||||
|
|
|
|||
|
|
@ -114,6 +114,7 @@ statistics-counts-separate-suspended-buried-cards = Separate suspended/buried ca
|
|||
|
||||
statistics-true-retention-title = True Retention
|
||||
statistics-true-retention-subtitle = Pass rate of cards with an interval ≥ 1 day.
|
||||
statistics-true-retention-tooltip = If you are using FSRS, your true retention is expected to be close to your desired retention. Please keep in mind that data for a single day is noisy, so it's better to look at monthly data.
|
||||
statistics-true-retention-range = Range
|
||||
statistics-true-retention-pass = Pass
|
||||
statistics-true-retention-fail = Fail
|
||||
|
|
|
|||
|
|
@ -1 +1 @@
|
|||
Subproject commit 620cb519a5b2ef623fef8fc7a0132fb1394fe3fa
|
||||
Subproject commit b3562ed3594d2afa0973edb877cc2f701ff162c3
|
||||
|
|
@ -69,7 +69,6 @@
|
|||
"bootstrap-icons": "^1.10.5",
|
||||
"codemirror": "^5.63.1",
|
||||
"d3": "^7.0.0",
|
||||
"dompurify": "^3.2.5",
|
||||
"fabric": "^5.3.0",
|
||||
"hammerjs": "^2.0.8",
|
||||
"intl-pluralrules": "^2.0.0",
|
||||
|
|
|
|||
|
|
@ -50,6 +50,7 @@ message Card {
|
|||
optional uint32 original_position = 18;
|
||||
optional FsrsMemoryState memory_state = 20;
|
||||
optional float desired_retention = 21;
|
||||
optional float decay = 22;
|
||||
string custom_data = 19;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -122,9 +122,10 @@ message DeckConfig {
|
|||
|
||||
repeated float fsrs_params_4 = 3;
|
||||
repeated float fsrs_params_5 = 5;
|
||||
repeated float fsrs_params_6 = 6;
|
||||
|
||||
// consider saving remaining ones for fsrs param changes
|
||||
reserved 6 to 8;
|
||||
reserved 7 to 8;
|
||||
|
||||
uint32 new_per_day = 9;
|
||||
uint32 reviews_per_day = 10;
|
||||
|
|
|
|||
|
|
@ -33,6 +33,7 @@ service NotetypesService {
|
|||
rpc GetFieldNames(NotetypeId) returns (generic.StringList);
|
||||
rpc RestoreNotetypeToStock(RestoreNotetypeToStockRequest)
|
||||
returns (collection.OpChanges);
|
||||
rpc GetClozeFieldOrds(NotetypeId) returns (GetClozeFieldOrdsResponse);
|
||||
}
|
||||
|
||||
// Implicitly includes any of the above methods that are not listed in the
|
||||
|
|
@ -242,3 +243,7 @@ enum ClozeField {
|
|||
CLOZE_FIELD_TEXT = 0;
|
||||
CLOZE_FIELD_BACK_EXTRA = 1;
|
||||
}
|
||||
|
||||
message GetClozeFieldOrdsResponse {
|
||||
repeated uint32 ords = 1;
|
||||
}
|
||||
|
|
@ -422,17 +422,16 @@ message GetOptimalRetentionParametersResponse {
|
|||
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;
|
||||
repeated float first_rating_prob = 5;
|
||||
repeated float review_rating_prob = 6;
|
||||
float loss_aversion = 7;
|
||||
uint32 learn_limit = 8;
|
||||
uint32 review_limit = 9;
|
||||
repeated float learning_step_transitions = 10;
|
||||
repeated float relearning_step_transitions = 11;
|
||||
repeated float state_rating_costs = 12;
|
||||
uint32 learning_step_count = 13;
|
||||
uint32 relearning_step_count = 14;
|
||||
}
|
||||
|
||||
message EvaluateParamsRequest {
|
||||
|
|
|
|||
|
|
@ -65,6 +65,7 @@ message CardStatsResponse {
|
|||
string preset = 21;
|
||||
optional string original_deck = 22;
|
||||
optional float desired_retention = 23;
|
||||
repeated float fsrs_params = 24;
|
||||
}
|
||||
|
||||
message GraphsRequest {
|
||||
|
|
|
|||
|
|
@ -281,6 +281,10 @@ class ModelManager(DeprecatedNamesMixin):
|
|||
def sort_idx(self, notetype: NotetypeDict) -> int:
|
||||
return notetype["sortf"]
|
||||
|
||||
def cloze_fields(self, mid: NotetypeId) -> Sequence[int]:
|
||||
"""The list of index of fields that are used by cloze deletion in the note type with id `mid`."""
|
||||
return self.col._backend.get_cloze_field_ords(mid)
|
||||
|
||||
# Adding & changing fields
|
||||
##################################################
|
||||
|
||||
|
|
|
|||
|
|
@ -6,3 +6,4 @@ protobuf>=4.21
|
|||
requests[socks]
|
||||
distro; sys_platform != "darwin" and sys_platform != "win32"
|
||||
psutil; sys_platform == "win32"
|
||||
typing_extensions
|
||||
|
|
|
|||
|
|
@ -382,6 +382,10 @@ tomli==2.0.1 \
|
|||
# via
|
||||
# build
|
||||
# pip-tools
|
||||
typing-extensions==4.13.2 \
|
||||
--hash=sha256:a439e7c04b49fec3e5d3e2beaa21755cadbbdc391694e28ccdd36ca4a1408f8c \
|
||||
--hash=sha256:e6c81219bd689f51865d9e372991c540bda33a0379d5573cddb9a3a23f7caaef
|
||||
# via -r requirements.anki.in
|
||||
urllib3==2.2.2 \
|
||||
--hash=sha256:a448b2f64d686155468037e1ace9f2d2199776e17f0a46610480d311f73e3472 \
|
||||
--hash=sha256:dd505485549a7a552833da5e6063639d0d177c04f23bc3864e41e5dc5f612168
|
||||
|
|
|
|||
|
|
@ -597,6 +597,7 @@ typing-extensions==4.12.2 \
|
|||
--hash=sha256:04e5ca0351e0f3f85c6853954072df659d0d13fac324d0072316b67d7794700d \
|
||||
--hash=sha256:1a7ead55c7e559dd4dee8856e3a88b41225abfe1ce8df57b7c13915fe121ffb8
|
||||
# via
|
||||
# -r requirements.anki.in
|
||||
# astroid
|
||||
# black
|
||||
# fluent-syntax
|
||||
|
|
|
|||
|
|
@ -220,6 +220,7 @@ def show(mw: aqt.AnkiQt) -> QDialog:
|
|||
"Market345",
|
||||
"Yuki",
|
||||
"🦙 (siid)",
|
||||
"Mukunda Madhav Dey",
|
||||
)
|
||||
)
|
||||
|
||||
|
|
|
|||
|
|
@ -40,7 +40,7 @@ from aqt import gui_hooks
|
|||
from aqt.log import ADDON_LOGGER_PREFIX, find_addon_logger, get_addon_logs_folder
|
||||
from aqt.qt import *
|
||||
from aqt.utils import (
|
||||
addCloseShortcut,
|
||||
add_close_shortcut,
|
||||
askUser,
|
||||
disable_help_button,
|
||||
getFile,
|
||||
|
|
@ -829,7 +829,7 @@ class AddonsDialog(QDialog):
|
|||
self.setAcceptDrops(True)
|
||||
self.redrawAddons()
|
||||
restoreGeom(self, "addons")
|
||||
addCloseShortcut(self)
|
||||
add_close_shortcut(self)
|
||||
gui_hooks.addons_dialog_will_show(self)
|
||||
self._onAddonSelectionChanged()
|
||||
self.show()
|
||||
|
|
|
|||
|
|
@ -57,6 +57,7 @@ from aqt.undo import UndoActionsInfo
|
|||
from aqt.utils import (
|
||||
HelpPage,
|
||||
KeyboardModifiersPressed,
|
||||
add_close_shortcut,
|
||||
add_ellipsis_to_action_label,
|
||||
current_window,
|
||||
ensure_editor_saved,
|
||||
|
|
@ -1092,6 +1093,7 @@ class Browser(QMainWindow):
|
|||
dialog.setWindowTitle(tr.actions_grade_now())
|
||||
layout = QHBoxLayout()
|
||||
dialog.setLayout(layout)
|
||||
add_close_shortcut(dialog)
|
||||
|
||||
# Add grade buttons
|
||||
for ease, label in [
|
||||
|
|
|
|||
|
|
@ -14,7 +14,7 @@ from anki.errors import NotFoundError
|
|||
from anki.lang import without_unicode_isolation
|
||||
from aqt.qt import *
|
||||
from aqt.utils import (
|
||||
addCloseShortcut,
|
||||
add_close_shortcut,
|
||||
disable_help_button,
|
||||
qconnect,
|
||||
restoreGeom,
|
||||
|
|
@ -53,7 +53,7 @@ class CardInfoDialog(QDialog):
|
|||
self.mw.garbage_collect_on_dialog_finish(self)
|
||||
disable_help_button(self)
|
||||
restoreGeom(self, self.GEOMETRY_KEY, default_size=(800, 800))
|
||||
addCloseShortcut(self)
|
||||
add_close_shortcut(self)
|
||||
setWindowIcon(self)
|
||||
|
||||
self.web: AnkiWebView | None = AnkiWebView(
|
||||
|
|
|
|||
|
|
@ -14,7 +14,6 @@ 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
|
||||
from ..operations.tag import add_tags_to_notes
|
||||
|
|
@ -52,7 +51,6 @@ class FindDuplicatesDialog(QDialog):
|
|||
self._dupes: list[tuple[str, list[NoteId]]] = []
|
||||
|
||||
# links
|
||||
form.webView.set_kind(AnkiWebViewKind.FIND_DUPLICATES)
|
||||
form.webView.set_bridge_command(self._on_duplicate_clicked, context=self)
|
||||
form.webView.stdHtml("", context=self)
|
||||
|
||||
|
|
|
|||
|
|
@ -29,6 +29,7 @@ class SearchContext:
|
|||
browser: aqt.browser.Browser
|
||||
order: bool | str | Column = True
|
||||
reverse: bool = False
|
||||
addon_metadata: dict | None = None
|
||||
# if set, provided ids will be used instead of the regular search
|
||||
ids: Sequence[ItemId] | None = None
|
||||
|
||||
|
|
|
|||
|
|
@ -269,6 +269,7 @@ class DataModel(QAbstractTableModel):
|
|||
# invalid sort column in config
|
||||
context.order = self.columns["noteCrt"]
|
||||
context.reverse = self._state.sort_backwards
|
||||
context.addon_metadata = {}
|
||||
gui_hooks.browser_will_search(context)
|
||||
if context.ids is None:
|
||||
context.ids = self._state.find_items(
|
||||
|
|
|
|||
|
|
@ -15,7 +15,7 @@ from anki.notes import NoteId
|
|||
from aqt.operations.notetype import change_notetype_of_notes
|
||||
from aqt.qt import *
|
||||
from aqt.utils import (
|
||||
addCloseShortcut,
|
||||
add_close_shortcut,
|
||||
disable_help_button,
|
||||
restoreGeom,
|
||||
saveGeom,
|
||||
|
|
@ -49,7 +49,7 @@ class ChangeNotetypeDialog(QDialog):
|
|||
self.setMinimumSize(400, 300)
|
||||
disable_help_button(self)
|
||||
restoreGeom(self, self.TITLE, default_size=(800, 800))
|
||||
addCloseShortcut(self)
|
||||
add_close_shortcut(self)
|
||||
|
||||
self.web = AnkiWebView(kind=AnkiWebViewKind.CHANGE_NOTETYPE)
|
||||
self.web.setVisible(False)
|
||||
|
|
|
|||
|
|
@ -27,6 +27,7 @@ from aqt.sound import av_player, play_clicked_audio
|
|||
from aqt.theme import theme_manager
|
||||
from aqt.utils import (
|
||||
HelpPage,
|
||||
add_close_shortcut,
|
||||
ask_user_dialog,
|
||||
askUser,
|
||||
disable_help_button,
|
||||
|
|
@ -90,6 +91,7 @@ class CardLayout(QDialog):
|
|||
gui_hooks.card_layout_will_show(self)
|
||||
self.redraw_everything()
|
||||
restoreGeom(self, "CardLayout")
|
||||
add_close_shortcut(self)
|
||||
restoreSplitter(self.mainArea, "CardLayoutMainArea")
|
||||
self.setWindowModality(Qt.WindowModality.ApplicationModal)
|
||||
self.show()
|
||||
|
|
|
|||
|
|
@ -15,7 +15,7 @@ from aqt.operations import QueryOp
|
|||
from aqt.operations.scheduling import custom_study
|
||||
from aqt.qt import *
|
||||
from aqt.taglimit import TagLimit
|
||||
from aqt.utils import disable_help_button, tr
|
||||
from aqt.utils import add_close_shortcut, disable_help_button, tr
|
||||
|
||||
RADIO_NEW = 1
|
||||
RADIO_REV = 2
|
||||
|
|
@ -63,6 +63,7 @@ class CustomStudy(QDialog):
|
|||
self.form.setupUi(self)
|
||||
disable_help_button(self)
|
||||
self.setupSignals()
|
||||
add_close_shortcut(self)
|
||||
self.form.radioNew.click()
|
||||
self.open()
|
||||
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@ from anki.decks import DeckDict
|
|||
from aqt.operations import QueryOp
|
||||
from aqt.operations.deck import update_deck_dict
|
||||
from aqt.qt import *
|
||||
from aqt.utils import addCloseShortcut, disable_help_button, restoreGeom, saveGeom, tr
|
||||
from aqt.utils import add_close_shortcut, disable_help_button, restoreGeom, saveGeom, tr
|
||||
|
||||
|
||||
class DeckDescriptionDialog(QDialog):
|
||||
|
|
@ -45,7 +45,7 @@ class DeckDescriptionDialog(QDialog):
|
|||
self.setMinimumWidth(400)
|
||||
disable_help_button(self)
|
||||
restoreGeom(self, self.TITLE)
|
||||
addCloseShortcut(self)
|
||||
add_close_shortcut(self)
|
||||
|
||||
box = QVBoxLayout()
|
||||
|
||||
|
|
|
|||
|
|
@ -13,7 +13,7 @@ from aqt import gui_hooks
|
|||
from aqt.qt import *
|
||||
from aqt.utils import (
|
||||
KeyboardModifiersPressed,
|
||||
addCloseShortcut,
|
||||
add_close_shortcut,
|
||||
disable_help_button,
|
||||
restoreGeom,
|
||||
saveGeom,
|
||||
|
|
@ -42,7 +42,7 @@ class DeckOptionsDialog(QDialog):
|
|||
self.setMinimumWidth(400)
|
||||
disable_help_button(self)
|
||||
restoreGeom(self, self.TITLE, default_size=(800, 800))
|
||||
addCloseShortcut(self)
|
||||
add_close_shortcut(self)
|
||||
|
||||
self.web = AnkiWebView(kind=AnkiWebViewKind.DECK_OPTIONS)
|
||||
self.web.load_sveltekit_page(f"deck-options/{self._deck['id']}")
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@ from anki.collection import OpChanges
|
|||
from anki.errors import NotFoundError
|
||||
from aqt import gui_hooks
|
||||
from aqt.qt import *
|
||||
from aqt.utils import restoreGeom, saveGeom, tr
|
||||
from aqt.utils import add_close_shortcut, restoreGeom, saveGeom, tr
|
||||
|
||||
|
||||
class EditCurrent(QMainWindow):
|
||||
|
|
@ -36,6 +36,7 @@ class EditCurrent(QMainWindow):
|
|||
close_button = self.form.buttonBox.button(QDialogButtonBox.StandardButton.Close)
|
||||
assert close_button is not None
|
||||
close_button.setShortcut(QKeySequence("Ctrl+Return"))
|
||||
add_close_shortcut(self)
|
||||
# qt5.14+ doesn't handle numpad enter on Windows
|
||||
self.compat_add_shorcut = QShortcut(QKeySequence("Ctrl+Enter"), self)
|
||||
qconnect(self.compat_add_shorcut.activated, close_button.click)
|
||||
|
|
|
|||
|
|
@ -320,7 +320,6 @@ require("anki/ui").loaded.then(() => require("anki/NoteEditor").instances[0].too
|
|||
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:
|
||||
|
|
@ -328,11 +327,11 @@ require("anki/ui").loaded.then(() => require("anki/NoteEditor").instances[0].too
|
|||
|
||||
return f"""<button tabindex=-1
|
||||
{id_attribute_assignment}
|
||||
class="{class_attribute}"
|
||||
class="anki-addon-button {class_attribute}"
|
||||
type="button"
|
||||
title="{title_attribute}"
|
||||
onclick="pycmd('{cmd}');{cmd_to_toggle_button}return false;"
|
||||
onmousedown="window.event.preventDefault();"
|
||||
data-cantoggle="{int(toggleable)}"
|
||||
data-command="{cmd}"
|
||||
>
|
||||
{image_element}
|
||||
{label_element}
|
||||
|
|
@ -556,6 +555,8 @@ require("anki/ui").loaded.then(() => require("anki/NoteEditor").instances[0].too
|
|||
note_type = self.note_type()
|
||||
flds = note_type["flds"]
|
||||
collapsed = [fld["collapsed"] for fld in flds]
|
||||
cloze_fields_ords = self.mw.col.models.cloze_fields(self.note.mid)
|
||||
cloze_fields = [ord in cloze_fields_ords for ord in range(len(flds))]
|
||||
plain_texts = [fld.get("plainText", False) for fld in flds]
|
||||
descriptions = [fld.get("description", "") for fld in flds]
|
||||
notetype_meta = {"id": self.note.mid, "modTime": note_type["mod"]}
|
||||
|
|
@ -585,6 +586,7 @@ require("anki/ui").loaded.then(() => require("anki/NoteEditor").instances[0].too
|
|||
setIsImageOcclusion({json.dumps(self.current_notetype_is_image_occlusion())});
|
||||
setNotetypeMeta({json.dumps(notetype_meta)});
|
||||
setCollapsed({json.dumps(collapsed)});
|
||||
setClozeFields({json.dumps(cloze_fields)});
|
||||
setPlainTexts({json.dumps(plain_texts)});
|
||||
setDescriptions({json.dumps(descriptions)});
|
||||
setFonts({json.dumps(self.fonts())});
|
||||
|
|
|
|||
|
|
@ -15,7 +15,6 @@ from anki.collection import EmptyCardsReport
|
|||
from aqt import gui_hooks
|
||||
from aqt.qt import QDialog, QDialogButtonBox, qconnect
|
||||
from aqt.utils import disable_help_button, restoreGeom, saveGeom, tooltip, tr
|
||||
from aqt.webview import AnkiWebViewKind
|
||||
|
||||
|
||||
def show_empty_cards(mw: aqt.main.AnkiQt) -> None:
|
||||
|
|
@ -47,7 +46,6 @@ class EmptyCardsDialog(QDialog):
|
|||
self.setWindowTitle(tr.empty_cards_window_title())
|
||||
disable_help_button(self)
|
||||
self.form.keep_notes.setText(tr.empty_cards_preserve_notes_checkbox())
|
||||
self.form.webview.set_kind(AnkiWebViewKind.EMPTY_CARDS)
|
||||
self.form.webview.set_bridge_command(self._on_note_link_clicked, self)
|
||||
|
||||
gui_hooks.empty_cards_will_show(self)
|
||||
|
|
|
|||
|
|
@ -19,6 +19,7 @@ from aqt import gui_hooks
|
|||
from aqt.errors import show_exception
|
||||
from aqt.qt import *
|
||||
from aqt.utils import (
|
||||
add_close_shortcut,
|
||||
checkInvalidFilename,
|
||||
disable_help_button,
|
||||
getSaveFile,
|
||||
|
|
@ -46,6 +47,7 @@ class ExportDialog(QDialog):
|
|||
self.cids = cids
|
||||
disable_help_button(self)
|
||||
self.setup(did)
|
||||
add_close_shortcut(self)
|
||||
self.exec()
|
||||
|
||||
def setup(self, did: DeckId | None) -> None:
|
||||
|
|
|
|||
|
|
@ -15,6 +15,7 @@ from aqt.qt import *
|
|||
from aqt.schema_change_tracker import ChangeTracker
|
||||
from aqt.utils import (
|
||||
HelpPage,
|
||||
add_close_shortcut,
|
||||
askUser,
|
||||
disable_help_button,
|
||||
getOnlyText,
|
||||
|
|
@ -50,6 +51,7 @@ class FieldDialog(QDialog):
|
|||
without_unicode_isolation(tr.fields_fields_for(val=self.model["name"]))
|
||||
)
|
||||
|
||||
add_close_shortcut(self)
|
||||
disable_help_button(self)
|
||||
help_button = self.form.buttonBox.button(QDialogButtonBox.StandardButton.Help)
|
||||
assert help_button is not None
|
||||
|
|
|
|||
|
|
@ -30,7 +30,7 @@
|
|||
<number>0</number>
|
||||
</property>
|
||||
<item>
|
||||
<widget class="AnkiWebView" name="webview" native="true">
|
||||
<widget class="EmptyCardsWebView" name="webview" native="true">
|
||||
<property name="url" stdset="0">
|
||||
<url>
|
||||
<string notr="true">about:blank</string>
|
||||
|
|
@ -81,7 +81,7 @@
|
|||
</widget>
|
||||
<customwidgets>
|
||||
<customwidget>
|
||||
<class>AnkiWebView</class>
|
||||
<class>EmptyCardsWebView</class>
|
||||
<extends>QWidget</extends>
|
||||
<header location="global">aqt/webview</header>
|
||||
<container>1</container>
|
||||
|
|
|
|||
|
|
@ -73,7 +73,7 @@
|
|||
<number>0</number>
|
||||
</property>
|
||||
<item>
|
||||
<widget class="AnkiWebView" name="webView" native="true">
|
||||
<widget class="FindDupesWebView" name="webView" native="true">
|
||||
<property name="url" stdset="0">
|
||||
<url>
|
||||
<string notr="true">about:blank</string>
|
||||
|
|
@ -98,7 +98,7 @@
|
|||
</widget>
|
||||
<customwidgets>
|
||||
<customwidget>
|
||||
<class>AnkiWebView</class>
|
||||
<class>FindDupesWebView</class>
|
||||
<extends>QWidget</extends>
|
||||
<header location="global">aqt/webview</header>
|
||||
<container>1</container>
|
||||
|
|
|
|||
|
|
@ -30,7 +30,7 @@
|
|||
<number>0</number>
|
||||
</property>
|
||||
<item>
|
||||
<widget class="AnkiWebView" name="web" native="true">
|
||||
<widget class="StatsWebView" name="web" native="true">
|
||||
<property name="url" stdset="0">
|
||||
<url>
|
||||
<string notr="true">about:blank</string>
|
||||
|
|
@ -146,7 +146,7 @@
|
|||
</widget>
|
||||
<customwidgets>
|
||||
<customwidget>
|
||||
<class>AnkiWebView</class>
|
||||
<class>StatsWebView</class>
|
||||
<extends>QWidget</extends>
|
||||
<header location="global">aqt/webview</header>
|
||||
<container>1</container>
|
||||
|
|
|
|||
|
|
@ -27,6 +27,7 @@ from aqt.operations import QueryOp
|
|||
from aqt.progress import ProgressUpdate
|
||||
from aqt.qt import *
|
||||
from aqt.utils import (
|
||||
add_close_shortcut,
|
||||
checkInvalidFilename,
|
||||
disable_help_button,
|
||||
getSaveFile,
|
||||
|
|
@ -53,6 +54,7 @@ class ExportDialog(QDialog):
|
|||
self.nids = nids
|
||||
disable_help_button(self)
|
||||
self.setup(did)
|
||||
add_close_shortcut(self)
|
||||
self.open()
|
||||
|
||||
def setup(self, did: DeckId | None) -> None:
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@ import aqt.deckconf
|
|||
import aqt.main
|
||||
import aqt.operations
|
||||
from aqt.qt import *
|
||||
from aqt.utils import addCloseShortcut, disable_help_button, restoreGeom, saveGeom, tr
|
||||
from aqt.utils import add_close_shortcut, disable_help_button, restoreGeom, saveGeom, tr
|
||||
from aqt.webview import AnkiWebView, AnkiWebViewKind
|
||||
|
||||
|
||||
|
|
@ -62,7 +62,7 @@ class ImportDialog(QDialog):
|
|||
self.setMinimumSize(*self.MIN_SIZE)
|
||||
disable_help_button(self)
|
||||
restoreGeom(self, self.args.title, default_size=self.DEFAULT_SIZE)
|
||||
addCloseShortcut(self)
|
||||
add_close_shortcut(self)
|
||||
|
||||
self.web: AnkiWebView | None = AnkiWebView(kind=self.args.kind)
|
||||
self.web.setVisible(False)
|
||||
|
|
|
|||
|
|
@ -141,14 +141,17 @@ class MediaServer(threading.Thread):
|
|||
) -> None:
|
||||
self._legacy_pages[id] = LegacyPage(html, context)
|
||||
|
||||
def get_page(self, id: int) -> LegacyPage | None:
|
||||
return self._legacy_pages.get(id)
|
||||
|
||||
def get_page_html(self, id: int) -> str | None:
|
||||
if page := self._legacy_pages.get(id):
|
||||
if page := self.get_page(id):
|
||||
return page.html
|
||||
else:
|
||||
return None
|
||||
|
||||
def get_page_context(self, id: int) -> PageContext | None:
|
||||
if page := self._legacy_pages.get(id):
|
||||
if page := self.get_page(id):
|
||||
return page.context
|
||||
else:
|
||||
return None
|
||||
|
|
@ -742,8 +745,21 @@ def _handle_dynamic_request(req: DynamicRequest) -> Response:
|
|||
|
||||
def legacy_page_data() -> Response:
|
||||
id = int(request.args["id"])
|
||||
if html := aqt.mw.mediaServer.get_page_html(id):
|
||||
return Response(html, mimetype="text/html")
|
||||
page = aqt.mw.mediaServer.get_page(id)
|
||||
if page:
|
||||
response = Response(page.html, mimetype="text/html")
|
||||
# Prevent JS in field content from being executed in the editor, as it would
|
||||
# have access to our internal API, and is a security risk.
|
||||
if page.context == PageContext.EDITOR:
|
||||
port = aqt.mw.mediaServer.getPort()
|
||||
csp_paths = (
|
||||
f"http://127.0.0.1:{port}/_anki/",
|
||||
f"http://127.0.0.1:{port}/_addons/",
|
||||
)
|
||||
response.headers["Content-Security-Policy"] = (
|
||||
f"script-src {' '.join(csp_paths)}"
|
||||
)
|
||||
return response
|
||||
else:
|
||||
return _text_response(HTTPStatus.NOT_FOUND, "page not found")
|
||||
|
||||
|
|
@ -752,7 +768,10 @@ _APIKEY = "".join(random.choices(string.ascii_letters + string.digits, k=32))
|
|||
|
||||
|
||||
def _have_api_access() -> bool:
|
||||
return request.headers.get("Authorization") == f"Bearer {_APIKEY}"
|
||||
return (
|
||||
request.headers.get("Authorization") == f"Bearer {_APIKEY}"
|
||||
or os.environ.get("ANKI_API_HOST") == "0.0.0.0"
|
||||
)
|
||||
|
||||
|
||||
# this currently only handles a single method; in the future, idempotent
|
||||
|
|
|
|||
|
|
@ -24,7 +24,7 @@ from anki.scheduler.v3 import CardAnswer
|
|||
from anki.scheduler.v3 import Scheduler as V3Scheduler
|
||||
from aqt.operations import CollectionOp
|
||||
from aqt.qt import *
|
||||
from aqt.utils import disable_help_button, getText, tooltip, tr
|
||||
from aqt.utils import add_close_shortcut, disable_help_button, getText, tooltip, tr
|
||||
|
||||
|
||||
def set_due_date_dialog(
|
||||
|
|
@ -104,6 +104,7 @@ def forget_cards(
|
|||
|
||||
dialog = QDialog(parent)
|
||||
disable_help_button(dialog)
|
||||
add_close_shortcut(dialog)
|
||||
form = aqt.forms.forget.Ui_Dialog()
|
||||
form.setupUi(dialog)
|
||||
|
||||
|
|
@ -153,6 +154,7 @@ def reposition_new_cards_dialog(
|
|||
|
||||
dialog = QDialog(parent)
|
||||
disable_help_button(dialog)
|
||||
add_close_shortcut(dialog)
|
||||
dialog.setWindowModality(Qt.WindowModality.WindowModal)
|
||||
form = aqt.forms.reposition.Ui_Dialog()
|
||||
form.setupUi(dialog)
|
||||
|
|
|
|||
|
|
@ -22,6 +22,7 @@ from aqt.sync import sync_login
|
|||
from aqt.theme import Theme
|
||||
from aqt.utils import (
|
||||
HelpPage,
|
||||
add_close_shortcut,
|
||||
askUser,
|
||||
disable_help_button,
|
||||
is_win,
|
||||
|
|
@ -64,6 +65,7 @@ class Preferences(QDialog):
|
|||
self.setup_profile()
|
||||
self.setup_global()
|
||||
self.setup_configurable_answer_keys()
|
||||
add_close_shortcut(self)
|
||||
self.show()
|
||||
|
||||
def setup_configurable_answer_keys(self):
|
||||
|
|
|
|||
|
|
@ -16,7 +16,7 @@ from aqt.operations.deck import set_current_deck
|
|||
from aqt.qt import *
|
||||
from aqt.theme import theme_manager
|
||||
from aqt.utils import (
|
||||
addCloseShortcut,
|
||||
add_close_shortcut,
|
||||
disable_help_button,
|
||||
getSaveFile,
|
||||
maybeHideClose,
|
||||
|
|
@ -25,7 +25,7 @@ from aqt.utils import (
|
|||
tooltip,
|
||||
tr,
|
||||
)
|
||||
from aqt.webview import AnkiWebViewKind
|
||||
from aqt.webview import LegacyStatsWebView
|
||||
|
||||
|
||||
class NewDeckStats(QDialog):
|
||||
|
|
@ -69,9 +69,8 @@ class NewDeckStats(QDialog):
|
|||
assert b is not None
|
||||
b.setAutoDefault(False)
|
||||
maybeHideClose(self.form.buttonBox)
|
||||
addCloseShortcut(self)
|
||||
add_close_shortcut(self)
|
||||
gui_hooks.stats_dialog_will_show(self)
|
||||
self.form.web.set_kind(AnkiWebViewKind.DECK_STATS)
|
||||
self.form.web.hide_while_preserving_layout()
|
||||
self.show()
|
||||
self.refresh()
|
||||
|
|
@ -154,6 +153,9 @@ class DeckStats(QDialog):
|
|||
self.name = "deckStats"
|
||||
self.period = 0
|
||||
self.form = aqt.forms.stats.Ui_Dialog()
|
||||
# Hack: Switch out web views dynamically to avoid maintaining multiple
|
||||
# Qt forms for different versions of the stats dialog.
|
||||
self.form.web = LegacyStatsWebView(self.mw)
|
||||
self.oldPos = None
|
||||
self.wholeCollection = False
|
||||
self.setMinimumWidth(700)
|
||||
|
|
@ -180,7 +182,7 @@ class DeckStats(QDialog):
|
|||
qconnect(f.year.clicked, lambda: self.changePeriod(1))
|
||||
qconnect(f.life.clicked, lambda: self.changePeriod(2))
|
||||
maybeHideClose(self.form.buttonBox)
|
||||
addCloseShortcut(self)
|
||||
add_close_shortcut(self)
|
||||
gui_hooks.stats_dialog_old_will_show(self)
|
||||
self.show()
|
||||
self.refresh()
|
||||
|
|
@ -232,7 +234,6 @@ class DeckStats(QDialog):
|
|||
stats = self.mw.col.stats()
|
||||
stats.wholeCollection = self.wholeCollection
|
||||
self.report = stats.report(type=self.period)
|
||||
self.form.web.set_kind(AnkiWebViewKind.LEGACY_DECK_STATS)
|
||||
self.form.web.stdHtml(
|
||||
f"<html><body>{self.report}</body></html>",
|
||||
js=["js/vendor/jquery.min.js", "js/vendor/plot.js"],
|
||||
|
|
|
|||
|
|
@ -16,6 +16,7 @@ from aqt.qt import *
|
|||
from aqt.utils import (
|
||||
HelpPage,
|
||||
HelpPageArgument,
|
||||
add_close_shortcut,
|
||||
disable_help_button,
|
||||
openHelp,
|
||||
restoreGeom,
|
||||
|
|
@ -52,6 +53,7 @@ class StudyDeck(QDialog):
|
|||
gui_hooks.state_did_reset.append(self.onReset)
|
||||
self.geomKey = f"studyDeck-{geomKey}"
|
||||
restoreGeom(self, self.geomKey)
|
||||
add_close_shortcut(self)
|
||||
disable_help_button(self)
|
||||
if not cancel:
|
||||
self.form.buttonBox.removeButton(
|
||||
|
|
|
|||
|
|
@ -28,6 +28,7 @@ from aqt.qt import (
|
|||
qconnect,
|
||||
)
|
||||
from aqt.utils import (
|
||||
add_close_shortcut,
|
||||
ask_user_dialog,
|
||||
disable_help_button,
|
||||
show_warning,
|
||||
|
|
@ -380,6 +381,7 @@ def get_id_and_pass_from_user(
|
|||
qconnect(bb.accepted, diag.accept)
|
||||
qconnect(bb.rejected, diag.reject)
|
||||
vbox.addWidget(bb)
|
||||
add_close_shortcut(diag)
|
||||
diag.setLayout(vbox)
|
||||
diag.adjustSize()
|
||||
diag.show()
|
||||
|
|
|
|||
|
|
@ -560,6 +560,7 @@ def getText(
|
|||
d = GetTextDialog(
|
||||
parent, prompt, help=help, edit=edit, default=default, title=title, **kwargs
|
||||
)
|
||||
add_close_shortcut(d)
|
||||
d.setWindowModality(Qt.WindowModality.WindowModal)
|
||||
if geomKey:
|
||||
restoreGeom(d, geomKey)
|
||||
|
|
@ -988,14 +989,6 @@ def maybeHideClose(bbox: QDialogButtonBox) -> None:
|
|||
bbox.removeButton(b)
|
||||
|
||||
|
||||
def addCloseShortcut(widg: QDialog) -> None:
|
||||
if not is_mac:
|
||||
return
|
||||
shortcut = QShortcut(QKeySequence("Ctrl+W"), widg)
|
||||
qconnect(shortcut.activated, widg.reject)
|
||||
setattr(widg, "_closeShortcut", shortcut)
|
||||
|
||||
|
||||
def add_close_shortcut(widg: QWidget) -> None:
|
||||
if not is_mac:
|
||||
return
|
||||
|
|
|
|||
|
|
@ -10,7 +10,9 @@ import re
|
|||
import sys
|
||||
from collections.abc import Callable, Sequence
|
||||
from enum import Enum
|
||||
from typing import TYPE_CHECKING, Any, cast
|
||||
from typing import TYPE_CHECKING, Any, Type, cast
|
||||
|
||||
from typing_extensions import TypedDict, Unpack
|
||||
|
||||
import anki
|
||||
import anki.lang
|
||||
|
|
@ -360,7 +362,9 @@ class AnkiWebView(QWebEngineView):
|
|||
kind: AnkiWebViewKind = AnkiWebViewKind.DEFAULT,
|
||||
) -> None:
|
||||
QWebEngineView.__init__(self, parent=parent)
|
||||
self.set_kind(kind)
|
||||
self._kind = kind
|
||||
self.set_title(kind.value)
|
||||
self.setPage(AnkiWebPage(self._onBridgeCmd, kind, self))
|
||||
# reduce flicker
|
||||
self.page().setBackgroundColor(theme_manager.qcolor(colors.CANVAS))
|
||||
|
||||
|
|
@ -390,17 +394,6 @@ class AnkiWebView(QWebEngineView):
|
|||
"""
|
||||
)
|
||||
|
||||
def set_kind(self, kind: AnkiWebViewKind) -> None:
|
||||
self._kind = kind
|
||||
self.set_title(kind.value)
|
||||
# this is an ugly hack to avoid breakages caused by
|
||||
# creating a default webview then immediately calling set_kind, which results
|
||||
# in the creation of two pages, and the second fails as the domDone
|
||||
# signal from the first one is received
|
||||
if kind != AnkiWebViewKind.DEFAULT:
|
||||
self.setPage(AnkiWebPage(self._onBridgeCmd, kind, self))
|
||||
self.page().setBackgroundColor(theme_manager.qcolor(colors.CANVAS))
|
||||
|
||||
def page(self) -> AnkiWebPage:
|
||||
return cast(AnkiWebPage, super().page())
|
||||
|
||||
|
|
@ -968,3 +961,53 @@ html {{ {font} }}
|
|||
@deprecated(info="use theme_manager.qcolor() instead")
|
||||
def get_window_bg_color(self, night_mode: bool | None = None) -> QColor:
|
||||
return theme_manager.qcolor(colors.CANVAS)
|
||||
|
||||
|
||||
# Pre-configured classes for use in Qt Designer
|
||||
##########################################################################
|
||||
|
||||
|
||||
class _AnkiWebViewKwargs(TypedDict, total=False):
|
||||
parent: QWidget | None
|
||||
title: str
|
||||
kind: AnkiWebViewKind
|
||||
|
||||
|
||||
def _create_ankiwebview_subclass(
|
||||
name: str,
|
||||
/,
|
||||
**fixed_kwargs: Unpack[_AnkiWebViewKwargs],
|
||||
) -> Type[AnkiWebView]:
|
||||
|
||||
def __init__(self, *args: Any, **kwargs: _AnkiWebViewKwargs) -> None:
|
||||
# user‑supplied kwargs override fixed kwargs
|
||||
merged = cast(_AnkiWebViewKwargs, {**fixed_kwargs, **kwargs})
|
||||
AnkiWebView.__init__(self, *args, **merged)
|
||||
|
||||
__init__.__qualname__ = f"{name}.__init__"
|
||||
if fixed_kwargs:
|
||||
__init__.__doc__ = (
|
||||
f"Auto‑generated wrapper that pre‑sets "
|
||||
f"{', '.join(f'{k}={v!r}' for k, v in fixed_kwargs.items())}."
|
||||
)
|
||||
|
||||
cls: Type[AnkiWebView] = type(name, (AnkiWebView,), {"__init__": __init__})
|
||||
|
||||
return cls
|
||||
|
||||
|
||||
# These subclasses are used in Qt Designer UI files to allow for configuring
|
||||
# web views at initialization time (custom widgets can otherwise only be
|
||||
# initialized with the default constructor)
|
||||
StatsWebView = _create_ankiwebview_subclass(
|
||||
"StatsWebView", kind=AnkiWebViewKind.DECK_STATS
|
||||
)
|
||||
LegacyStatsWebView = _create_ankiwebview_subclass(
|
||||
"LegacyStatsWebView", kind=AnkiWebViewKind.LEGACY_DECK_STATS
|
||||
)
|
||||
EmptyCardsWebView = _create_ankiwebview_subclass(
|
||||
"EmptyCardsWebView", kind=AnkiWebViewKind.EMPTY_CARDS
|
||||
)
|
||||
FindDupesWebView = _create_ankiwebview_subclass(
|
||||
"FindDupesWebView", kind=AnkiWebViewKind.FIND_DUPLICATES
|
||||
)
|
||||
|
|
|
|||
|
|
@ -549,6 +549,10 @@ hooks = [
|
|||
You can modify context.search to change the text that is sent to the
|
||||
searching backend.
|
||||
|
||||
If you need to pass metadata to the browser_did_search hook, you can
|
||||
do it with context.addon_metadata. For example, to trigger filtering
|
||||
based on a new custom filter.
|
||||
|
||||
If you set context.ids to a list of ids, the regular search will
|
||||
not be performed, and the provided ids will be used instead.
|
||||
|
||||
|
|
@ -578,6 +582,9 @@ hooks = [
|
|||
backend did not recognize will be returned as an empty string, and can be
|
||||
replaced with custom content.
|
||||
|
||||
You can retrieve metadata passed from browser_will_search with
|
||||
context.addon_metadata (for example to trigger post-processing filtering).
|
||||
|
||||
Columns is a list of string values identifying what each column in the row
|
||||
represents.
|
||||
""",
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@
|
|||
use std::sync::Arc;
|
||||
|
||||
use fsrs::FSRS;
|
||||
use fsrs::FSRS5_DEFAULT_DECAY;
|
||||
use itertools::Itertools;
|
||||
use strum::Display;
|
||||
use strum::EnumIter;
|
||||
|
|
@ -541,10 +542,13 @@ impl RowContext {
|
|||
.memory_state
|
||||
.as_ref()
|
||||
.zip(self.cards[0].days_since_last_review(&self.timing))
|
||||
.map(|(state, days_elapsed)| {
|
||||
let r = FSRS::new(None)
|
||||
.unwrap()
|
||||
.current_retrievability((*state).into(), days_elapsed);
|
||||
.zip(Some(self.cards[0].decay.unwrap_or(FSRS5_DEFAULT_DECAY)))
|
||||
.map(|((state, days_elapsed), decay)| {
|
||||
let r = FSRS::new(None).unwrap().current_retrievability(
|
||||
(*state).into(),
|
||||
days_elapsed,
|
||||
decay,
|
||||
);
|
||||
format!("{:.0}%", r * 100.)
|
||||
})
|
||||
.unwrap_or_default()
|
||||
|
|
|
|||
|
|
@ -95,6 +95,7 @@ pub struct Card {
|
|||
pub(crate) original_position: Option<u32>,
|
||||
pub(crate) memory_state: Option<FsrsMemoryState>,
|
||||
pub(crate) desired_retention: Option<f32>,
|
||||
pub(crate) decay: Option<f32>,
|
||||
/// JSON object or empty; exposed through the reviewer for persisting custom
|
||||
/// state
|
||||
pub(crate) custom_data: String,
|
||||
|
|
@ -145,6 +146,7 @@ impl Default for Card {
|
|||
original_position: None,
|
||||
memory_state: None,
|
||||
desired_retention: None,
|
||||
decay: None,
|
||||
custom_data: String::new(),
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -106,6 +106,7 @@ impl TryFrom<anki_proto::cards::Card> for Card {
|
|||
original_position: c.original_position,
|
||||
memory_state: c.memory_state.map(Into::into),
|
||||
desired_retention: c.desired_retention,
|
||||
decay: c.decay,
|
||||
custom_data: c.custom_data,
|
||||
})
|
||||
}
|
||||
|
|
@ -134,6 +135,7 @@ impl From<Card> for anki_proto::cards::Card {
|
|||
original_position: c.original_position,
|
||||
memory_state: c.memory_state.map(Into::into),
|
||||
desired_retention: c.desired_retention,
|
||||
decay: c.decay,
|
||||
custom_data: c.custom_data,
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -43,6 +43,7 @@ mod mathjax_caps {
|
|||
|
||||
#[derive(Debug)]
|
||||
enum Token<'a> {
|
||||
// The parameter is the cloze number as is appears in the field content.
|
||||
OpenCloze(u16),
|
||||
Text(&'a str),
|
||||
CloseCloze,
|
||||
|
|
@ -114,6 +115,7 @@ enum TextOrCloze<'a> {
|
|||
|
||||
#[derive(Debug)]
|
||||
struct ExtractedCloze<'a> {
|
||||
// `ordinal` is the cloze number as is appears in the field content.
|
||||
ordinal: u16,
|
||||
nodes: Vec<TextOrCloze<'a>>,
|
||||
hint: Option<&'a str>,
|
||||
|
|
@ -409,12 +411,14 @@ pub fn expand_clozes_to_reveal_latex(text: &str) -> String {
|
|||
buf
|
||||
}
|
||||
|
||||
// Whether `text` contains any cloze number above 0
|
||||
pub(crate) fn contains_cloze(text: &str) -> bool {
|
||||
parse_text_with_clozes(text)
|
||||
.iter()
|
||||
.any(|node| matches!(node, TextOrCloze::Cloze(e) if e.ordinal != 0))
|
||||
}
|
||||
|
||||
/// Returns the set of cloze number as they appear in the fields's content.
|
||||
pub fn cloze_numbers_in_string(html: &str) -> HashSet<u16> {
|
||||
let mut set = HashSet::with_capacity(4);
|
||||
add_cloze_numbers_in_string(html, &mut set);
|
||||
|
|
@ -432,11 +436,21 @@ fn add_cloze_numbers_in_text_with_clozes(nodes: &[TextOrCloze], set: &mut HashSe
|
|||
}
|
||||
}
|
||||
|
||||
/// Add to `set` the cloze numbers as they appear in `field`.
|
||||
#[allow(clippy::implicit_hasher)]
|
||||
pub fn add_cloze_numbers_in_string(field: &str, set: &mut HashSet<u16>) {
|
||||
add_cloze_numbers_in_text_with_clozes(&parse_text_with_clozes(field), set)
|
||||
}
|
||||
|
||||
/// The set of cloze numbers as they appear in any of the fields from `fields`.
|
||||
pub fn cloze_number_in_fields(fields: impl IntoIterator<Item: AsRef<str>>) -> HashSet<u16> {
|
||||
let mut set = HashSet::with_capacity(4);
|
||||
for field in fields {
|
||||
add_cloze_numbers_in_string(field.as_ref(), &mut set);
|
||||
}
|
||||
set
|
||||
}
|
||||
|
||||
fn strip_html_inside_mathjax(text: &str) -> Cow<str> {
|
||||
MATHJAX.replace_all(text, |caps: &Captures| -> String {
|
||||
format!(
|
||||
|
|
|
|||
|
|
@ -76,6 +76,7 @@ const DEFAULT_DECK_CONFIG_INNER: DeckConfigInner = DeckConfigInner {
|
|||
bury_interday_learning: false,
|
||||
fsrs_params_4: vec![],
|
||||
fsrs_params_5: vec![],
|
||||
fsrs_params_6: vec![],
|
||||
desired_retention: 0.9,
|
||||
other: Vec::new(),
|
||||
historical_retention: 0.9,
|
||||
|
|
@ -107,9 +108,11 @@ impl DeckConfig {
|
|||
self.usn = usn;
|
||||
}
|
||||
|
||||
/// Retrieve the FSRS 5.0 params, falling back on 4.x ones.
|
||||
/// Retrieve the FSRS 6.0 params, falling back on 5.0 or 4.x ones.
|
||||
pub fn fsrs_params(&self) -> &Vec<f32> {
|
||||
if self.inner.fsrs_params_5.len() == 19 {
|
||||
if self.inner.fsrs_params_6.len() == 21 {
|
||||
&self.inner.fsrs_params_6
|
||||
} else if self.inner.fsrs_params_5.len() == 19 {
|
||||
&self.inner.fsrs_params_5
|
||||
} else {
|
||||
&self.inner.fsrs_params_4
|
||||
|
|
|
|||
|
|
@ -74,6 +74,8 @@ pub struct DeckConfSchema11 {
|
|||
#[serde(default)]
|
||||
fsrs_params_5: Vec<f32>,
|
||||
#[serde(default)]
|
||||
fsrs_params_6: Vec<f32>,
|
||||
#[serde(default)]
|
||||
desired_retention: f32,
|
||||
#[serde(default)]
|
||||
ignore_revlogs_before_date: String,
|
||||
|
|
@ -310,6 +312,7 @@ impl Default for DeckConfSchema11 {
|
|||
bury_interday_learning: false,
|
||||
fsrs_params_4: vec![],
|
||||
fsrs_params_5: vec![],
|
||||
fsrs_params_6: vec![],
|
||||
desired_retention: 0.9,
|
||||
sm2_retention: 0.9,
|
||||
param_search: "".to_string(),
|
||||
|
|
@ -391,6 +394,7 @@ impl From<DeckConfSchema11> for DeckConfig {
|
|||
bury_interday_learning: c.bury_interday_learning,
|
||||
fsrs_params_4: c.fsrs_params_4,
|
||||
fsrs_params_5: c.fsrs_params_5,
|
||||
fsrs_params_6: c.fsrs_params_6,
|
||||
ignore_revlogs_before_date: c.ignore_revlogs_before_date,
|
||||
easy_days_percentages: c.easy_days_percentages,
|
||||
desired_retention: c.desired_retention,
|
||||
|
|
@ -504,6 +508,7 @@ impl From<DeckConfig> for DeckConfSchema11 {
|
|||
bury_interday_learning: i.bury_interday_learning,
|
||||
fsrs_params_4: i.fsrs_params_4,
|
||||
fsrs_params_5: i.fsrs_params_5,
|
||||
fsrs_params_6: i.fsrs_params_6,
|
||||
desired_retention: i.desired_retention,
|
||||
sm2_retention: i.historical_retention,
|
||||
param_search: i.param_search,
|
||||
|
|
@ -532,6 +537,7 @@ static RESERVED_DECKCONF_KEYS: Set<&'static str> = phf_set! {
|
|||
"newGatherPriority",
|
||||
"fsrsWeights",
|
||||
"fsrsParams5",
|
||||
"fsrsParams6",
|
||||
"desiredRetention",
|
||||
"stopTimerOnAnswer",
|
||||
"secondsToShowQuestion",
|
||||
|
|
|
|||
|
|
@ -50,7 +50,7 @@ impl Collection {
|
|||
deck: DeckId,
|
||||
) -> Result<anki_proto::deck_config::DeckConfigsForUpdate> {
|
||||
let mut defaults = DeckConfig::default();
|
||||
defaults.inner.fsrs_params_5 = DEFAULT_PARAMETERS.into();
|
||||
defaults.inner.fsrs_params_6 = DEFAULT_PARAMETERS.into();
|
||||
let last_optimize = self.get_config_i32(I32ConfigKey::LastFsrsOptimize) as u32;
|
||||
let days_since_last_fsrs_optimize = if last_optimize > 0 {
|
||||
self.timing_today()?
|
||||
|
|
@ -88,10 +88,14 @@ impl Collection {
|
|||
// grab the config and sort it
|
||||
let mut config = self.storage.all_deck_config()?;
|
||||
config.sort_unstable_by(|a, b| a.name.cmp(&b.name));
|
||||
// pre-fill empty fsrs 5 params with 4 params
|
||||
// pre-fill empty fsrs params with older params
|
||||
config.iter_mut().for_each(|c| {
|
||||
if c.inner.fsrs_params_5.is_empty() {
|
||||
c.inner.fsrs_params_5 = c.inner.fsrs_params_4.clone();
|
||||
if c.inner.fsrs_params_6.is_empty() {
|
||||
c.inner.fsrs_params_6 = if c.inner.fsrs_params_5.is_empty() {
|
||||
c.inner.fsrs_params_4.clone()
|
||||
} else {
|
||||
c.inner.fsrs_params_5.clone()
|
||||
};
|
||||
}
|
||||
});
|
||||
|
||||
|
|
@ -165,10 +169,11 @@ impl Collection {
|
|||
|
||||
// add/update provided configs
|
||||
for conf in &mut req.configs {
|
||||
// If the user has provided empty FSRS5 params, zero out any
|
||||
// If the user has provided empty FSRS6 params, zero out any
|
||||
// old params as well, so we don't fall back on them, which would
|
||||
// be surprising as they're not shown in the GUI.
|
||||
if conf.inner.fsrs_params_5.is_empty() {
|
||||
if conf.inner.fsrs_params_6.is_empty() {
|
||||
conf.inner.fsrs_params_5.clear();
|
||||
conf.inner.fsrs_params_4.clear();
|
||||
}
|
||||
// check the provided parameters are valid before we save them
|
||||
|
|
@ -370,7 +375,7 @@ impl Collection {
|
|||
) {
|
||||
Ok(params) => {
|
||||
println!("{}: {:?}", config.name, params.params);
|
||||
config.inner.fsrs_params_5 = params.params;
|
||||
config.inner.fsrs_params_6 = params.params;
|
||||
}
|
||||
Err(AnkiError::Interrupted) => return Err(AnkiError::Interrupted),
|
||||
Err(err) => {
|
||||
|
|
|
|||
|
|
@ -1,8 +1,6 @@
|
|||
// Copyright: Ankitects Pty Ltd and contributors
|
||||
// License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
||||
use std::collections::HashSet;
|
||||
|
||||
use crate::cloze::add_cloze_numbers_in_string;
|
||||
use crate::cloze::cloze_number_in_fields;
|
||||
use crate::collection::Collection;
|
||||
use crate::decks::DeckId;
|
||||
use crate::error;
|
||||
|
|
@ -128,10 +126,7 @@ impl crate::services::NotesService for Collection {
|
|||
&mut self,
|
||||
note: anki_proto::notes::Note,
|
||||
) -> error::Result<anki_proto::notes::ClozeNumbersInNoteResponse> {
|
||||
let mut set = HashSet::with_capacity(4);
|
||||
for field in ¬e.fields {
|
||||
add_cloze_numbers_in_string(field, &mut set);
|
||||
}
|
||||
let set = cloze_number_in_fields(note.fields);
|
||||
Ok(anki_proto::notes::ClozeNumbersInNoteResponse {
|
||||
numbers: set.into_iter().map(|n| n as u32).collect(),
|
||||
})
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ use rand::Rng;
|
|||
use rand::SeedableRng;
|
||||
|
||||
use super::Notetype;
|
||||
use crate::cloze::add_cloze_numbers_in_string;
|
||||
use crate::cloze::cloze_number_in_fields;
|
||||
use crate::notetype::NotetypeKind;
|
||||
use crate::prelude::*;
|
||||
use crate::template::ParsedTemplate;
|
||||
|
|
@ -148,10 +148,7 @@ impl<N: Deref<Target = Notetype>> CardGenContext<N> {
|
|||
extracted: &ExtractedCardInfo,
|
||||
) -> Vec<CardToGenerate> {
|
||||
// gather all cloze numbers
|
||||
let mut set = HashSet::with_capacity(4);
|
||||
for field in note.fields() {
|
||||
add_cloze_numbers_in_string(field, &mut set);
|
||||
}
|
||||
let set = cloze_number_in_fields(note.fields());
|
||||
set.into_iter()
|
||||
.filter_map(|cloze_ord| {
|
||||
let card_ord = cloze_ord.saturating_sub(1).min(499);
|
||||
|
|
|
|||
|
|
@ -212,6 +212,21 @@ impl crate::services::NotetypesService for Collection {
|
|||
)
|
||||
.map(Into::into)
|
||||
}
|
||||
|
||||
fn get_cloze_field_ords(
|
||||
&mut self,
|
||||
input: anki_proto::notetypes::NotetypeId,
|
||||
) -> error::Result<anki_proto::notetypes::GetClozeFieldOrdsResponse> {
|
||||
Ok(anki_proto::notetypes::GetClozeFieldOrdsResponse {
|
||||
ords: self
|
||||
.get_notetype(input.into())?
|
||||
.unwrap()
|
||||
.cloze_fields()
|
||||
.iter()
|
||||
.map(|ord| (*ord) as u32)
|
||||
.collect(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl From<anki_proto::notetypes::Notetype> for Notetype {
|
||||
|
|
|
|||
|
|
@ -7,6 +7,8 @@ use anki_proto::scheduler::ComputeMemoryStateResponse;
|
|||
use fsrs::FSRSItem;
|
||||
use fsrs::MemoryState;
|
||||
use fsrs::FSRS;
|
||||
use fsrs::FSRS5_DEFAULT_DECAY;
|
||||
use fsrs::FSRS6_DEFAULT_DECAY;
|
||||
use itertools::Itertools;
|
||||
|
||||
use super::params::ignore_revlogs_before_ms_from_config;
|
||||
|
|
@ -76,6 +78,15 @@ impl Collection {
|
|||
.then(|| Rescheduler::new(self))
|
||||
.transpose()?;
|
||||
let fsrs = FSRS::new(req.as_ref().map(|w| &w.params[..]).or(Some([].as_slice())))?;
|
||||
let decay = req.as_ref().map(|w| {
|
||||
if w.params.is_empty() {
|
||||
FSRS6_DEFAULT_DECAY // default decay for FSRS-6
|
||||
} else if w.params.len() < 21 {
|
||||
FSRS5_DEFAULT_DECAY // default decay for FSRS-4.5 and FSRS-5
|
||||
} else {
|
||||
w.params[20]
|
||||
}
|
||||
});
|
||||
let historical_retention = req.as_ref().map(|w| w.historical_retention);
|
||||
let items = fsrs_items_for_memory_states(
|
||||
&fsrs,
|
||||
|
|
@ -94,6 +105,7 @@ impl Collection {
|
|||
if let (Some(req), Some(item)) = (&req, item) {
|
||||
card.set_memory_state(&fsrs, Some(item), historical_retention.unwrap())?;
|
||||
card.desired_retention = desired_retention;
|
||||
card.decay = decay;
|
||||
// if rescheduling
|
||||
if let Some(reviews) = &last_revlog_info {
|
||||
// and we have a last review time for the card
|
||||
|
|
|
|||
|
|
@ -75,6 +75,7 @@ pub(crate) fn apply_load_balance_and_easy_days(
|
|||
fn create_review_priority_fn(
|
||||
review_order: ReviewCardOrder,
|
||||
deck_size: usize,
|
||||
params: Vec<f32>,
|
||||
) -> Option<ReviewPriorityFn> {
|
||||
// Helper macro to wrap closure in ReviewPriorityFn
|
||||
macro_rules! wrap {
|
||||
|
|
@ -91,11 +92,12 @@ fn create_review_priority_fn(
|
|||
// Interval-based ordering
|
||||
IntervalsAscending => wrap!(|c| c.interval as i32),
|
||||
IntervalsDescending => wrap!(|c| -(c.interval as i32)),
|
||||
|
||||
// Retrievability-based ordering
|
||||
RetrievabilityAscending => wrap!(|c| (c.retrievability() * 1000.0) as i32),
|
||||
RetrievabilityAscending => {
|
||||
wrap!(move |c| (c.retrievability(¶ms) * 1000.0) as i32)
|
||||
}
|
||||
RetrievabilityDescending => {
|
||||
wrap!(|c| -(c.retrievability() * 1000.0) as i32)
|
||||
wrap!(move |c| -(c.retrievability(¶ms) * 1000.0) as i32)
|
||||
}
|
||||
|
||||
// Due date ordering
|
||||
|
|
@ -123,8 +125,22 @@ impl Collection {
|
|||
.col
|
||||
.storage
|
||||
.get_revlog_entries_for_searched_cards_in_card_order()?;
|
||||
let cards = guard.col.storage.all_searched_cards()?;
|
||||
let mut cards = guard.col.storage.all_searched_cards()?;
|
||||
drop(guard);
|
||||
fn is_included_card(c: &Card) -> bool {
|
||||
c.queue != CardQueue::Suspended
|
||||
&& c.queue != CardQueue::PreviewRepeat
|
||||
&& c.queue != CardQueue::New
|
||||
}
|
||||
// calculate any missing memory state
|
||||
for c in &mut cards {
|
||||
if is_included_card(c) && c.memory_state.is_none() {
|
||||
let original = c.clone();
|
||||
let new_state = self.compute_memory_state(c.id)?.state;
|
||||
c.memory_state = new_state.map(Into::into);
|
||||
self.update_card_inner(c, original, self.usn()?)?;
|
||||
}
|
||||
}
|
||||
let days_elapsed = self.timing_today().unwrap().days_elapsed as i32;
|
||||
let new_cards = cards
|
||||
.iter()
|
||||
|
|
@ -133,7 +149,7 @@ impl Collection {
|
|||
+ req.deck_size as usize;
|
||||
let mut converted_cards = cards
|
||||
.into_iter()
|
||||
.filter(|c| c.queue != CardQueue::Suspended && c.queue != CardQueue::PreviewRepeat)
|
||||
.filter(is_included_card)
|
||||
.filter_map(|c| Card::convert(c, days_elapsed))
|
||||
.collect_vec();
|
||||
let introduced_today_count = self
|
||||
|
|
@ -181,28 +197,26 @@ impl Collection {
|
|||
.review_order
|
||||
.try_into()
|
||||
.ok()
|
||||
.and_then(|order| create_review_priority_fn(order, deck_size));
|
||||
.and_then(|order| create_review_priority_fn(order, deck_size, req.params.clone()));
|
||||
|
||||
let config = SimulatorConfig {
|
||||
deck_size,
|
||||
learn_span: req.days_to_simulate as usize,
|
||||
max_cost_perday: f32::MAX,
|
||||
max_ivl: req.max_interval as f32,
|
||||
learn_costs: p.learn_costs,
|
||||
review_costs: p.review_costs,
|
||||
first_rating_prob: p.first_rating_prob,
|
||||
review_rating_prob: p.review_rating_prob,
|
||||
first_rating_offsets: p.first_rating_offsets,
|
||||
first_session_lens: p.first_session_lens,
|
||||
forget_rating_offset: p.forget_rating_offset,
|
||||
forget_session_len: p.forget_session_len,
|
||||
loss_aversion: 1.0,
|
||||
learn_limit: req.new_limit as usize,
|
||||
review_limit: req.review_limit as usize,
|
||||
new_cards_ignore_review_limit: req.new_cards_ignore_review_limit,
|
||||
suspend_after_lapses: req.suspend_after_lapse_count,
|
||||
post_scheduling_fn,
|
||||
review_priority_fn,
|
||||
learning_step_transitions: p.learning_step_transitions,
|
||||
relearning_step_transitions: p.relearning_step_transitions,
|
||||
state_rating_costs: p.state_rating_costs,
|
||||
learning_step_count: p.learning_step_count,
|
||||
relearning_step_count: p.relearning_step_count,
|
||||
};
|
||||
|
||||
Ok((config, converted_cards))
|
||||
|
|
|
|||
|
|
@ -320,17 +320,31 @@ impl crate::services::SchedulerService for Collection {
|
|||
learn_span: simulator_config.learn_span as u32,
|
||||
max_cost_perday: simulator_config.max_cost_perday,
|
||||
max_ivl: simulator_config.max_ivl,
|
||||
learn_costs: simulator_config.learn_costs.to_vec(),
|
||||
review_costs: simulator_config.review_costs.to_vec(),
|
||||
first_rating_prob: simulator_config.first_rating_prob.to_vec(),
|
||||
review_rating_prob: simulator_config.review_rating_prob.to_vec(),
|
||||
first_rating_offsets: simulator_config.first_rating_offsets.to_vec(),
|
||||
first_session_lens: simulator_config.first_session_lens.to_vec(),
|
||||
forget_rating_offset: simulator_config.forget_rating_offset,
|
||||
forget_session_len: simulator_config.forget_session_len,
|
||||
loss_aversion: simulator_config.loss_aversion,
|
||||
loss_aversion: 1.0,
|
||||
learn_limit: simulator_config.learn_limit as u32,
|
||||
review_limit: simulator_config.review_limit as u32,
|
||||
learning_step_transitions: simulator_config
|
||||
.learning_step_transitions
|
||||
.iter()
|
||||
.flatten()
|
||||
.cloned()
|
||||
.collect(),
|
||||
relearning_step_transitions: simulator_config
|
||||
.relearning_step_transitions
|
||||
.iter()
|
||||
.flatten()
|
||||
.cloned()
|
||||
.collect(),
|
||||
state_rating_costs: simulator_config
|
||||
.state_rating_costs
|
||||
.iter()
|
||||
.flatten()
|
||||
.cloned()
|
||||
.collect(),
|
||||
learning_step_count: simulator_config.learning_step_count as u32,
|
||||
relearning_step_count: simulator_config.relearning_step_count as u32,
|
||||
})
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -555,7 +555,7 @@ impl SqlWriter<'_> {
|
|||
}
|
||||
|
||||
fn write_all_fields(&mut self, val: &str) {
|
||||
self.args.push(format!("(?i)^{}$", to_re(val)));
|
||||
self.args.push(format!("(?is)^{}$", to_re(val)));
|
||||
write!(self.sql, "regexp_fields(?{}, n.flds)", self.args.len()).unwrap();
|
||||
}
|
||||
|
||||
|
|
@ -1081,7 +1081,7 @@ mod test {
|
|||
s(ctx, "*:te*st"),
|
||||
(
|
||||
"(regexp_fields(?1, n.flds))".into(),
|
||||
vec!["(?i)^te.*st$".into()]
|
||||
vec!["(?is)^te.*st$".into()]
|
||||
)
|
||||
);
|
||||
// all field search with regex
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@
|
|||
// License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
||||
|
||||
use fsrs::FSRS;
|
||||
use fsrs::FSRS5_DEFAULT_DECAY;
|
||||
|
||||
use crate::card::CardType;
|
||||
use crate::card::FsrsMemoryState;
|
||||
|
|
@ -37,10 +38,11 @@ impl Collection {
|
|||
let fsrs_retrievability = card
|
||||
.memory_state
|
||||
.zip(Some(days_elapsed))
|
||||
.map(|(state, days)| {
|
||||
.zip(Some(card.decay.unwrap_or(FSRS5_DEFAULT_DECAY)))
|
||||
.map(|((state, days), decay)| {
|
||||
FSRS::new(None)
|
||||
.unwrap()
|
||||
.current_retrievability(state.into(), days)
|
||||
.current_retrievability(state.into(), days, decay)
|
||||
});
|
||||
|
||||
let original_deck = if card.original_deck_id == DeckId(0) {
|
||||
|
|
@ -75,6 +77,7 @@ impl Collection {
|
|||
memory_state: card.memory_state.map(Into::into),
|
||||
fsrs_retrievability,
|
||||
custom_data: card.custom_data,
|
||||
fsrs_params: preset.fsrs_params().to_vec(),
|
||||
preset: preset.name,
|
||||
original_deck: if original_deck != deck {
|
||||
Some(original_deck.human_name())
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@
|
|||
|
||||
use anki_proto::stats::graphs_response::Retrievability;
|
||||
use fsrs::FSRS;
|
||||
use fsrs::FSRS5_DEFAULT_DECAY;
|
||||
|
||||
use crate::prelude::TimestampSecs;
|
||||
use crate::scheduler::timing::SchedTimingToday;
|
||||
|
|
@ -30,7 +31,11 @@ impl GraphsContext {
|
|||
entry.1 += 1;
|
||||
if let Some(state) = card.memory_state {
|
||||
let elapsed_days = card.days_since_last_review(&timing).unwrap_or_default();
|
||||
let r = fsrs.current_retrievability(state.into(), elapsed_days);
|
||||
let r = fsrs.current_retrievability(
|
||||
state.into(),
|
||||
elapsed_days,
|
||||
card.decay.unwrap_or(FSRS5_DEFAULT_DECAY),
|
||||
);
|
||||
|
||||
*retrievability
|
||||
.retrievability
|
||||
|
|
|
|||
|
|
@ -42,6 +42,11 @@ pub(crate) struct CardData {
|
|||
deserialize_with = "default_on_invalid"
|
||||
)]
|
||||
pub(crate) fsrs_desired_retention: Option<f32>,
|
||||
#[serde(
|
||||
skip_serializing_if = "Option::is_none",
|
||||
deserialize_with = "default_on_invalid"
|
||||
)]
|
||||
pub(crate) decay: Option<f32>,
|
||||
|
||||
/// A string representation of a JSON object storing optional data
|
||||
/// associated with the card, so v3 custom scheduling code can persist
|
||||
|
|
@ -57,6 +62,7 @@ impl CardData {
|
|||
fsrs_stability: card.memory_state.as_ref().map(|m| m.stability),
|
||||
fsrs_difficulty: card.memory_state.as_ref().map(|m| m.difficulty),
|
||||
fsrs_desired_retention: card.desired_retention,
|
||||
decay: card.decay,
|
||||
custom_data: card.custom_data.clone(),
|
||||
}
|
||||
}
|
||||
|
|
@ -87,6 +93,9 @@ impl CardData {
|
|||
if let Some(v) = &mut self.fsrs_desired_retention {
|
||||
round_to_places(v, 2)
|
||||
}
|
||||
if let Some(v) = &mut self.decay {
|
||||
round_to_places(v, 3)
|
||||
}
|
||||
serde_json::to_string(&self).map_err(Into::into)
|
||||
}
|
||||
}
|
||||
|
|
@ -159,11 +168,12 @@ mod test {
|
|||
fsrs_stability: Some(123.45678),
|
||||
fsrs_difficulty: Some(1.234567),
|
||||
fsrs_desired_retention: Some(0.987654),
|
||||
decay: Some(0.123456),
|
||||
custom_data: "".to_string(),
|
||||
};
|
||||
assert_eq!(
|
||||
data.convert_to_json().unwrap(),
|
||||
r#"{"s":123.457,"d":1.235,"dr":0.99}"#
|
||||
r#"{"s":123.457,"d":1.235,"dr":0.99,"decay":0.123}"#
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -85,6 +85,7 @@ fn row_to_card(row: &Row) -> result::Result<Card, rusqlite::Error> {
|
|||
original_position: data.original_position,
|
||||
memory_state: data.memory_state(),
|
||||
desired_retention: data.fsrs_desired_retention,
|
||||
decay: data.decay,
|
||||
custom_data: data.custom_data,
|
||||
})
|
||||
}
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@ use std::sync::Arc;
|
|||
|
||||
use fnv::FnvHasher;
|
||||
use fsrs::FSRS;
|
||||
use fsrs::FSRS5_DEFAULT_DECAY;
|
||||
use regex::Regex;
|
||||
use rusqlite::functions::FunctionFlags;
|
||||
use rusqlite::params;
|
||||
|
|
@ -325,10 +326,11 @@ fn add_extract_fsrs_retrievability(db: &Connection) -> rusqlite::Result<()> {
|
|||
let review_day = due.saturating_sub(ivl);
|
||||
days_elapsed.saturating_sub(review_day) as u32
|
||||
};
|
||||
let decay = card_data.decay.unwrap_or_default();
|
||||
Ok(card_data.memory_state().map(|state| {
|
||||
FSRS::new(None)
|
||||
.unwrap()
|
||||
.current_retrievability(state.into(), days_elapsed)
|
||||
.current_retrievability(state.into(), days_elapsed, decay)
|
||||
}))
|
||||
},
|
||||
)
|
||||
|
|
@ -374,10 +376,11 @@ fn add_extract_fsrs_relative_retrievability(db: &Connection) -> rusqlite::Result
|
|||
{
|
||||
// avoid div by zero
|
||||
desired_retrievability = desired_retrievability.max(0.0001);
|
||||
let decay = card_data.decay.unwrap_or(FSRS5_DEFAULT_DECAY);
|
||||
|
||||
let current_retrievability = FSRS::new(None)
|
||||
.unwrap()
|
||||
.current_retrievability(state.into(), days_elapsed)
|
||||
.current_retrievability(state.into(), days_elapsed, decay)
|
||||
.max(0.0001);
|
||||
|
||||
return Ok(Some(
|
||||
|
|
|
|||
|
|
@ -332,6 +332,7 @@ impl From<CardEntry> for Card {
|
|||
original_position: data.original_position,
|
||||
memory_state: data.memory_state(),
|
||||
desired_retention: data.fsrs_desired_retention,
|
||||
decay: data.decay,
|
||||
custom_data: data.custom_data,
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -71,7 +71,7 @@ impl NormalSyncer<'_> {
|
|||
let local = self.col.sync_meta()?;
|
||||
let local_bytes = local.collection_bytes;
|
||||
let limit = *MAXIMUM_SYNC_PAYLOAD_BYTES_UNCOMPRESSED;
|
||||
if local.collection_bytes > limit {
|
||||
if self.server.endpoint.as_str().contains("ankiweb") && local.collection_bytes > limit {
|
||||
return Err(AnkiError::sync_error(
|
||||
format!("{local_bytes} > {limit}"),
|
||||
SyncErrorKind::UploadTooLarge,
|
||||
|
|
|
|||
|
|
@ -15,7 +15,7 @@ use nom::combinator::map;
|
|||
use nom::sequence::delimited;
|
||||
use regex::Regex;
|
||||
|
||||
use crate::cloze::add_cloze_numbers_in_string;
|
||||
use crate::cloze::cloze_number_in_fields;
|
||||
use crate::error::AnkiError;
|
||||
use crate::error::Result;
|
||||
use crate::error::TemplateError;
|
||||
|
|
@ -673,11 +673,7 @@ pub fn render_card(
|
|||
}
|
||||
|
||||
fn cloze_is_empty(field_map: &HashMap<&str, Cow<str>>, card_ord: u16) -> bool {
|
||||
let mut set = HashSet::with_capacity(4);
|
||||
for field in field_map.values() {
|
||||
add_cloze_numbers_in_string(field.as_ref(), &mut set);
|
||||
}
|
||||
!set.contains(&(card_ord + 1))
|
||||
!cloze_number_in_fields(field_map.values()).contains(&(card_ord + 1))
|
||||
}
|
||||
|
||||
// Field requirements
|
||||
|
|
|
|||
|
|
@ -71,8 +71,12 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
|||
});
|
||||
}
|
||||
|
||||
$: disabled =
|
||||
!alwaysEnabled && (!$focusedInput || !editingInputIsRichText($focusedInput));
|
||||
$: enabled =
|
||||
alwaysEnabled ||
|
||||
($focusedInput &&
|
||||
editingInputIsRichText($focusedInput) &&
|
||||
$focusedInput.isClozeField);
|
||||
$: disabled = !enabled;
|
||||
|
||||
const incrementKeyCombination = "Control+Shift+C";
|
||||
const sameKeyCombination = "Control+Alt+Shift+C";
|
||||
|
|
|
|||
|
|
@ -16,6 +16,7 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
|||
description: string;
|
||||
collapsed: boolean;
|
||||
hidden: boolean;
|
||||
isClozeField: boolean;
|
||||
}
|
||||
|
||||
export interface EditorFieldAPI {
|
||||
|
|
@ -82,6 +83,7 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
|||
element,
|
||||
direction: directionStore,
|
||||
editingArea: editingArea as EditingAreaAPI,
|
||||
isClozeField: field.isClozeField,
|
||||
});
|
||||
|
||||
setContextProperty(api);
|
||||
|
|
|
|||
|
|
@ -133,7 +133,7 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
|||
}
|
||||
|
||||
for (const [index, [, fieldContent]] of fs.entries()) {
|
||||
fieldStores[index].set(sanitize(fieldContent));
|
||||
fieldStores[index].set(fieldContent);
|
||||
}
|
||||
|
||||
fieldNames = newFieldNames;
|
||||
|
|
@ -144,6 +144,10 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
|||
fieldsCollapsed =
|
||||
sessionOptions[notetypeMeta?.id]?.fieldsCollapsed ?? defaultCollapsed;
|
||||
}
|
||||
let clozeFields: boolean[] = [];
|
||||
export function setClozeFields(defaultClozeFields: boolean[]): void {
|
||||
clozeFields = defaultClozeFields;
|
||||
}
|
||||
|
||||
let richTextsHidden: boolean[] = [];
|
||||
let plainTextsHidden: boolean[] = [];
|
||||
|
|
@ -276,6 +280,7 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
|||
direction: fonts[index][2] ? "rtl" : "ltr",
|
||||
collapsed: fieldsCollapsed[index],
|
||||
hidden: hideFieldInOcclusionType(index, ioFields),
|
||||
isClozeField: clozeFields[index],
|
||||
})) as FieldData[];
|
||||
|
||||
let lastSavedTags: string[] | null = null;
|
||||
|
|
@ -424,7 +429,6 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
|||
} from "../routes/image-occlusion/store";
|
||||
import CollapseLabel from "./CollapseLabel.svelte";
|
||||
import * as oldEditorAdapter from "./old-editor-adapter";
|
||||
import { sanitize } from "$lib/domlib";
|
||||
|
||||
$: isIOImageLoaded = false;
|
||||
$: ioImageLoadedStore.set(isIOImageLoaded);
|
||||
|
|
@ -574,6 +578,7 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
|||
saveSession,
|
||||
setFields,
|
||||
setCollapsed,
|
||||
setClozeFields,
|
||||
setPlainTexts,
|
||||
setDescriptions,
|
||||
setFonts,
|
||||
|
|
@ -763,6 +768,7 @@ the AddCards dialog) should be implemented in the user of this component.
|
|||
$focusedInput = null;
|
||||
}}
|
||||
bind:this={richTextInputs[index]}
|
||||
isClozeField={field.isClozeField}
|
||||
/>
|
||||
</Collapsible>
|
||||
</svelte:fragment>
|
||||
|
|
|
|||
|
|
@ -4,8 +4,42 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
|||
-->
|
||||
<script lang="ts">
|
||||
import ButtonGroup from "$lib/components/ButtonGroup.svelte";
|
||||
import { bridgeCommand } from "@tslib/bridgecommand";
|
||||
import { toggleEditorButton } from "../old-editor-adapter";
|
||||
import { singleCallback } from "@tslib/typing";
|
||||
import { on } from "@tslib/events";
|
||||
|
||||
export let buttons: string[];
|
||||
const { buttons } = $props<{ buttons: string[] }>();
|
||||
|
||||
$effect(() => {
|
||||
// Each time the buttons are changed...
|
||||
buttons;
|
||||
|
||||
// Add event handlers to each button
|
||||
const addonButtons = document.querySelectorAll(".anki-addon-button");
|
||||
const cbs = [...addonButtons].map((button) =>
|
||||
singleCallback(
|
||||
on(button, "click", () => {
|
||||
const command = button.getAttribute("data-command");
|
||||
if (command) {
|
||||
bridgeCommand(command);
|
||||
}
|
||||
const toggleable = button.getAttribute("data-cantoggle");
|
||||
if (toggleable === "1") {
|
||||
toggleEditorButton(button as HTMLButtonElement);
|
||||
}
|
||||
|
||||
return false;
|
||||
}),
|
||||
on(button as HTMLButtonElement, "mousedown", (evt) => {
|
||||
evt.preventDefault();
|
||||
evt.stopPropagation();
|
||||
}),
|
||||
),
|
||||
);
|
||||
|
||||
return singleCallback(...cbs);
|
||||
});
|
||||
|
||||
const radius = "5px";
|
||||
function getBorderRadius(index: number, length: number): string {
|
||||
|
|
|
|||
|
|
@ -14,6 +14,9 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
|||
$: richTextAPI = $focusedInput as RichTextInputAPI;
|
||||
|
||||
async function onSurround({ detail }): Promise<void> {
|
||||
if (!richTextAPI.isClozeField) {
|
||||
return;
|
||||
}
|
||||
const richText = await richTextAPI.element;
|
||||
const { prefix, suffix } = detail;
|
||||
|
||||
|
|
|
|||
|
|
@ -15,6 +15,7 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
|||
import ClozeButtons from "../ClozeButtons.svelte";
|
||||
|
||||
export let isBlock: boolean;
|
||||
export let isClozeField: boolean;
|
||||
|
||||
const dispatch = createEventDispatcher();
|
||||
</script>
|
||||
|
|
@ -40,7 +41,9 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
|||
</IconButton>
|
||||
</ButtonGroup>
|
||||
|
||||
<ClozeButtons on:surround alwaysEnabled={true} />
|
||||
{#if isClozeField}
|
||||
<ClozeButtons on:surround alwaysEnabled={true} />
|
||||
{/if}
|
||||
|
||||
<ButtonGroup>
|
||||
<IconButton
|
||||
|
|
|
|||
|
|
@ -34,6 +34,8 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
|||
let cleanup: Callback;
|
||||
let richTextInput: RichTextInputAPI | null = null;
|
||||
let allowPromise = Promise.resolve();
|
||||
// Whether the last focused input field corresponds to a cloze field.
|
||||
let isClozeField: boolean = true;
|
||||
|
||||
async function initialize(input: EditingInputAPI | null): Promise<void> {
|
||||
cleanup?.();
|
||||
|
|
@ -50,6 +52,7 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
|||
on(container, "movecaretafter" as any, showOnAutofocus),
|
||||
on(container, "selectall" as any, showSelectAll),
|
||||
);
|
||||
isClozeField = input.isClozeField;
|
||||
}
|
||||
|
||||
// Wait if the mathjax overlay is still active
|
||||
|
|
@ -242,6 +245,7 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
|||
|
||||
<MathjaxButtons
|
||||
{isBlock}
|
||||
{isClozeField}
|
||||
on:setinline={async () => {
|
||||
isBlock = false;
|
||||
await updateBlockAttribute();
|
||||
|
|
|
|||
|
|
@ -22,6 +22,7 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
|||
/** The API exposed by the editable component */
|
||||
editable: ContentEditableAPI;
|
||||
customStyles: Promise<Record<string, any>>;
|
||||
isClozeField: boolean;
|
||||
}
|
||||
|
||||
function editingInputIsRichText(
|
||||
|
|
@ -84,6 +85,7 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
|||
|
||||
export let hidden = false;
|
||||
export const focusFlag = new Flag();
|
||||
export let isClozeField: boolean;
|
||||
|
||||
const { focusedInput } = noteEditorContext.get();
|
||||
const { content, editingInputs } = editingAreaContext.get();
|
||||
|
|
@ -156,6 +158,7 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
|||
inputHandler,
|
||||
editable: {} as ContentEditableAPI,
|
||||
customStyles,
|
||||
isClozeField,
|
||||
};
|
||||
|
||||
const allContexts = getAllContexts();
|
||||
|
|
@ -204,6 +207,10 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
|||
}
|
||||
}
|
||||
|
||||
$: {
|
||||
api.isClozeField = isClozeField;
|
||||
}
|
||||
|
||||
onMount(() => {
|
||||
$editingInputs.push(api);
|
||||
$editingInputs = $editingInputs;
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
|||
export { className as class };
|
||||
|
||||
export let title: string;
|
||||
export let onTitleClick: ((_e: MouseEvent | KeyboardEvent) => void) | null = null;
|
||||
</script>
|
||||
|
||||
<div
|
||||
|
|
@ -24,7 +25,22 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
|||
style:--container-margin="0"
|
||||
>
|
||||
<div class="position-relative">
|
||||
<h1>{title}</h1>
|
||||
{#if onTitleClick}
|
||||
<span
|
||||
on:click={onTitleClick}
|
||||
on:keydown={onTitleClick}
|
||||
role="button"
|
||||
tabindex="0"
|
||||
>
|
||||
<h1>
|
||||
{title}
|
||||
</h1>
|
||||
</span>
|
||||
{:else}
|
||||
<h1>
|
||||
{title}
|
||||
</h1>
|
||||
{/if}
|
||||
<div class="help-badge position-absolute" class:rtl>
|
||||
<slot name="tooltip" />
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -5,5 +5,4 @@ export * from "./content-editable";
|
|||
export * from "./location";
|
||||
export * from "./move-nodes";
|
||||
export * from "./place-caret";
|
||||
export * from "./sanitize";
|
||||
export * from "./surround";
|
||||
|
|
|
|||
|
|
@ -1,10 +0,0 @@
|
|||
// Copyright: Ankitects Pty Ltd and contributors
|
||||
// License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
||||
|
||||
import DOMPurify from "dompurify";
|
||||
|
||||
export function sanitize(html: string): string {
|
||||
// We need to treat the text as a document fragment, or a style tag
|
||||
// at the start of input will be discarded.
|
||||
return DOMPurify.sanitize(html, { FORCE_BODY: true });
|
||||
}
|
||||
|
|
@ -57,12 +57,6 @@
|
|||
"path": "node_modules/@tootallnate/once",
|
||||
"licenseFile": "node_modules/@tootallnate/once/LICENSE"
|
||||
},
|
||||
"@types/trusted-types@2.0.7": {
|
||||
"licenses": "MIT",
|
||||
"repository": "https://github.com/DefinitelyTyped/DefinitelyTyped",
|
||||
"path": "node_modules/@types/trusted-types",
|
||||
"licenseFile": "node_modules/@types/trusted-types/LICENSE"
|
||||
},
|
||||
"abab@2.0.6": {
|
||||
"licenses": "BSD-3-Clause",
|
||||
"repository": "https://github.com/jsdom/abab",
|
||||
|
|
@ -101,8 +95,8 @@
|
|||
"repository": "https://github.com/TooTallNate/node-agent-base",
|
||||
"publisher": "Nathan Rajlich",
|
||||
"email": "nathan@tootallnate.net",
|
||||
"path": "node_modules/http-proxy-agent/node_modules/agent-base",
|
||||
"licenseFile": "node_modules/http-proxy-agent/node_modules/agent-base/README.md"
|
||||
"path": "node_modules/agent-base",
|
||||
"licenseFile": "node_modules/agent-base/README.md"
|
||||
},
|
||||
"asynckit@0.4.0": {
|
||||
"licenses": "MIT",
|
||||
|
|
@ -442,14 +436,6 @@
|
|||
"path": "node_modules/domexception",
|
||||
"licenseFile": "node_modules/domexception/LICENSE.txt"
|
||||
},
|
||||
"dompurify@3.2.5": {
|
||||
"licenses": "(MPL-2.0 OR Apache-2.0)",
|
||||
"repository": "https://github.com/cure53/DOMPurify",
|
||||
"publisher": "Dr.-Ing. Mario Heiderich, Cure53",
|
||||
"email": "mario@cure53.de",
|
||||
"path": "node_modules/dompurify",
|
||||
"licenseFile": "node_modules/dompurify/LICENSE"
|
||||
},
|
||||
"empty-npm-package@1.0.0": {
|
||||
"licenses": "ISC",
|
||||
"path": "node_modules/canvas"
|
||||
|
|
@ -586,14 +572,6 @@
|
|||
"path": "node_modules/lodash-es",
|
||||
"licenseFile": "node_modules/lodash-es/LICENSE"
|
||||
},
|
||||
"lru-cache@10.4.3": {
|
||||
"licenses": "ISC",
|
||||
"repository": "https://github.com/isaacs/node-lru-cache",
|
||||
"publisher": "Isaac Z. Schlueter",
|
||||
"email": "i@izs.me",
|
||||
"path": "node_modules/lru-cache",
|
||||
"licenseFile": "node_modules/lru-cache/LICENSE"
|
||||
},
|
||||
"marked@5.1.2": {
|
||||
"licenses": "MIT",
|
||||
"repository": "https://github.com/markedjs/marked",
|
||||
|
|
@ -790,16 +768,16 @@
|
|||
"repository": "https://github.com/jsdom/whatwg-url",
|
||||
"publisher": "Sebastian Mayr",
|
||||
"email": "github@smayr.name",
|
||||
"path": "node_modules/jsdom/node_modules/whatwg-url",
|
||||
"licenseFile": "node_modules/jsdom/node_modules/whatwg-url/LICENSE.txt"
|
||||
"path": "node_modules/whatwg-url",
|
||||
"licenseFile": "node_modules/whatwg-url/LICENSE.txt"
|
||||
},
|
||||
"whatwg-url@11.0.0": {
|
||||
"licenses": "MIT",
|
||||
"repository": "https://github.com/jsdom/whatwg-url",
|
||||
"publisher": "Sebastian Mayr",
|
||||
"email": "github@smayr.name",
|
||||
"path": "node_modules/whatwg-url",
|
||||
"licenseFile": "node_modules/whatwg-url/LICENSE.txt"
|
||||
"path": "node_modules/data-urls/node_modules/whatwg-url",
|
||||
"licenseFile": "node_modules/data-urls/node_modules/whatwg-url/LICENSE.txt"
|
||||
},
|
||||
"ws@8.18.0": {
|
||||
"licenses": "MIT",
|
||||
|
|
|
|||
|
|
@ -18,6 +18,16 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
|||
|
||||
$: fsrsEnabled = stats?.memoryState != null;
|
||||
$: desiredRetention = stats?.desiredRetention ?? 0.9;
|
||||
$: decay = (() => {
|
||||
const paramsLength = stats?.fsrsParams?.length ?? 0;
|
||||
if (paramsLength === 0) {
|
||||
return 0.2; // default decay for FSRS-6
|
||||
}
|
||||
if (paramsLength < 21) {
|
||||
return 0.5; // default decay for FSRS-4.5 and FSRS-5
|
||||
}
|
||||
return stats?.fsrsParams?.[20] ?? 0.2;
|
||||
})();
|
||||
</script>
|
||||
|
||||
<Container breakpoint="md" --gutter-inline="1rem" --gutter-block="0.5rem">
|
||||
|
|
@ -33,7 +43,7 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
|||
{/if}
|
||||
{#if fsrsEnabled}
|
||||
<Row>
|
||||
<ForgettingCurve revlog={stats.revlog} {desiredRetention} />
|
||||
<ForgettingCurve revlog={stats.revlog} {desiredRetention} {decay} />
|
||||
</Row>
|
||||
{/if}
|
||||
{:else}
|
||||
|
|
|
|||
|
|
@ -21,6 +21,7 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
|||
|
||||
export let revlog: RevlogEntry[];
|
||||
export let desiredRetention: number;
|
||||
export let decay: number;
|
||||
let svg: HTMLElement | SVGElement | null = null;
|
||||
const bounds = defaultGraphBounds();
|
||||
const title = tr.cardStatsFsrsForgettingCurveTitle();
|
||||
|
|
@ -47,6 +48,7 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
|||
svg as SVGElement,
|
||||
bounds,
|
||||
desiredRetention,
|
||||
decay,
|
||||
);
|
||||
</script>
|
||||
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
|||
import { filterRevlogEntryByReviewKind } from "./forgetting-curve";
|
||||
|
||||
export let revlog: RevlogEntry[];
|
||||
export let fsrsEnabled: boolean = false;
|
||||
export const fsrsEnabled: boolean = false;
|
||||
|
||||
function reviewKindClass(entry: RevlogEntry): string {
|
||||
switch (entry.reviewKind) {
|
||||
|
|
@ -174,7 +174,16 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
|||
{/each}
|
||||
</div>
|
||||
</div>
|
||||
{#if fsrsEnabled}{/if}
|
||||
<!-- {#if fsrsEnabled}
|
||||
<div class="column">
|
||||
<div class="column-head">{tr2.cardStatsFsrsStability()}</div>
|
||||
<div class="column-content right">
|
||||
{#each revlogRows as row, _index}
|
||||
<div>{row.stability}</div>
|
||||
{/each}
|
||||
</div>
|
||||
</div>
|
||||
{/if} -->
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
|
|
|
|||
|
|
@ -11,12 +11,11 @@ import { axisBottom, axisLeft, line, max, min, pointer, scaleLinear, scaleTime,
|
|||
import { type GraphBounds, setDataAvailable } from "../graphs/graph-helpers";
|
||||
import { hideTooltip, showTooltip } from "../graphs/tooltip-utils.svelte";
|
||||
|
||||
const FACTOR = 19 / 81;
|
||||
const DECAY = -0.5;
|
||||
const MIN_POINTS = 1000;
|
||||
|
||||
function forgettingCurve(stability: number, daysElapsed: number): number {
|
||||
return Math.pow((daysElapsed / stability) * FACTOR + 1.0, DECAY);
|
||||
function forgettingCurve(stability: number, daysElapsed: number, decay: number): number {
|
||||
const factor = Math.pow(0.9, 1 / -decay) - 1;
|
||||
return Math.pow((daysElapsed / stability) * factor + 1.0, -decay);
|
||||
}
|
||||
|
||||
interface DataPoint {
|
||||
|
|
@ -68,7 +67,7 @@ export function filterRevlog(revlog: RevlogEntry[]): RevlogEntry[] {
|
|||
return result.filter((entry) => filterRevlogEntryByReviewKind(entry));
|
||||
}
|
||||
|
||||
export function prepareData(revlog: RevlogEntry[], maxDays: number) {
|
||||
export function prepareData(revlog: RevlogEntry[], maxDays: number, decay: number) {
|
||||
const data: DataPoint[] = [];
|
||||
let lastReviewTime = 0;
|
||||
let lastStability = 0;
|
||||
|
|
@ -97,7 +96,7 @@ export function prepareData(revlog: RevlogEntry[], maxDays: number) {
|
|||
let elapsedDays = 0;
|
||||
while (elapsedDays < totalDaysElapsed - step) {
|
||||
elapsedDays += step;
|
||||
const retrievability = forgettingCurve(lastStability, elapsedDays);
|
||||
const retrievability = forgettingCurve(lastStability, elapsedDays, decay);
|
||||
data.push({
|
||||
date: new Date((lastReviewTime + elapsedDays * 86400) * 1000),
|
||||
daysSinceFirstLearn: data[data.length - 1].daysSinceFirstLearn + step,
|
||||
|
|
@ -128,7 +127,7 @@ export function prepareData(revlog: RevlogEntry[], maxDays: number) {
|
|||
let elapsedDays = 0;
|
||||
while (elapsedDays < totalDaysSinceLastReview - step) {
|
||||
elapsedDays += step;
|
||||
const retrievability = forgettingCurve(lastStability, elapsedDays);
|
||||
const retrievability = forgettingCurve(lastStability, elapsedDays, decay);
|
||||
data.push({
|
||||
date: new Date((lastReviewTime + elapsedDays * 86400) * 1000),
|
||||
daysSinceFirstLearn: data[data.length - 1].daysSinceFirstLearn + step,
|
||||
|
|
@ -138,7 +137,7 @@ export function prepareData(revlog: RevlogEntry[], maxDays: number) {
|
|||
});
|
||||
}
|
||||
daysSinceFirstLearn += totalDaysSinceLastReview;
|
||||
const retrievability = forgettingCurve(lastStability, totalDaysSinceLastReview);
|
||||
const retrievability = forgettingCurve(lastStability, totalDaysSinceLastReview, decay);
|
||||
data.push({
|
||||
date: new Date(now * 1000),
|
||||
daysSinceFirstLearn: daysSinceFirstLearn,
|
||||
|
|
@ -151,7 +150,7 @@ export function prepareData(revlog: RevlogEntry[], maxDays: number) {
|
|||
let previewDaysElapsed = 0;
|
||||
while (previewDaysElapsed < previewDays) {
|
||||
previewDaysElapsed += step;
|
||||
const retrievability = forgettingCurve(lastStability, elapsedDays + previewDaysElapsed);
|
||||
const retrievability = forgettingCurve(lastStability, elapsedDays + previewDaysElapsed, decay);
|
||||
data.push({
|
||||
date: new Date((now + previewDaysElapsed * 86400) * 1000),
|
||||
daysSinceFirstLearn: data[data.length - 1].daysSinceFirstLearn + step,
|
||||
|
|
@ -185,6 +184,7 @@ export function renderForgettingCurve(
|
|||
svgElem: SVGElement,
|
||||
bounds: GraphBounds,
|
||||
desiredRetention: number,
|
||||
decay: number,
|
||||
) {
|
||||
const svg = select(svgElem);
|
||||
const trans = svg.transition().duration(600) as any;
|
||||
|
|
@ -194,7 +194,7 @@ export function renderForgettingCurve(
|
|||
}
|
||||
const maxDays = calculateMaxDays(filteredRevlog, timeRange);
|
||||
|
||||
const data = prepareData(filteredRevlog, maxDays);
|
||||
const data = prepareData(filteredRevlog, maxDays, decay);
|
||||
|
||||
if (data.length === 0) {
|
||||
setDataAvailable(svg, false);
|
||||
|
|
|
|||
|
|
@ -47,7 +47,10 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
|||
$: computing = computingParams || checkingParams;
|
||||
$: defaultparamSearch = `preset:"${state.getCurrentNameForSearch()}" -is:suspended`;
|
||||
$: roundedRetention = Number($config.desiredRetention.toFixed(2));
|
||||
$: desiredRetentionWarning = getRetentionWarning(roundedRetention);
|
||||
$: desiredRetentionWarning = getRetentionWarning(
|
||||
roundedRetention,
|
||||
fsrsParams($config),
|
||||
);
|
||||
$: retentionWarningClass = getRetentionWarningClass(roundedRetention);
|
||||
|
||||
$: newCardsIgnoreReviewLimit = state.newCardsIgnoreReviewLimit;
|
||||
|
|
@ -64,8 +67,8 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
|||
reviewOrder: $config.reviewOrder,
|
||||
});
|
||||
|
||||
function getRetentionWarning(retention: number): string {
|
||||
const decay = -0.5;
|
||||
function getRetentionWarning(retention: number, params: number[]): string {
|
||||
const decay = params.length > 20 ? -params[20] : -0.5; // default decay for FSRS-4.5 and FSRS-5
|
||||
const factor = 0.9 ** (1 / decay) - 1;
|
||||
const stability = 100;
|
||||
const days = Math.round(
|
||||
|
|
@ -142,7 +145,7 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
|||
: tr.deckConfigFsrsParamsNoReviews();
|
||||
setTimeout(() => alert(msg), 200);
|
||||
} else {
|
||||
$config.fsrsParams5 = resp.params;
|
||||
$config.fsrsParams6 = resp.params;
|
||||
}
|
||||
if (computeParamsProgress) {
|
||||
computeParamsProgress.current = computeParamsProgress.total;
|
||||
|
|
@ -244,9 +247,9 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
|||
|
||||
<div class="ms-1 me-1">
|
||||
<ParamsInputRow
|
||||
bind:value={$config.fsrsParams5}
|
||||
bind:value={$config.fsrsParams6}
|
||||
defaultValue={[]}
|
||||
defaults={defaults.fsrsParams5}
|
||||
defaults={defaults.fsrsParams6}
|
||||
>
|
||||
<SettingTitle on:click={() => openHelpModal("modelParams")}>
|
||||
{tr.deckConfigWeights()}
|
||||
|
|
|
|||
|
|
@ -233,7 +233,7 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
|||
);
|
||||
}
|
||||
|
||||
let easyDayPercentages = [...$config.easyDaysPercentages];
|
||||
$: easyDayPercentages = [...$config.easyDaysPercentages];
|
||||
</script>
|
||||
|
||||
<div class="modal" class:show={shown} class:d-block={shown} tabindex="-1">
|
||||
|
|
@ -308,7 +308,7 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
|||
</details>
|
||||
|
||||
<details>
|
||||
<summary>{"Advanced settings"}</summary>
|
||||
<summary>{tr.deckConfigAdvancedSettings()}</summary>
|
||||
<SpinBoxRow
|
||||
bind:value={simulateFsrsRequest.maxInterval}
|
||||
defaultValue={$config.maximumReviewInterval}
|
||||
|
|
@ -351,7 +351,7 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
|||
<SettingTitle
|
||||
on:click={() => openHelpModal("simulateFsrsReview")}
|
||||
>
|
||||
{"Smooth Graph"}
|
||||
{tr.deckConfigSmoothGraph()}
|
||||
</SettingTitle>
|
||||
</SwitchRow>
|
||||
|
||||
|
|
@ -363,7 +363,7 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
|||
<SettingTitle
|
||||
on:click={() => openHelpModal("simulateFsrsReview")}
|
||||
>
|
||||
{"Suspend Leeches"}
|
||||
{tr.deckConfigSuspendLeeches()}
|
||||
</SettingTitle>
|
||||
</SwitchRow>
|
||||
|
||||
|
|
@ -451,8 +451,7 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
|||
onPresetChange();
|
||||
}}
|
||||
>
|
||||
<!-- {tr.deckConfigApplyChanges()} -->
|
||||
{"Save to Preset Options"}
|
||||
{tr.deckConfigSaveOptionsToPreset()}
|
||||
</button>
|
||||
|
||||
{#if processing}
|
||||
|
|
|
|||
|
|
@ -461,7 +461,9 @@ export async function commitEditing(): Promise<void> {
|
|||
}
|
||||
|
||||
export function fsrsParams(config: DeckConfig_Config): number[] {
|
||||
if (config.fsrsParams5) {
|
||||
if (config.fsrsParams6) {
|
||||
return config.fsrsParams6;
|
||||
} else if (config.fsrsParams5) {
|
||||
return config.fsrsParams5;
|
||||
} else {
|
||||
return config.fsrsParams4;
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
|||
// When title is null (default), the graph is inlined, not having TitledContainer wrapper.
|
||||
export let title: string | null = null;
|
||||
export let subtitle: string | null = null;
|
||||
export let onTitleClick: ((_e: MouseEvent | KeyboardEvent) => void) | null = null;
|
||||
</script>
|
||||
|
||||
{#if title == null}
|
||||
|
|
@ -18,7 +19,8 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
|||
<slot />
|
||||
</div>
|
||||
{:else}
|
||||
<TitledContainer class="d-flex flex-column" {title}>
|
||||
<TitledContainer class="d-flex flex-column" {title} {onTitleClick}>
|
||||
<slot slot="tooltip" name="tooltip"></slot>
|
||||
<div class="graph d-flex flex-grow-1 flex-column justify-content-center">
|
||||
{#if subtitle}
|
||||
<div class="subtitle">{subtitle}</div>
|
||||
|
|
|
|||
|
|
@ -5,6 +5,11 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
|||
<script lang="ts">
|
||||
import type { GraphsResponse } from "@generated/anki/stats_pb";
|
||||
import * as tr from "@generated/ftl";
|
||||
import { HelpPage } from "@tslib/help-page";
|
||||
import HelpModal from "$lib/components/HelpModal.svelte";
|
||||
import type Carousel from "bootstrap/js/dist/carousel";
|
||||
import type Modal from "bootstrap/js/dist/modal";
|
||||
import type { HelpItem } from "$lib/components/types";
|
||||
|
||||
import { type RevlogRange } from "./graph-helpers";
|
||||
import { DisplayMode, type PeriodTrueRetentionData, Scope } from "./true-retention";
|
||||
|
|
@ -31,13 +36,43 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
|||
}
|
||||
});
|
||||
|
||||
const retentionHelp = {
|
||||
trueRetention: {
|
||||
title: tr.statisticsTrueRetentionTitle(),
|
||||
help: tr.statisticsTrueRetentionTooltip(),
|
||||
},
|
||||
};
|
||||
|
||||
const helpSections: HelpItem[] = Object.values(retentionHelp);
|
||||
|
||||
let modal: Modal;
|
||||
let carousel: Carousel;
|
||||
|
||||
function openHelpModal(index: number): void {
|
||||
modal.show();
|
||||
carousel.to(index);
|
||||
}
|
||||
|
||||
let mode: DisplayMode = $state(DisplayMode.Summary);
|
||||
|
||||
const title = tr.statisticsTrueRetentionTitle();
|
||||
const subtitle = tr.statisticsTrueRetentionSubtitle();
|
||||
const onTitleClick = () => {
|
||||
openHelpModal(Object.keys(retentionHelp).indexOf("trueRetention"));
|
||||
};
|
||||
</script>
|
||||
|
||||
<Graph {title} {subtitle}>
|
||||
<Graph {title} {subtitle} {onTitleClick}>
|
||||
<HelpModal
|
||||
title={tr.statisticsTrueRetentionTitle()}
|
||||
url={HelpPage.DeckOptions.fsrs}
|
||||
slot="tooltip"
|
||||
{helpSections}
|
||||
on:mount={(e) => {
|
||||
modal = e.detail.modal;
|
||||
carousel = e.detail.carousel;
|
||||
}}
|
||||
/>
|
||||
<InputBox>
|
||||
<label>
|
||||
<input type="radio" bind:group={mode} value={DisplayMode.Young} />
|
||||
|
|
|
|||
|
|
@ -99,6 +99,7 @@ export class ImportCsvState {
|
|||
|
||||
const shouldRefetchMetadata = this.shouldRefetchMetadata(changed);
|
||||
if (shouldRefetchMetadata) {
|
||||
const { globalTags, updatedTags } = changed;
|
||||
changed = await getCsvMetadata({
|
||||
path: this.path,
|
||||
delimiter: changed.delimiter,
|
||||
|
|
@ -106,6 +107,9 @@ export class ImportCsvState {
|
|||
deckId: getDeckId(changed) ?? undefined,
|
||||
isHtml: changed.isHtml,
|
||||
});
|
||||
// carry over tags
|
||||
changed.globalTags = globalTags;
|
||||
changed.updatedTags = updatedTags;
|
||||
}
|
||||
|
||||
const globalNotetype = getGlobalNotetype(changed);
|
||||
|
|
|
|||
26
yarn.lock
26
yarn.lock
|
|
@ -1661,13 +1661,6 @@ __metadata:
|
|||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@types/trusted-types@npm:^2.0.7":
|
||||
version: 2.0.7
|
||||
resolution: "@types/trusted-types@npm:2.0.7"
|
||||
checksum: 10c0/4c4855f10de7c6c135e0d32ce462419d8abbbc33713b31d294596c0cc34ae1fa6112a2f9da729c8f7a20707782b0d69da3b1f8df6645b0366d08825ca1522e0c
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@typescript-eslint/eslint-plugin@npm:^5.60.1":
|
||||
version: 5.62.0
|
||||
resolution: "@typescript-eslint/eslint-plugin@npm:5.62.0"
|
||||
|
|
@ -2024,7 +2017,6 @@ __metadata:
|
|||
cross-env: "npm:^7.0.2"
|
||||
d3: "npm:^7.0.0"
|
||||
diff: "npm:^5.0.0"
|
||||
dompurify: "npm:^3.2.5"
|
||||
dprint: "npm:^0.47.2"
|
||||
esbuild: "npm:^0.25.0"
|
||||
esbuild-sass-plugin: "npm:^2"
|
||||
|
|
@ -2392,9 +2384,9 @@ __metadata:
|
|||
linkType: hard
|
||||
|
||||
"caniuse-lite@npm:^1.0.30001431, caniuse-lite@npm:^1.0.30001524, caniuse-lite@npm:^1.0.30001669":
|
||||
version: 1.0.30001671
|
||||
resolution: "caniuse-lite@npm:1.0.30001671"
|
||||
checksum: 10c0/9bb81be7be641fdcdf4d3722b661d4204cc203a489c16080503a72b1605bd5c1061f8ae2452cc6c15d6957c818182824eb34e6569521051795f42cd14e844f99
|
||||
version: 1.0.30001714
|
||||
resolution: "caniuse-lite@npm:1.0.30001714"
|
||||
checksum: 10c0/b0e3372f018c5c177912f0282af98049057d83c80846293a4e3df728644a622db42a9e8971d6b7708d76e0fd4e9f6d5ce93802cf4e6818de80fdf371dc0f6a06
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
|
|
@ -3176,18 +3168,6 @@ __metadata:
|
|||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"dompurify@npm:^3.2.5":
|
||||
version: 3.2.5
|
||||
resolution: "dompurify@npm:3.2.5"
|
||||
dependencies:
|
||||
"@types/trusted-types": "npm:^2.0.7"
|
||||
dependenciesMeta:
|
||||
"@types/trusted-types":
|
||||
optional: true
|
||||
checksum: 10c0/b564167cc588933ad2d25c185296716bdd7124e9d2a75dac76efea831bb22d1230ce5205a1ab6ce4c1010bb32ac35f7a5cb2dd16c78cbf382111f1228362aa59
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"domutils@npm:^3.0.1":
|
||||
version: 3.1.0
|
||||
resolution: "domutils@npm:3.1.0"
|
||||
|
|
|
|||
Loading…
Reference in a new issue