mirror of
https://github.com/ankitects/anki.git
synced 2025-11-06 12:47:11 -05:00
Merge branch 'main' into svelte-reviewer-bottom
This commit is contained in:
commit
45120aef14
20 changed files with 120 additions and 65 deletions
13
.idea.dist/repo.iml
Normal file
13
.idea.dist/repo.iml
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<module type="PYTHON_MODULE" version="4">
|
||||
<component name="NewModuleRootManager">
|
||||
<content url="file://$MODULE_DIR$">
|
||||
<sourceFolder url="file://$MODULE_DIR$/out/pylib" isTestSource="false" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/pylib" isTestSource="false" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/qt" isTestSource="false" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/extra" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/out/pyenv" />
|
||||
</content>
|
||||
<orderEntry type="sourceFolder" forTests="false" />
|
||||
</component>
|
||||
</module>
|
||||
|
|
@ -12,8 +12,7 @@
|
|||
"command": "tools/ninja.bat",
|
||||
"args": [
|
||||
"pylib",
|
||||
"qt",
|
||||
"extract:win_amd64_audio"
|
||||
"qt"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -247,6 +247,12 @@ Hanna Nilsén <hanni614@student.liu.se>
|
|||
Elias Johansson Lara <elias.johanssonlara@gmail.com>
|
||||
Toby Penner <tobypenner01@gmail.com>
|
||||
Danilo Spillebeen <spillebeendanilo@gmail.com>
|
||||
Matbe766 <matildabergstrom01@gmail.com>
|
||||
Amanda Sternberg <mandis.sternberg@gmail.com>
|
||||
arold0 <arold0@icloud.com>
|
||||
nav1s <nav1s@proton.me>
|
||||
Ranjit Odedra <ranjitodedra.dev@gmail.com>
|
||||
Eltaurus <https://github.com/Eltaurus-Lt>
|
||||
|
||||
********************
|
||||
|
||||
|
|
|
|||
|
|
@ -46,10 +46,14 @@ see and install a number of recommended extensions.
|
|||
|
||||
## PyCharm/IntelliJ
|
||||
|
||||
If you decide to use PyCharm instead of VS Code, there are somethings to be
|
||||
aware of.
|
||||
### Setting up Python environment
|
||||
|
||||
### Pylib References
|
||||
To make PyCharm recognize `anki` and `aqt` imports, you need to add source paths to _Settings > Project Structure_.
|
||||
You can copy the provided .idea.dist directory to set up the paths automatically:
|
||||
|
||||
You'll need to use File>Project Structure to tell IntelliJ that pylib/ is a
|
||||
sources root, so it knows references to 'anki' in aqt are valid.
|
||||
```
|
||||
mkdir .idea && cd .idea
|
||||
ln -sf ../.idea.dist/* .
|
||||
```
|
||||
|
||||
You also need to add a new Python interpreter under _Settings > Python > Interpreter_ pointing to the Python executable under `out/pyenv` (available after building Anki).
|
||||
|
|
|
|||
|
|
@ -382,7 +382,7 @@ deck-config-which-deck = Which deck would you like to display options for?
|
|||
## Messages related to the FSRS scheduler
|
||||
|
||||
deck-config-updating-cards = Updating cards: { $current_cards_count }/{ $total_cards_count }...
|
||||
deck-config-invalid-parameters = The provided FSRS parameters are invalid. Leave them blank to use the default parameters.
|
||||
deck-config-invalid-parameters = The provided FSRS parameters are invalid. Leave them blank to use the default values.
|
||||
deck-config-not-enough-history = Insufficient review history to perform this operation.
|
||||
deck-config-must-have-400-reviews =
|
||||
{ $count ->
|
||||
|
|
|
|||
|
|
@ -1,9 +1,10 @@
|
|||
launcher-title = Anki Launcher
|
||||
launcher-press-enter-to-install = Press the Enter/Return key on your keyboard to install or update Anki.
|
||||
launcher-press-enter-to-start = Press enter to start Anki.
|
||||
launcher-anki-will-start-shortly = Anki will start shortly.
|
||||
launcher-you-can-close-this-window = You can close this window.
|
||||
launcher-updating-anki = Updating Anki...
|
||||
launcher-latest-anki = Latest Anki (just press Enter)
|
||||
launcher-latest-anki = Install Latest Anki (default)
|
||||
launcher-choose-a-version = Choose a version
|
||||
launcher-sync-project-changes = Sync project changes
|
||||
launcher-keep-existing-version = Keep existing version ({ $current })
|
||||
|
|
@ -13,7 +14,7 @@ launcher-on = on
|
|||
launcher-off = off
|
||||
launcher-cache-downloads = Cache downloads: { $state }
|
||||
launcher-download-mirror = Download mirror: { $state }
|
||||
launcher-uninstall = Uninstall
|
||||
launcher-uninstall = Uninstall Anki
|
||||
launcher-invalid-input = Invalid input. Please try again.
|
||||
launcher-latest-releases = Latest releases: { $releases }
|
||||
launcher-enter-the-version-you-want = Enter the version you want to install:
|
||||
|
|
|
|||
|
|
@ -289,6 +289,10 @@ class AddCards(QMainWindow):
|
|||
def _add_current_note(self) -> None:
|
||||
note = self.editor.note
|
||||
|
||||
# Prevent adding a note that has already been added (e.g., from double-clicking)
|
||||
if note.id != 0:
|
||||
return
|
||||
|
||||
if not self._note_can_be_added(note):
|
||||
return
|
||||
|
||||
|
|
|
|||
|
|
@ -80,7 +80,7 @@ class SidebarItem:
|
|||
self.search_node = search_node
|
||||
self.on_expanded = on_expanded
|
||||
self.children: list[SidebarItem] = []
|
||||
self.tooltip: str | None = None
|
||||
self.tooltip: str = name
|
||||
self._parent_item: SidebarItem | None = None
|
||||
self._expanded = expanded
|
||||
self._row_in_parent: int | None = None
|
||||
|
|
|
|||
|
|
@ -85,11 +85,11 @@
|
|||
</item>
|
||||
<item row="2" column="2">
|
||||
<widget class="QSpinBox" name="limit">
|
||||
<property name="maximumSize">
|
||||
<size>
|
||||
<width>60</width>
|
||||
<height>16777215</height>
|
||||
</size>
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Fixed" vsizetype="Fixed">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="minimum">
|
||||
<number>1</number>
|
||||
|
|
@ -168,11 +168,11 @@
|
|||
</item>
|
||||
<item row="1" column="1">
|
||||
<widget class="QSpinBox" name="limit_2">
|
||||
<property name="maximumSize">
|
||||
<size>
|
||||
<width>60</width>
|
||||
<height>16777215</height>
|
||||
</size>
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Fixed" vsizetype="Fixed">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="minimum">
|
||||
<number>1</number>
|
||||
|
|
|
|||
|
|
@ -260,6 +260,7 @@ class Preferences(QDialog):
|
|||
self.update_login_status()
|
||||
self.confirm_sync_after_login()
|
||||
|
||||
self.update_network()
|
||||
sync_login(self.mw, on_success)
|
||||
|
||||
def sync_logout(self) -> None:
|
||||
|
|
|
|||
|
|
@ -209,11 +209,20 @@ def on_full_sync_timer(mw: aqt.main.AnkiQt, label: str) -> None:
|
|||
return
|
||||
sync_progress = progress.full_sync
|
||||
|
||||
# If we've reached total, show the "checking" label
|
||||
if sync_progress.transferred == sync_progress.total:
|
||||
label = tr.sync_checking()
|
||||
|
||||
total = sync_progress.total
|
||||
transferred = sync_progress.transferred
|
||||
|
||||
# Scale both to kilobytes with floor division
|
||||
max_for_bar = total // 1024
|
||||
value_for_bar = transferred // 1024
|
||||
|
||||
mw.progress.update(
|
||||
value=sync_progress.transferred,
|
||||
max=sync_progress.total,
|
||||
value=value_for_bar,
|
||||
max=max_for_bar,
|
||||
process=False,
|
||||
label=label,
|
||||
)
|
||||
|
|
|
|||
|
|
@ -7,4 +7,7 @@ fn main() {
|
|||
.manifest_required()
|
||||
.unwrap();
|
||||
}
|
||||
println!("cargo:rerun-if-changed=../../out/buildhash");
|
||||
let buildhash = std::fs::read_to_string("../../out/buildhash").unwrap_or_default();
|
||||
println!("cargo:rustc-env=BUILDHASH={buildhash}");
|
||||
}
|
||||
|
|
|
|||
|
|
@ -152,7 +152,9 @@ fn run() -> Result<()> {
|
|||
let sync_time = file_timestamp_secs(&state.sync_complete_marker);
|
||||
state.pyproject_modified_by_user = pyproject_time > sync_time;
|
||||
let pyproject_has_changed = state.pyproject_modified_by_user;
|
||||
if !launcher_requested && !pyproject_has_changed {
|
||||
let different_launcher = diff_launcher_was_installed(&state)?;
|
||||
|
||||
if !launcher_requested && !pyproject_has_changed && !different_launcher {
|
||||
// If no launcher request and venv is already up to date, launch Anki normally
|
||||
let args: Vec<String> = std::env::args().skip(1).collect();
|
||||
let cmd = build_python_command(&state, &args)?;
|
||||
|
|
@ -173,6 +175,8 @@ fn run() -> Result<()> {
|
|||
|
||||
ensure_os_supported()?;
|
||||
|
||||
println!("{}\n", state.tr.launcher_press_enter_to_install());
|
||||
|
||||
check_versions(&mut state);
|
||||
|
||||
main_menu_loop(&state)?;
|
||||
|
|
@ -323,7 +327,6 @@ fn handle_version_install_or_update(state: &State, choice: MainMenuChoice) -> Re
|
|||
}
|
||||
|
||||
command
|
||||
.env("UV_CACHE_DIR", &state.uv_cache_dir)
|
||||
.env("UV_PYTHON_INSTALL_DIR", &state.uv_python_install_dir)
|
||||
.env(
|
||||
"UV_HTTP_TIMEOUT",
|
||||
|
|
@ -342,10 +345,6 @@ fn handle_version_install_or_update(state: &State, choice: MainMenuChoice) -> Re
|
|||
}
|
||||
}
|
||||
|
||||
if state.no_cache_marker.exists() {
|
||||
command.env("UV_NO_CACHE", "1");
|
||||
}
|
||||
|
||||
match command.ensure_success() {
|
||||
Ok(_) => {
|
||||
// Sync succeeded
|
||||
|
|
@ -601,18 +600,27 @@ fn get_version_kind(state: &State) -> Result<Option<VersionKind>> {
|
|||
}
|
||||
|
||||
fn with_only_latest_patch(versions: &[String]) -> Vec<String> {
|
||||
// Only show the latest patch release for a given (major, minor)
|
||||
// Assumes versions are sorted in descending order (newest first)
|
||||
// Only show the latest patch release for a given (major, minor),
|
||||
// and exclude pre-releases if a newer major_minor exists
|
||||
let mut seen_major_minor = std::collections::HashSet::new();
|
||||
versions
|
||||
.iter()
|
||||
.filter(|v| {
|
||||
let (major, minor, _, _) = parse_version_for_filtering(v);
|
||||
let (major, minor, _, is_prerelease) = parse_version_for_filtering(v);
|
||||
if major == 2 {
|
||||
return true;
|
||||
}
|
||||
let major_minor = (major, minor);
|
||||
if seen_major_minor.contains(&major_minor) {
|
||||
false
|
||||
} else if is_prerelease
|
||||
&& seen_major_minor
|
||||
.iter()
|
||||
.any(|&(seen_major, seen_minor)| (seen_major, seen_minor) > (major, minor))
|
||||
{
|
||||
// Exclude pre-release if a newer major_minor exists
|
||||
false
|
||||
} else {
|
||||
seen_major_minor.insert(major_minor);
|
||||
true
|
||||
|
|
@ -1011,6 +1019,15 @@ fn uv_command(state: &State) -> Result<Command> {
|
|||
.env("UV_DEFAULT_INDEX", &pypi_mirror);
|
||||
}
|
||||
|
||||
if state.no_cache_marker.exists() {
|
||||
command.env("UV_NO_CACHE", "1");
|
||||
} else {
|
||||
command.env("UV_CACHE_DIR", &state.uv_cache_dir);
|
||||
}
|
||||
|
||||
// have uv use the system certstore instead of webpki-roots'
|
||||
command.env("UV_NATIVE_TLS", "1");
|
||||
|
||||
Ok(command)
|
||||
}
|
||||
|
||||
|
|
@ -1105,6 +1122,20 @@ fn show_mirror_submenu(state: &State) -> Result<()> {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
fn diff_launcher_was_installed(state: &State) -> Result<bool> {
|
||||
let launcher_version = option_env!("BUILDHASH").unwrap_or("dev").trim();
|
||||
let launcher_version_path = state.uv_install_root.join("launcher-version");
|
||||
if let Ok(content) = read_file(&launcher_version_path) {
|
||||
if let Ok(version_str) = String::from_utf8(content) {
|
||||
if version_str.trim() == launcher_version {
|
||||
return Ok(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
write_file(launcher_version_path, launcher_version)?;
|
||||
Ok(true)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
|
|
|||
|
|
@ -122,7 +122,7 @@ pub(crate) fn basic(tr: &I18n) -> Notetype {
|
|||
|
||||
pub(crate) fn basic_typing(tr: &I18n) -> Notetype {
|
||||
let mut nt = basic(tr);
|
||||
nt.config.original_stock_kind = StockKind::BasicTyping as i32;
|
||||
nt.config.original_stock_kind = OriginalStockKind::BasicTyping as i32;
|
||||
nt.name = tr.notetypes_basic_type_answer_name().into();
|
||||
let front = tr.notetypes_front_field();
|
||||
let back = tr.notetypes_back_field();
|
||||
|
|
@ -138,7 +138,7 @@ pub(crate) fn basic_typing(tr: &I18n) -> Notetype {
|
|||
|
||||
pub(crate) fn basic_forward_reverse(tr: &I18n) -> Notetype {
|
||||
let mut nt = basic(tr);
|
||||
nt.config.original_stock_kind = StockKind::BasicAndReversed as i32;
|
||||
nt.config.original_stock_kind = OriginalStockKind::BasicAndReversed as i32;
|
||||
nt.name = tr.notetypes_basic_reversed_name().into();
|
||||
let front = tr.notetypes_front_field();
|
||||
let back = tr.notetypes_back_field();
|
||||
|
|
@ -156,7 +156,7 @@ pub(crate) fn basic_forward_reverse(tr: &I18n) -> Notetype {
|
|||
|
||||
pub(crate) fn basic_optional_reverse(tr: &I18n) -> Notetype {
|
||||
let mut nt = basic_forward_reverse(tr);
|
||||
nt.config.original_stock_kind = StockKind::BasicOptionalReversed as i32;
|
||||
nt.config.original_stock_kind = OriginalStockKind::BasicOptionalReversed as i32;
|
||||
nt.name = tr.notetypes_basic_optional_reversed_name().into();
|
||||
let addrev = tr.notetypes_add_reverse_field();
|
||||
nt.add_field(addrev.as_ref());
|
||||
|
|
|
|||
|
|
@ -13,13 +13,7 @@ impl From<FSRSError> for AnkiError {
|
|||
FSRSError::OptimalNotFound => AnkiError::FsrsUnableToDetermineDesiredRetention,
|
||||
FSRSError::Interrupted => AnkiError::Interrupted,
|
||||
FSRSError::InvalidParameters => AnkiError::FsrsParamsInvalid,
|
||||
FSRSError::InvalidInput => AnkiError::InvalidInput {
|
||||
source: InvalidInputError {
|
||||
message: "invalid params provided".to_string(),
|
||||
source: None,
|
||||
backtrace: None,
|
||||
},
|
||||
},
|
||||
FSRSError::InvalidInput => AnkiError::FsrsParamsInvalid,
|
||||
FSRSError::InvalidDeckSize => AnkiError::InvalidInput {
|
||||
source: InvalidInputError {
|
||||
message: "no cards to simulate".to_string(),
|
||||
|
|
|
|||
|
|
@ -58,7 +58,7 @@ trait DiffTrait {
|
|||
if self.get_typed() == self.get_expected() {
|
||||
format_typeans!(format!(
|
||||
"<span class=typeGood>{}</span>",
|
||||
self.get_expected_original()
|
||||
htmlescape::encode_minimal(&self.get_expected_original())
|
||||
))
|
||||
} else {
|
||||
let output = self.to_tokens();
|
||||
|
|
@ -391,6 +391,15 @@ mod test {
|
|||
assert_eq!(ctx, "<code id=typeans>123</code>");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn correct_input_is_escaped() {
|
||||
let ctx = Diff::new("source <dir>/bin/activate", "source <dir>/bin/activate");
|
||||
assert_eq!(
|
||||
ctx.to_html(),
|
||||
"<code id=typeans><span class=typeGood>source <dir>/bin/activate</span></code>"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn correct_input_is_collapsed() {
|
||||
let ctx = Diff::new("123", "123");
|
||||
|
|
|
|||
|
|
@ -5,8 +5,6 @@ import os
|
|||
import sys
|
||||
|
||||
sys.path.extend(["pylib", "qt", "out/pylib", "out/qt"])
|
||||
if sys.platform == "win32":
|
||||
os.environ["PATH"] += ";out\\extracted\\win_amd64_audio"
|
||||
|
||||
import aqt
|
||||
|
||||
|
|
|
|||
|
|
@ -31,7 +31,6 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
|||
</script>
|
||||
|
||||
<script lang="ts">
|
||||
import { randomUUID } from "@tslib/uuid";
|
||||
import { onDestroy } from "svelte";
|
||||
import { writable } from "svelte/store";
|
||||
|
||||
|
|
@ -66,7 +65,7 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
|||
$: empty = title === "MathJax";
|
||||
$: encoded = encodeURIComponent(converted);
|
||||
|
||||
const uuid = randomUUID();
|
||||
const uuid = crypto.randomUUID();
|
||||
const imageHeight = writable(0);
|
||||
imageToHeightMap.set(uuid, imageHeight);
|
||||
|
||||
|
|
|
|||
|
|
@ -1,16 +0,0 @@
|
|||
// Copyright: Ankitects Pty Ltd and contributors
|
||||
// License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
||||
|
||||
/**
|
||||
* TODO replace with crypto.randomUUID
|
||||
*/
|
||||
export function randomUUID(): string {
|
||||
const value = `${1e7}-${1e3}-${4e3}-${8e3}-${1e11}`;
|
||||
|
||||
return value.replace(/[018]/g, (character: string): string =>
|
||||
(
|
||||
Number(character)
|
||||
^ (crypto.getRandomValues(new Uint8Array(1))[0]
|
||||
& (15 >> (Number(character) / 4)))
|
||||
).toString(16));
|
||||
}
|
||||
|
|
@ -6939,8 +6939,8 @@ __metadata:
|
|||
linkType: hard
|
||||
|
||||
"vite@npm:6":
|
||||
version: 6.3.6
|
||||
resolution: "vite@npm:6.3.6"
|
||||
version: 6.4.1
|
||||
resolution: "vite@npm:6.4.1"
|
||||
dependencies:
|
||||
esbuild: "npm:^0.25.0"
|
||||
fdir: "npm:^6.4.4"
|
||||
|
|
@ -6989,7 +6989,7 @@ __metadata:
|
|||
optional: true
|
||||
bin:
|
||||
vite: bin/vite.js
|
||||
checksum: 10c0/add701f1e72596c002275782e38d0389ab400c1be330c93a3009804d62db68097a936ca1c53c3301df3aaacfe5e328eab547060f31ef9c49a277ae50df6ad4fb
|
||||
checksum: 10c0/77bb4c5b10f2a185e7859cc9a81c789021bc18009b02900347d1583b453b58e4b19ff07a5e5a5b522b68fc88728460bb45a63b104d969e8c6a6152aea3b849f7
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue