mirror of
https://github.com/ankitects/anki.git
synced 2026-01-05 18:13:56 -05:00
Merge branch 'main' into fix-cargo-build-implicit-deps
This commit is contained in:
commit
962468e39e
9 changed files with 60 additions and 29 deletions
|
|
@ -234,7 +234,7 @@ class DeckBrowser:
|
|||
if node.collapsed:
|
||||
prefix = "+"
|
||||
else:
|
||||
prefix = "-"
|
||||
prefix = "−"
|
||||
|
||||
def indent() -> str:
|
||||
return " " * 6 * (node.level - 1)
|
||||
|
|
|
|||
|
|
@ -14,7 +14,7 @@ from markdown import markdown
|
|||
|
||||
import aqt
|
||||
from anki.collection import HelpPage
|
||||
from anki.errors import BackendError, Interrupted
|
||||
from anki.errors import BackendError, CardTypeError, Interrupted
|
||||
from anki.utils import is_win
|
||||
from aqt.addons import AddonManager, AddonMeta
|
||||
from aqt.qt import *
|
||||
|
|
@ -36,6 +36,14 @@ def show_exception(*, parent: QWidget, exception: Exception) -> None:
|
|||
global _mbox
|
||||
error_lines = []
|
||||
help_page = HelpPage.TROUBLESHOOTING
|
||||
|
||||
# default to PlainText
|
||||
text_format = Qt.TextFormat.PlainText
|
||||
|
||||
# set CardTypeError messages as rich text to allow HTML formatting
|
||||
if isinstance(exception, CardTypeError):
|
||||
text_format = Qt.TextFormat.RichText
|
||||
|
||||
if isinstance(exception, BackendError):
|
||||
if exception.context:
|
||||
error_lines.append(exception.context)
|
||||
|
|
@ -51,7 +59,7 @@ def show_exception(*, parent: QWidget, exception: Exception) -> None:
|
|||
)
|
||||
error_text = "\n".join(error_lines)
|
||||
print(error_lines)
|
||||
_mbox = _init_message_box(str(exception), error_text, help_page)
|
||||
_mbox = _init_message_box(str(exception), error_text, help_page, text_format)
|
||||
_mbox.show()
|
||||
|
||||
|
||||
|
|
@ -171,7 +179,10 @@ if not os.environ.get("DEBUG"):
|
|||
|
||||
|
||||
def _init_message_box(
|
||||
user_text: str, debug_text: str, help_page=HelpPage.TROUBLESHOOTING
|
||||
user_text: str,
|
||||
debug_text: str,
|
||||
help_page=HelpPage.TROUBLESHOOTING,
|
||||
text_format=Qt.TextFormat.PlainText,
|
||||
):
|
||||
global _mbox
|
||||
|
||||
|
|
@ -179,7 +190,7 @@ def _init_message_box(
|
|||
_mbox.setWindowTitle("Anki")
|
||||
_mbox.setText(user_text)
|
||||
_mbox.setIcon(QMessageBox.Icon.Warning)
|
||||
_mbox.setTextFormat(Qt.TextFormat.PlainText)
|
||||
_mbox.setTextFormat(text_format)
|
||||
|
||||
def show_help():
|
||||
openHelp(help_page)
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@ use crate::import_export::package::media::SafeMediaEntry;
|
|||
use crate::import_export::ImportProgress;
|
||||
use crate::media::files::add_hash_suffix_to_file_stem;
|
||||
use crate::media::files::sha1_of_reader;
|
||||
use crate::media::Checksums;
|
||||
use crate::prelude::*;
|
||||
use crate::progress::ThrottlingProgressHandler;
|
||||
|
||||
|
|
@ -75,7 +76,7 @@ impl Context<'_> {
|
|||
fn prepare_media(
|
||||
media_entries: Vec<SafeMediaEntry>,
|
||||
archive: &mut ZipArchive<File>,
|
||||
existing_sha1s: &HashMap<String, Sha1Hash>,
|
||||
existing_sha1s: &Checksums,
|
||||
progress: &mut ThrottlingProgressHandler<ImportProgress>,
|
||||
) -> Result<MediaUseMap> {
|
||||
let mut media_map = MediaUseMap::default();
|
||||
|
|
|
|||
|
|
@ -173,7 +173,9 @@ pub fn add_data_to_folder_uniquely<'a, P>(
|
|||
where
|
||||
P: AsRef<Path>,
|
||||
{
|
||||
let normalized_name = normalize_filename(desired_name);
|
||||
// force lowercase to account for case-insensitive filesystems
|
||||
// but not within normalize_filename, for existing media refs
|
||||
let normalized_name: Cow<_> = normalize_filename(desired_name).to_lowercase().into();
|
||||
|
||||
let mut target_path = folder.as_ref().join(normalized_name.as_ref());
|
||||
|
||||
|
|
@ -496,8 +498,14 @@ mod test {
|
|||
"test.mp3"
|
||||
);
|
||||
|
||||
// different contents
|
||||
// different contents, filenames differ only by case
|
||||
let h2 = sha1_of_data(b"hello1");
|
||||
assert_eq!(
|
||||
add_data_to_folder_uniquely(dpath, "Test.mp3", b"hello1", h2).unwrap(),
|
||||
"test-88fdd585121a4ccb3d1540527aee53a77c77abb8.mp3"
|
||||
);
|
||||
|
||||
// same contents, filenames differ only by case
|
||||
assert_eq!(
|
||||
add_data_to_folder_uniquely(dpath, "test.mp3", b"hello1", h2).unwrap(),
|
||||
"test-88fdd585121a4ccb3d1540527aee53a77c77abb8.mp3"
|
||||
|
|
|
|||
|
|
@ -6,7 +6,6 @@ pub mod files;
|
|||
mod service;
|
||||
|
||||
use std::borrow::Cow;
|
||||
use std::collections::HashMap;
|
||||
use std::path::Path;
|
||||
use std::path::PathBuf;
|
||||
|
||||
|
|
@ -22,6 +21,7 @@ use crate::progress::ThrottlingProgressHandler;
|
|||
use crate::sync::http_client::HttpSyncClient;
|
||||
use crate::sync::login::SyncAuth;
|
||||
use crate::sync::media::database::client::changetracker::ChangeTracker;
|
||||
pub use crate::sync::media::database::client::Checksums;
|
||||
use crate::sync::media::database::client::MediaDatabase;
|
||||
use crate::sync::media::database::client::MediaEntry;
|
||||
use crate::sync::media::progress::MediaSyncProgress;
|
||||
|
|
@ -157,7 +157,7 @@ impl MediaManager {
|
|||
pub fn all_checksums_after_checking(
|
||||
&self,
|
||||
progress: impl FnMut(usize) -> bool,
|
||||
) -> Result<HashMap<String, Sha1Hash>> {
|
||||
) -> Result<Checksums> {
|
||||
ChangeTracker::new(&self.media_folder, progress).register_changes(&self.db)?;
|
||||
self.db.all_registered_checksums()
|
||||
}
|
||||
|
|
@ -176,7 +176,7 @@ impl MediaManager {
|
|||
|
||||
/// All checksums without registering changes first.
|
||||
#[cfg(test)]
|
||||
pub(crate) fn all_checksums_as_is(&self) -> HashMap<String, [u8; 20]> {
|
||||
pub(crate) fn all_checksums_as_is(&self) -> Checksums {
|
||||
self.db.all_registered_checksums().unwrap()
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -18,6 +18,20 @@ use crate::prelude::*;
|
|||
|
||||
pub mod changetracker;
|
||||
|
||||
pub struct Checksums(HashMap<String, Sha1Hash>);
|
||||
|
||||
impl Checksums {
|
||||
// case-fold filenames when checking files to be imported
|
||||
// to account for case-insensitive filesystems
|
||||
pub fn get(&self, key: impl AsRef<str>) -> Option<&Sha1Hash> {
|
||||
self.0.get(key.as_ref().to_lowercase().as_str())
|
||||
}
|
||||
|
||||
pub fn contains_key(&self, key: impl AsRef<str>) -> bool {
|
||||
self.get(key).is_some()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq)]
|
||||
pub struct MediaEntry {
|
||||
pub fname: String,
|
||||
|
|
@ -175,11 +189,12 @@ delete from media where fname=?",
|
|||
}
|
||||
|
||||
/// Returns all filenames and checksums, where the checksum is not null.
|
||||
pub(crate) fn all_registered_checksums(&self) -> error::Result<HashMap<String, Sha1Hash>> {
|
||||
pub(crate) fn all_registered_checksums(&self) -> error::Result<Checksums> {
|
||||
self.db
|
||||
.prepare("SELECT fname, csum FROM media WHERE csum IS NOT NULL")?
|
||||
.query_and_then([], row_to_name_and_checksum)?
|
||||
.collect()
|
||||
.collect::<error::Result<_>>()
|
||||
.map(Checksums)
|
||||
}
|
||||
|
||||
pub(crate) fn force_resync(&self) -> error::Result<()> {
|
||||
|
|
|
|||
|
|
@ -202,7 +202,7 @@ fn sveltekit_temp_file(path: &str) -> bool {
|
|||
}
|
||||
|
||||
fn check_cargo_deny() -> Result<()> {
|
||||
Command::run("cargo install cargo-deny@0.18.3")?;
|
||||
Command::run("cargo install cargo-deny@0.18.6")?;
|
||||
Command::run("cargo deny check")?;
|
||||
Ok(())
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,7 +4,12 @@
|
|||
import { getRange, getSelection } from "./cross-browser";
|
||||
|
||||
function wrappedExceptForWhitespace(text: string, front: string, back: string): string {
|
||||
const match = text.match(/^(\s*)([^]*?)(\s*)$/)!;
|
||||
const normalizedText = text
|
||||
.replace(/ /g, " ")
|
||||
.replace(/ /g, " ")
|
||||
.replace(/\u00A0/g, " ");
|
||||
|
||||
const match = normalizedText.match(/^(\s*)([^]*?)(\s*)$/)!;
|
||||
return match[1] + front + match[2] + back + match[3];
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -37,7 +37,9 @@ export const addOrUpdateNote = async function(
|
|||
backExtra,
|
||||
tags,
|
||||
});
|
||||
showResult(mode.noteId, result, noteCount);
|
||||
if (result.note) {
|
||||
showResult(mode.noteId, result, noteCount);
|
||||
}
|
||||
} else {
|
||||
const result = await addImageOcclusionNote({
|
||||
// IOCloningMode is not used on mobile
|
||||
|
|
@ -55,23 +57,12 @@ export const addOrUpdateNote = async function(
|
|||
// show toast message
|
||||
const showResult = (noteId: number | null, result: OpChanges, count: number) => {
|
||||
const props = $state({
|
||||
message: "",
|
||||
type: "error" as "error" | "success",
|
||||
message: noteId ? tr.browsingCardsUpdated({ count: count }) : tr.importingCardsAdded({ count: count }),
|
||||
type: "success" as "error" | "success",
|
||||
showToast: true,
|
||||
});
|
||||
mount(Toast, {
|
||||
target: document.body,
|
||||
props,
|
||||
});
|
||||
|
||||
if (result.note) {
|
||||
const msg = noteId ? tr.browsingCardsUpdated({ count: count }) : tr.importingCardsAdded({ count: count });
|
||||
props.message = msg;
|
||||
props.type = "success";
|
||||
props.showToast = true;
|
||||
} else {
|
||||
const msg = tr.notetypesErrorGeneratingCloze();
|
||||
props.message = msg;
|
||||
props.showToast = true;
|
||||
}
|
||||
};
|
||||
|
|
|
|||
Loading…
Reference in a new issue