Merge branch 'main' into tag-nc

This commit is contained in:
Abdo 2025-09-25 07:04:09 +03:00 committed by GitHub
commit 4e4a5f8f52
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
11 changed files with 104 additions and 13 deletions

4
Cargo.lock generated
View file

@ -46,9 +46,9 @@ checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923"
[[package]] [[package]]
name = "ammonia" name = "ammonia"
version = "4.1.1" version = "4.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d6b346764dd0814805de8abf899fe03065bcee69bb1a4771c785817e39f3978f" checksum = "17e913097e1a2124b46746c980134e8c954bc17a6a59bb3fde96f088d126dde6"
dependencies = [ dependencies = [
"cssparser", "cssparser",
"html5ever 0.35.0", "html5ever 0.35.0",

View file

@ -51,7 +51,7 @@ ninja_gen = { "path" = "build/ninja_gen" }
unicase = "=2.6.0" # any changes could invalidate sqlite indexes unicase = "=2.6.0" # any changes could invalidate sqlite indexes
# normal # normal
ammonia = "4.1.0" ammonia = "4.1.2"
anyhow = "1.0.98" anyhow = "1.0.98"
async-compression = { version = "0.4.24", features = ["zstd", "tokio"] } async-compression = { version = "0.4.24", features = ["zstd", "tokio"] }
async-stream = "0.3.6" async-stream = "0.3.6"

View file

@ -20,6 +20,7 @@ service CollectionService {
rpc LatestProgress(generic.Empty) returns (Progress); rpc LatestProgress(generic.Empty) returns (Progress);
rpc SetWantsAbort(generic.Empty) returns (generic.Empty); rpc SetWantsAbort(generic.Empty) returns (generic.Empty);
rpc SetLoadBalancerEnabled(generic.Bool) returns (OpChanges); rpc SetLoadBalancerEnabled(generic.Bool) returns (OpChanges);
rpc GetCustomColours(generic.Empty) returns (GetCustomColoursResponse);
} }
// Implicitly includes any of the above methods that are not listed in the // Implicitly includes any of the above methods that are not listed in the
@ -163,3 +164,7 @@ message CreateBackupRequest {
bool force = 2; bool force = 2;
bool wait_for_completion = 3; bool wait_for_completion = 3;
} }
message GetCustomColoursResponse {
repeated string colours = 1;
}

View file

@ -18,7 +18,7 @@ from anki._legacy import DeprecatedNamesMixinForModule
TR = anki._fluent.LegacyTranslationEnum TR = anki._fluent.LegacyTranslationEnum
FormatTimeSpan = _pb.FormatTimespanRequest FormatTimeSpan = _pb.FormatTimespanRequest
# When adding new languages here, check lang_to_disk_lang() below
langs = sorted( langs = sorted(
[ [
("Afrikaans", "af_ZA"), ("Afrikaans", "af_ZA"),
@ -38,6 +38,7 @@ langs = sorted(
("Italiano", "it_IT"), ("Italiano", "it_IT"),
("lo jbobau", "jbo_EN"), ("lo jbobau", "jbo_EN"),
("Lenga d'òc", "oc_FR"), ("Lenga d'òc", "oc_FR"),
("Қазақша", "kk_KZ"),
("Magyar", "hu_HU"), ("Magyar", "hu_HU"),
("Nederlands", "nl_NL"), ("Nederlands", "nl_NL"),
("Norsk", "nb_NO"), ("Norsk", "nb_NO"),
@ -64,6 +65,7 @@ langs = sorted(
("Українська мова", "uk_UA"), ("Українська мова", "uk_UA"),
("Հայերեն", "hy_AM"), ("Հայերեն", "hy_AM"),
("עִבְרִית", "he_IL"), ("עִבְרִית", "he_IL"),
("ייִדיש", "yi"),
("العربية", "ar_SA"), ("العربية", "ar_SA"),
("فارسی", "fa_IR"), ("فارسی", "fa_IR"),
("ภาษาไทย", "th_TH"), ("ภาษาไทย", "th_TH"),
@ -104,6 +106,7 @@ compatMap = {
"it": "it_IT", "it": "it_IT",
"ja": "ja_JP", "ja": "ja_JP",
"jbo": "jbo_EN", "jbo": "jbo_EN",
"kk": "kk_KZ",
"ko": "ko_KR", "ko": "ko_KR",
"la": "la_LA", "la": "la_LA",
"mn": "mn_MN", "mn": "mn_MN",
@ -126,6 +129,7 @@ compatMap = {
"uk": "uk_UA", "uk": "uk_UA",
"uz": "uz_UZ", "uz": "uz_UZ",
"vi": "vi_VN", "vi": "vi_VN",
"yi": "yi",
} }
@ -233,7 +237,7 @@ def get_def_lang(user_lang: str | None = None) -> tuple[int, str]:
def is_rtl(lang: str) -> bool: def is_rtl(lang: str) -> bool:
return lang in ("he", "ar", "fa", "ug") return lang in ("he", "ar", "fa", "ug", "yi")
# strip off unicode isolation markers from a translated string # strip off unicode isolation markers from a translated string

View file

@ -226,6 +226,7 @@ def show(mw: aqt.AnkiQt) -> QDialog:
"Anon_0000", "Anon_0000",
"Bilolbek Normuminov", "Bilolbek Normuminov",
"Sagiv Marzini", "Sagiv Marzini",
"Zhanibek Rassululy",
) )
) )

View file

@ -170,13 +170,42 @@ def favicon() -> Response:
def _mime_for_path(path: str) -> str: def _mime_for_path(path: str) -> str:
"Mime type for provided path/filename." "Mime type for provided path/filename."
if path.endswith(".css"):
# some users may have invalid mime type in the Windows registry _, ext = os.path.splitext(path)
return "text/css" ext = ext.lower()
elif path.endswith(".js") or path.endswith(".mjs"):
return "application/javascript" # Badly-behaved apps on Windows can alter the standard mime types in the registry, which can completely
# break Anki's UI. So we hard-code the most common extensions.
mime_types = {
".css": "text/css",
".js": "application/javascript",
".mjs": "application/javascript",
".html": "text/html",
".htm": "text/html",
".svg": "image/svg+xml",
".png": "image/png",
".jpg": "image/jpeg",
".jpeg": "image/jpeg",
".gif": "image/gif",
".webp": "image/webp",
".ico": "image/x-icon",
".json": "application/json",
".woff": "font/woff",
".woff2": "font/woff2",
".ttf": "font/ttf",
".otf": "font/otf",
".mp3": "audio/mpeg",
".mp4": "video/mp4",
".webm": "video/webm",
".ogg": "audio/ogg",
".pdf": "application/pdf",
".txt": "text/plain",
}
if mime := mime_types.get(ext):
return mime
else: else:
# autodetect # fallback to mimetypes, which may consult the registry
mime, _encoding = mimetypes.guess_type(path) mime, _encoding = mimetypes.guess_type(path)
return mime or "application/octet-stream" return mime or "application/octet-stream"
@ -601,7 +630,7 @@ def deck_options_ready() -> bytes:
def save_custom_colours() -> bytes: def save_custom_colours() -> bytes:
colors = [ colors = [
QColorDialog.customColor(i).name(QColor.NameFormat.HexArgb) QColorDialog.customColor(i).name(QColor.NameFormat.HexRgb)
for i in range(QColorDialog.customCount()) for i in range(QColorDialog.customCount())
] ]
aqt.mw.col.set_config("customColorPickerPalette", colors) aqt.mw.col.set_config("customColorPickerPalette", colors)
@ -630,6 +659,7 @@ post_handler_list = [
exposed_backend_list = [ exposed_backend_list = [
# CollectionService # CollectionService
"latest_progress", "latest_progress",
"get_custom_colours",
# DeckService # DeckService
"get_deck_names", "get_deck_names",
# I18nService # I18nService

View file

@ -134,5 +134,8 @@ pub fn ensure_os_supported() -> Result<()> {
#[cfg(all(unix, not(target_os = "macos")))] #[cfg(all(unix, not(target_os = "macos")))]
unix::ensure_glibc_supported()?; unix::ensure_glibc_supported()?;
#[cfg(target_os = "windows")]
windows::ensure_windows_version_supported()?;
Ok(()) Ok(())
} }

View file

@ -38,6 +38,26 @@ fn is_windows_10() -> bool {
} }
} }
/// Ensures Windows 10 version 1809 or later
pub fn ensure_windows_version_supported() -> Result<()> {
unsafe {
let mut info = OSVERSIONINFOW {
dwOSVersionInfoSize: std::mem::size_of::<OSVERSIONINFOW>() as u32,
..Default::default()
};
if RtlGetVersion(&mut info).is_err() {
anyhow::bail!("Failed to get Windows version information");
}
if info.dwBuildNumber >= 17763 {
return Ok(());
}
anyhow::bail!("Windows 10 version 1809 or later is required.")
}
}
pub fn ensure_terminal_shown() -> Result<()> { pub fn ensure_terminal_shown() -> Result<()> {
unsafe { unsafe {
if !GetConsoleWindow().is_invalid() { if !GetConsoleWindow().is_invalid() {

View file

@ -1,8 +1,10 @@
// Copyright: Ankitects Pty Ltd and contributors // Copyright: Ankitects Pty Ltd and contributors
// License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html // License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
use anki_proto::collection::GetCustomColoursResponse;
use anki_proto::generic; use anki_proto::generic;
use crate::collection::Collection; use crate::collection::Collection;
use crate::config::ConfigKey;
use crate::error; use crate::error;
use crate::prelude::BoolKey; use crate::prelude::BoolKey;
use crate::prelude::Op; use crate::prelude::Op;
@ -62,4 +64,13 @@ impl crate::services::CollectionService for Collection {
}) })
.map(Into::into) .map(Into::into)
} }
fn get_custom_colours(
&mut self,
) -> error::Result<anki_proto::collection::GetCustomColoursResponse> {
let colours = self
.get_config_optional(ConfigKey::CustomColorPickerPalette)
.unwrap_or_default();
Ok(GetCustomColoursResponse { colours })
}
} }

View file

@ -71,6 +71,7 @@ pub(crate) enum ConfigKey {
NextNewCardPosition, NextNewCardPosition,
#[strum(to_string = "schedVer")] #[strum(to_string = "schedVer")]
SchedulerVersion, SchedulerVersion,
CustomColorPickerPalette,
} }
#[derive(PartialEq, Eq, Serialize_repr, Deserialize_repr, Clone, Copy, Debug)] #[derive(PartialEq, Eq, Serialize_repr, Deserialize_repr, Clone, Copy, Debug)]

View file

@ -55,6 +55,7 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
onWheelDragX, onWheelDragX,
} from "./tools/tool-zoom"; } from "./tools/tool-zoom";
import { fillMask } from "./tools/tool-fill"; import { fillMask } from "./tools/tool-fill";
import { getCustomColours, saveCustomColours } from "@generated/backend";
export let canvas; export let canvas;
export let iconSize; export let iconSize;
@ -76,6 +77,16 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
let colourRef: HTMLInputElement | undefined; let colourRef: HTMLInputElement | undefined;
const colour = writable(SHAPE_MASK_COLOR); const colour = writable(SHAPE_MASK_COLOR);
const customColorPickerPalette = writable<string[]>([]);
async function loadCustomColours() {
customColorPickerPalette.set(
(await getCustomColours({})).colours.filter(
(hex) => !hex.startsWith("#ffffff"),
),
);
}
function onClick(event: MouseEvent) { function onClick(event: MouseEvent) {
const upperCanvas = document.querySelector(".upper-canvas"); const upperCanvas = document.querySelector(".upper-canvas");
if (event.target == upperCanvas) { if (event.target == upperCanvas) {
@ -233,6 +244,7 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
on(document, "touchstart", onTouchstart), on(document, "touchstart", onTouchstart),
on(document, "mousemove", onMousemoveDocument), on(document, "mousemove", onMousemoveDocument),
); );
loadCustomColours();
}); });
onDestroy(() => { onDestroy(() => {
@ -241,7 +253,10 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
</script> </script>
<datalist id="colour-palette"> <datalist id="colour-palette">
<option value={SHAPE_MASK_COLOR}></option> <option>{SHAPE_MASK_COLOR}</option>
{#each $customColorPickerPalette as colour}
<option>{colour}</option>
{/each}
</datalist> </datalist>
<input <input
@ -251,6 +266,7 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
list="colour-palette" list="colour-palette"
value={SHAPE_MASK_COLOR} value={SHAPE_MASK_COLOR}
on:input={(e) => ($colour = e.currentTarget!.value)} on:input={(e) => ($colour = e.currentTarget!.value)}
on:change={() => saveCustomColours({})}
/> />
<div class="tool-bar-container" style:--fill-tool-colour={$colour}> <div class="tool-bar-container" style:--fill-tool-colour={$colour}>