mirror of
https://github.com/ankitects/anki.git
synced 2025-09-18 14:02:21 -04:00
Merge branch 'main' into patch-7
This commit is contained in:
commit
9f9661e8db
26 changed files with 2734 additions and 3276 deletions
|
@ -237,6 +237,7 @@ Kevin Nakamura <grinkers@grinkers.net>
|
|||
Bradley Szoke <bradleyszoke@gmail.com>
|
||||
jcznk <https://github.com/jcznk>
|
||||
Thomas Rixen <thomas.rixen@student.uclouvain.be>
|
||||
Siyuan Mattuwu Yan <syan4@ualberta.ca>
|
||||
|
||||
********************
|
||||
|
||||
|
|
5
Cargo.lock
generated
5
Cargo.lock
generated
|
@ -3959,6 +3959,7 @@ dependencies = [
|
|||
"anki_process",
|
||||
"anyhow",
|
||||
"camino",
|
||||
"serde_json",
|
||||
"walkdir",
|
||||
"which",
|
||||
]
|
||||
|
@ -5974,9 +5975,9 @@ checksum = "56199f7ddabf13fe5074ce809e7d3f42b42ae711800501b5b16ea82ad029c39d"
|
|||
|
||||
[[package]]
|
||||
name = "slab"
|
||||
version = "0.4.10"
|
||||
version = "0.4.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "04dc19736151f35336d325007ac991178d504a119863a2fcb3758cdb5e52c50d"
|
||||
checksum = "7a2ae44ef20feb57a68b23d846850f861394c2e02dc425a50098ae8c90267589"
|
||||
|
||||
[[package]]
|
||||
name = "slotmap"
|
||||
|
|
|
@ -169,7 +169,7 @@ fn build_rsbridge(build: &mut Build) -> Result<()> {
|
|||
|
||||
pub fn check_rust(build: &mut Build) -> Result<()> {
|
||||
let inputs = inputs![
|
||||
glob!("{rslib/**,pylib/rsbridge/**,ftl/**,build/**,qt/launcher/**}"),
|
||||
glob!("{rslib/**,pylib/rsbridge/**,ftl/**,build/**,qt/launcher/**,tools/minilints/**}"),
|
||||
"Cargo.lock",
|
||||
"Cargo.toml",
|
||||
"rust-toolchain.toml",
|
||||
|
|
5784
cargo/licenses.json
5784
cargo/licenses.json
File diff suppressed because it is too large
Load diff
|
@ -176,7 +176,7 @@ class MnemoFact:
|
|||
try:
|
||||
fact_view = self.cards[0].fact_view_id
|
||||
except IndexError as err:
|
||||
raise Exception(f"Fact {id} has no cards") from err
|
||||
raise Exception(f"Fact {self.id} has no cards") from err
|
||||
|
||||
if fact_view.startswith("1.") or fact_view.startswith("1::"):
|
||||
return FrontOnly
|
||||
|
@ -187,7 +187,7 @@ class MnemoFact:
|
|||
elif fact_view.startswith("5.1"):
|
||||
return Cloze
|
||||
|
||||
raise Exception(f"Fact {id} has unknown fact view: {fact_view}")
|
||||
raise Exception(f"Fact {self.id} has unknown fact view: {fact_view}")
|
||||
|
||||
def anki_fields(self, fact_view: type[MnemoFactView]) -> list[str]:
|
||||
return [munge_field(self.fields.get(k, "")) for k in fact_view.field_keys]
|
||||
|
|
Binary file not shown.
Before Width: | Height: | Size: 727 B |
27
qt/aqt/data/qt/icons/media-record.svg
Normal file
27
qt/aqt/data/qt/icons/media-record.svg
Normal file
|
@ -0,0 +1,27 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg width="21" height="21" viewBox="0 0 21 21" version="1.1"
|
||||
xmlns="http://www.w3.org/2000/svg">
|
||||
<g id="Layer-1" transform="translate(0.5,0.5)">
|
||||
<rect x="0" y="0" width="20" height="20" fill="none"/>
|
||||
<g transform="translate(14.8974,6.3648)">
|
||||
<path d="M0,0C0,3.403 -2.042,6.161 -4.56,6.161C-7.078,6.161 -9.12,3.403 -9.12,0C-9.12,-3.403 -7.078,-6.161 -4.56,-6.161C-2.042,-6.161 0,-3.403 0,0"
|
||||
fill="black" fill-rule="nonzero"/>
|
||||
</g>
|
||||
<g transform="matrix(0,-1,-1,0,10.3374,1.8048)">
|
||||
<ellipse cx="-4.56" cy="0" rx="6.161" ry="4.56"
|
||||
fill="none" stroke="black" stroke-width="0.25"/>
|
||||
</g>
|
||||
<g transform="translate(3.1987,14.4958)">
|
||||
<path d="M0,-9.484C-0.76,-4.212 3.287,0 7.12,-0.046C10.864,-0.09 14.742,-4.199 14.076,-9.343"
|
||||
fill="none" stroke="black" stroke-width="2" fill-rule="nonzero"/>
|
||||
</g>
|
||||
<g transform="matrix(-1,0,0,1,20.573,18.613)">
|
||||
<rect x="5.387" y="0.601" width="9.799" height="0.185"
|
||||
fill="none" stroke="black" stroke-width="2"/>
|
||||
</g>
|
||||
<g transform="matrix(-1,0,0,1,20.741,13.51)">
|
||||
<rect x="9.899" y="1.163" width="0.943" height="4.164"
|
||||
fill="none" stroke="black" stroke-width="2"/>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 1.3 KiB |
|
@ -483,7 +483,7 @@ def update_deck_configs() -> bytes:
|
|||
update.abort = True
|
||||
|
||||
def on_success(changes: OpChanges) -> None:
|
||||
if isinstance(window := aqt.mw.app.activeWindow(), DeckOptionsDialog):
|
||||
if isinstance(window := aqt.mw.app.activeModalWidget(), DeckOptionsDialog):
|
||||
window.reject()
|
||||
|
||||
def handle_on_main() -> None:
|
||||
|
@ -511,7 +511,7 @@ def set_scheduling_states() -> bytes:
|
|||
|
||||
def import_done() -> bytes:
|
||||
def update_window_modality() -> None:
|
||||
if window := aqt.mw.app.activeWindow():
|
||||
if window := aqt.mw.app.activeModalWidget():
|
||||
from aqt.import_export.import_dialog import ImportDialog
|
||||
|
||||
if isinstance(window, ImportDialog):
|
||||
|
@ -529,7 +529,7 @@ def import_request(endpoint: str) -> bytes:
|
|||
response.ParseFromString(output)
|
||||
|
||||
def handle_on_main() -> None:
|
||||
window = aqt.mw.app.activeWindow()
|
||||
window = aqt.mw.app.activeModalWidget()
|
||||
on_op_finished(aqt.mw, response, window)
|
||||
|
||||
aqt.mw.taskman.run_on_main(handle_on_main)
|
||||
|
@ -569,7 +569,7 @@ def change_notetype() -> bytes:
|
|||
data = request.data
|
||||
|
||||
def handle_on_main() -> None:
|
||||
window = aqt.mw.app.activeWindow()
|
||||
window = aqt.mw.app.activeModalWidget()
|
||||
if isinstance(window, ChangeNotetypeDialog):
|
||||
window.save(data)
|
||||
|
||||
|
@ -579,7 +579,7 @@ def change_notetype() -> bytes:
|
|||
|
||||
def deck_options_require_close() -> bytes:
|
||||
def handle_on_main() -> None:
|
||||
window = aqt.mw.app.activeWindow()
|
||||
window = aqt.mw.app.activeModalWidget()
|
||||
if isinstance(window, DeckOptionsDialog):
|
||||
window.require_close()
|
||||
|
||||
|
@ -591,7 +591,7 @@ def deck_options_require_close() -> bytes:
|
|||
|
||||
def deck_options_ready() -> bytes:
|
||||
def handle_on_main() -> None:
|
||||
window = aqt.mw.app.activeWindow()
|
||||
window = aqt.mw.app.activeModalWidget()
|
||||
if isinstance(window, DeckOptionsDialog):
|
||||
window.set_ready()
|
||||
|
||||
|
|
|
@ -743,7 +743,8 @@ class RecordDialog(QDialog):
|
|||
def _setup_dialog(self) -> None:
|
||||
self.setWindowTitle("Anki")
|
||||
icon = QLabel()
|
||||
icon.setPixmap(QPixmap("icons:media-record.png"))
|
||||
qicon = QIcon("icons:media-record.svg")
|
||||
icon.setPixmap(qicon.pixmap(60, 60))
|
||||
self.label = QLabel("...")
|
||||
hbox = QHBoxLayout()
|
||||
hbox.addWidget(icon)
|
||||
|
|
|
@ -73,7 +73,7 @@ def handle_sync_error(mw: aqt.main.AnkiQt, err: Exception) -> None:
|
|||
elif isinstance(err, Interrupted):
|
||||
# no message to show
|
||||
return
|
||||
show_warning(str(err))
|
||||
show_warning(str(err), parent=mw)
|
||||
|
||||
|
||||
def on_normal_sync_timer(mw: aqt.main.AnkiQt) -> None:
|
||||
|
|
|
@ -226,29 +226,45 @@ def ask_user_dialog(
|
|||
)
|
||||
|
||||
|
||||
def show_info(text: str, callback: Callable | None = None, **kwargs: Any) -> MessageBox:
|
||||
def show_info(
|
||||
text: str,
|
||||
callback: Callable | None = None,
|
||||
parent: QWidget | None = None,
|
||||
**kwargs: Any,
|
||||
) -> MessageBox:
|
||||
"Show a small info window with an OK button."
|
||||
if "icon" not in kwargs:
|
||||
kwargs["icon"] = QMessageBox.Icon.Information
|
||||
return MessageBox(
|
||||
text,
|
||||
callback=(lambda _: callback()) if callback is not None else None,
|
||||
parent=parent,
|
||||
**kwargs,
|
||||
)
|
||||
|
||||
|
||||
def show_warning(
|
||||
text: str, callback: Callable | None = None, **kwargs: Any
|
||||
text: str,
|
||||
callback: Callable | None = None,
|
||||
parent: QWidget | None = None,
|
||||
**kwargs: Any,
|
||||
) -> MessageBox:
|
||||
"Show a small warning window with an OK button."
|
||||
return show_info(text, icon=QMessageBox.Icon.Warning, callback=callback, **kwargs)
|
||||
return show_info(
|
||||
text, icon=QMessageBox.Icon.Warning, callback=callback, parent=parent, **kwargs
|
||||
)
|
||||
|
||||
|
||||
def show_critical(
|
||||
text: str, callback: Callable | None = None, **kwargs: Any
|
||||
text: str,
|
||||
callback: Callable | None = None,
|
||||
parent: QWidget | None = None,
|
||||
**kwargs: Any,
|
||||
) -> MessageBox:
|
||||
"Show a small critical error window with an OK button."
|
||||
return show_info(text, icon=QMessageBox.Icon.Critical, callback=callback, **kwargs)
|
||||
return show_info(
|
||||
text, icon=QMessageBox.Icon.Critical, callback=callback, parent=parent, **kwargs
|
||||
)
|
||||
|
||||
|
||||
def showWarning(
|
||||
|
|
|
@ -33,6 +33,12 @@ class _MacOSHelper:
|
|||
"On completion, file should be saved if no error has arrived."
|
||||
self._dll.end_wav_record()
|
||||
|
||||
def disable_appnap(self) -> None:
|
||||
self._dll.disable_appnap()
|
||||
|
||||
def enable_appnap(self) -> None:
|
||||
self._dll.enable_appnap()
|
||||
|
||||
|
||||
# this must not be overwritten or deallocated
|
||||
@CFUNCTYPE(None, c_char_p) # type: ignore
|
||||
|
|
25
qt/mac/appnap.swift
Normal file
25
qt/mac/appnap.swift
Normal file
|
@ -0,0 +1,25 @@
|
|||
// Copyright: Ankitects Pty Ltd and contributors
|
||||
// License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
||||
|
||||
import Foundation
|
||||
|
||||
private var currentActivity: NSObjectProtocol?
|
||||
|
||||
@_cdecl("disable_appnap")
|
||||
public func disableAppNap() {
|
||||
// No-op if already assigned
|
||||
guard currentActivity == nil else { return }
|
||||
|
||||
currentActivity = ProcessInfo.processInfo.beginActivity(
|
||||
options: .userInitiatedAllowingIdleSystemSleep,
|
||||
reason: "AppNap is disabled"
|
||||
)
|
||||
}
|
||||
|
||||
@_cdecl("enable_appnap")
|
||||
public func enableAppNap() {
|
||||
guard let activity = currentActivity else { return }
|
||||
|
||||
ProcessInfo.processInfo.endActivity(activity)
|
||||
currentActivity = nil
|
||||
}
|
|
@ -15,6 +15,7 @@ echo "Building macOS helper dylib..."
|
|||
# Create the wheel using uv
|
||||
echo "Creating wheel..."
|
||||
cd "$SCRIPT_DIR"
|
||||
rm -rf dist
|
||||
"$PROJ_ROOT/out/extracted/uv/uv" build --wheel
|
||||
|
||||
echo "Build complete!"
|
||||
|
|
|
@ -1,8 +1,6 @@
|
|||
# Copyright: Ankitects Pty Ltd and contributors
|
||||
# License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
||||
|
||||
import os
|
||||
import platform
|
||||
import subprocess
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
|
|
@ -4,7 +4,7 @@ build-backend = "hatchling.build"
|
|||
|
||||
[project]
|
||||
name = "anki-mac-helper"
|
||||
version = "0.1.0"
|
||||
version = "0.1.1"
|
||||
description = "Small support library for Anki on Macs"
|
||||
requires-python = ">=3.9"
|
||||
license = { text = "AGPL-3.0-or-later" }
|
||||
|
|
14
qt/mac/update-launcher-env
Executable file
14
qt/mac/update-launcher-env
Executable file
|
@ -0,0 +1,14 @@
|
|||
#!/bin/bash
|
||||
#
|
||||
# Build and install into the launcher venv
|
||||
|
||||
set -e
|
||||
|
||||
./build.sh
|
||||
if [[ "$OSTYPE" == "darwin"* ]]; then
|
||||
export VIRTUAL_ENV=$HOME/Library/Application\ Support/AnkiProgramFiles/.venv
|
||||
else
|
||||
export VIRTUAL_ENV=$HOME/.local/share/AnkiProgramFiles/.venv
|
||||
fi
|
||||
../../out/extracted/uv/uv pip install dist/*.whl
|
||||
|
|
@ -12,7 +12,7 @@ dependencies = [
|
|||
"send2trash",
|
||||
"waitress>=2.0.0",
|
||||
"pywin32; sys.platform == 'win32'",
|
||||
"anki-mac-helper; sys.platform == 'darwin'",
|
||||
"anki-mac-helper>=0.1.1; sys.platform == 'darwin'",
|
||||
"pip-system-certs!=5.1",
|
||||
"pyqt6>=6.2",
|
||||
"pyqt6-webengine>=6.2",
|
||||
|
|
|
@ -99,8 +99,6 @@ impl Collection {
|
|||
historical_retention.unwrap_or(0.9),
|
||||
ignore_before,
|
||||
)?;
|
||||
let preset_desired_retention =
|
||||
req.as_ref().map(|w| w.preset_desired_retention).unwrap();
|
||||
let mut progress = self.new_progress_handler::<ComputeMemoryProgress>();
|
||||
progress.update(false, |s| s.total_cards = items.len() as u32)?;
|
||||
for (idx, (card_id, item)) in items.into_iter().enumerate() {
|
||||
|
@ -108,6 +106,7 @@ impl Collection {
|
|||
let mut card = self.storage.get_card(card_id)?.or_not_found(card_id)?;
|
||||
let original = card.clone();
|
||||
if let Some(req) = &req {
|
||||
let preset_desired_retention = req.preset_desired_retention;
|
||||
// Store decay and desired retention in the card so that add-ons, card info,
|
||||
// stats and browser search/sorts don't need to access the deck config.
|
||||
// Unlike memory states, scheduler doesn't use decay and dr stored in the card.
|
||||
|
|
|
@ -142,10 +142,11 @@ impl Collection {
|
|||
// 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 fsrs_data = self.compute_memory_state(c.id)?;
|
||||
c.memory_state = fsrs_data.state.map(Into::into);
|
||||
c.desired_retention = Some(fsrs_data.desired_retention);
|
||||
c.decay = Some(fsrs_data.decay);
|
||||
self.storage.update_card(c)?;
|
||||
}
|
||||
}
|
||||
let days_elapsed = self.timing_today().unwrap().days_elapsed as i32;
|
||||
|
@ -293,7 +294,8 @@ impl Collection {
|
|||
(
|
||||
*result.memorized_cnt_per_day.last().unwrap_or(&0.),
|
||||
result.cost_per_day.iter().sum::<f32>(),
|
||||
result.review_cnt_per_day.iter().sum::<usize>() as u32,
|
||||
result.review_cnt_per_day.iter().sum::<usize>() as u32
|
||||
+ result.learn_cnt_per_day.iter().sum::<usize>() as u32,
|
||||
),
|
||||
))
|
||||
})
|
||||
|
|
|
@ -12,5 +12,6 @@ anki_io.workspace = true
|
|||
anki_process.workspace = true
|
||||
anyhow.workspace = true
|
||||
camino.workspace = true
|
||||
serde_json.workspace = true
|
||||
walkdir.workspace = true
|
||||
which.workspace = true
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
// License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
||||
|
||||
use std::cell::LazyCell;
|
||||
use std::collections::BTreeMap;
|
||||
use std::collections::HashSet;
|
||||
use std::env;
|
||||
use std::fs;
|
||||
|
@ -148,7 +149,7 @@ impl LintContext {
|
|||
|
||||
if last_author == "49699333+dependabot[bot]@users.noreply.github.com" {
|
||||
println!("Dependabot whitelisted.");
|
||||
return Ok(());
|
||||
std::process::exit(0);
|
||||
} else if all_contributors.contains(last_author.as_str()) {
|
||||
return Ok(());
|
||||
}
|
||||
|
@ -267,5 +268,16 @@ fn generate_licences() -> Result<String> {
|
|||
"--manifest-path",
|
||||
"rslib/Cargo.toml",
|
||||
])?;
|
||||
Ok(output.stdout)
|
||||
|
||||
let licenses: Vec<BTreeMap<String, serde_json::Value>> = serde_json::from_str(&output.stdout)?;
|
||||
|
||||
let filtered: Vec<BTreeMap<String, serde_json::Value>> = licenses
|
||||
.into_iter()
|
||||
.map(|mut entry| {
|
||||
entry.remove("version");
|
||||
entry
|
||||
})
|
||||
.collect();
|
||||
|
||||
Ok(serde_json::to_string_pretty(&filtered)?)
|
||||
}
|
||||
|
|
|
@ -12,7 +12,6 @@ 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
|
||||
|
@ -25,22 +24,9 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
|||
style:--container-margin="0"
|
||||
>
|
||||
<div class="position-relative">
|
||||
{#if onTitleClick}
|
||||
<span
|
||||
on:click={onTitleClick}
|
||||
on:keydown={onTitleClick}
|
||||
role="button"
|
||||
tabindex="0"
|
||||
>
|
||||
<h1>
|
||||
{title}
|
||||
</h1>
|
||||
</span>
|
||||
{:else}
|
||||
<h1>
|
||||
{title}
|
||||
</h1>
|
||||
{/if}
|
||||
<h1>
|
||||
{title}
|
||||
</h1>
|
||||
<div class="help-badge position-absolute" class:rtl>
|
||||
<slot name="tooltip" />
|
||||
</div>
|
||||
|
|
|
@ -8,7 +8,6 @@ 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}
|
||||
|
@ -19,8 +18,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} {onTitleClick}>
|
||||
<slot slot="tooltip" name="tooltip"></slot>
|
||||
<TitledContainer class="d-flex flex-column" {title}>
|
||||
<slot name="tooltip" slot="tooltip"></slot>
|
||||
<div class="graph d-flex flex-grow-1 flex-column justify-content-center">
|
||||
{#if subtitle}
|
||||
<div class="subtitle">{subtitle}</div>
|
||||
|
|
|
@ -57,22 +57,29 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
|||
|
||||
const title = tr.statisticsTrueRetentionTitle();
|
||||
const subtitle = tr.statisticsTrueRetentionSubtitle();
|
||||
const onTitleClick = () => {
|
||||
const onHelpClick = () => {
|
||||
openHelpModal(Object.keys(retentionHelp).indexOf("trueRetention"));
|
||||
};
|
||||
</script>
|
||||
|
||||
<Graph {title} {subtitle} {onTitleClick}>
|
||||
<HelpModal
|
||||
title={tr.statisticsTrueRetentionTitle()}
|
||||
url={HelpPage.DeckOptions.fsrs}
|
||||
<Graph {title} {subtitle}>
|
||||
<div
|
||||
slot="tooltip"
|
||||
{helpSections}
|
||||
on:mount={(e) => {
|
||||
modal = e.detail.modal;
|
||||
carousel = e.detail.carousel;
|
||||
}}
|
||||
/>
|
||||
onclick={onHelpClick}
|
||||
onkeydown={onHelpClick}
|
||||
role="button"
|
||||
tabindex="-1"
|
||||
>
|
||||
<HelpModal
|
||||
title={tr.statisticsTrueRetentionTitle()}
|
||||
url={HelpPage.DeckOptions.fsrs}
|
||||
{helpSections}
|
||||
on:mount={(e) => {
|
||||
modal = e.detail.modal;
|
||||
carousel = e.detail.carousel;
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
<InputBox>
|
||||
<label>
|
||||
<input type="radio" bind:group={mode} value={DisplayMode.Young} />
|
||||
|
|
6
uv.lock
6
uv.lock
|
@ -147,10 +147,10 @@ dev = [
|
|||
|
||||
[[package]]
|
||||
name = "anki-mac-helper"
|
||||
version = "0.1.0"
|
||||
version = "0.1.1"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/b3/9f/c4d3e635ddbd2c6c24ff5454e96900fd2061b9abbb0198b9283446780d08/anki_mac_helper-0.1.0-py3-none-any.whl", hash = "sha256:ed449aba27ea3bc7999054afa10dacf08ef856ed7af46526d9c8599d8179a618", size = 40637, upload-time = "2025-06-19T14:38:07.672Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/40/82/edb6194704defec181dddce8bc6a53c4afc72fa1f2bb4d68ffe244567767/anki_mac_helper-0.1.1-py3-none-any.whl", hash = "sha256:774a69cf9f0fe6d4b54949e2d6588a54a76ff51e78cbfc26b527d1ed3c5b34e3", size = 41812, upload-time = "2025-08-19T09:43:50.345Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -214,7 +214,7 @@ qt68 = [
|
|||
[package.metadata]
|
||||
requires-dist = [
|
||||
{ name = "anki-audio", marker = "(sys_platform == 'darwin' and extra == 'audio') or (sys_platform == 'win32' and extra == 'audio')", specifier = "==0.1.0" },
|
||||
{ name = "anki-mac-helper", marker = "sys_platform == 'darwin'" },
|
||||
{ name = "anki-mac-helper", marker = "sys_platform == 'darwin'", specifier = ">=0.1.1" },
|
||||
{ name = "beautifulsoup4" },
|
||||
{ name = "flask" },
|
||||
{ name = "flask-cors" },
|
||||
|
|
Loading…
Reference in a new issue