Merge branch 'main' into sassy-comments

This commit is contained in:
Matthias Metelka 2022-10-20 23:38:31 +02:00
commit 876f1670cc
30 changed files with 327 additions and 134 deletions

1
Cargo.lock generated
View file

@ -118,6 +118,7 @@ dependencies = [
"unicase", "unicase",
"unicode-normalization", "unicode-normalization",
"utime", "utime",
"which",
"zip", "zip",
"zstd", "zstd",
] ]

View file

@ -579,6 +579,15 @@ alias(
], ],
) )
alias(
name = "which",
actual = "@raze__which__4_3_0//:which",
tags = [
"cargo-raze",
"manual",
],
)
alias( alias(
name = "zip", name = "zip",
actual = "@raze__zip__0_6_2//:zip", actual = "@raze__zip__0_6_2//:zip",

View file

@ -60,7 +60,7 @@ editing-collapse-field = Collapse field
editing-underline-text = Underline text editing-underline-text = Underline text
editing-unordered-list = Unordered list editing-unordered-list = Unordered list
editing-warning-cloze-deletions-will-not-work = Warning, cloze deletions will not work until you switch the type at the top to Cloze. editing-warning-cloze-deletions-will-not-work = Warning, cloze deletions will not work until you switch the type at the top to Cloze.
editing-toggle-mathjax-rendering = Toggle MathJax Rendering editing-mathjax-preview = MathJax Preview
editing-shrink-images = Shrink Images editing-shrink-images = Shrink Images
editing-close-html-tags = Auto-close HTML tags editing-close-html-tags = Auto-close HTML tags

View file

@ -579,6 +579,15 @@ alias(
], ],
) )
alias(
name = "which",
actual = "@raze__which__4_3_0//:which",
tags = [
"cargo-raze",
"manual",
],
)
alias( alias(
name = "zip", name = "zip",
actual = "@raze__zip__0_6_2//:zip", actual = "@raze__zip__0_6_2//:zip",

View file

@ -345,26 +345,19 @@ class AnkiApp(QApplication):
################################################## ##################################################
def eventFilter(self, src: Any, evt: QEvent) -> bool: def eventFilter(self, src: Any, evt: QEvent) -> bool:
if evt.type() == QEvent.Type.HoverEnter: pointer_classes = (
if ( QPushButton,
( QCheckBox,
isinstance( QRadioButton,
src, QMenu,
( # classes with PyQt5 compatibility proxy
QPushButton, without_qt5_compat_wrapper(QToolButton),
QCheckBox, without_qt5_compat_wrapper(QTabBar),
QRadioButton, )
# classes with PyQt5 compatibility proxy if evt.type() in [QEvent.Type.Enter, QEvent.Type.HoverEnter]:
without_qt5_compat_wrapper(QToolButton), if (isinstance(src, pointer_classes) and src.isEnabled()) or (
without_qt5_compat_wrapper(QTabBar), isinstance(src, without_qt5_compat_wrapper(QComboBox))
), and not src.isEditable()
)
)
and src.isEnabled()
or (
isinstance(src, without_qt5_compat_wrapper(QComboBox))
and not src.isEditable()
)
): ):
self.setOverrideCursor(QCursor(Qt.CursorShape.PointingHandCursor)) self.setOverrideCursor(QCursor(Qt.CursorShape.PointingHandCursor))
else: else:

View file

@ -226,6 +226,7 @@ def show(mw: aqt.AnkiQt) -> QDialog:
"Nicholas Flint", "Nicholas Flint",
"Daniel Vieira Memoria10X", "Daniel Vieira Memoria10X",
"Luka Warren", "Luka Warren",
"Christos Longros",
) )
) )

View file

@ -126,19 +126,22 @@ class Browser(QMainWindow):
self._closeEventHasCleanedUp = False self._closeEventHasCleanedUp = False
self.form = aqt.forms.browser.Ui_Dialog() self.form = aqt.forms.browser.Ui_Dialog()
self.form.setupUi(self) self.form.setupUi(self)
restoreGeom(self, "editor", 0)
restoreSplitter(self.form.splitter, "editor3")
self.form.splitter.setChildrenCollapsible(False) self.form.splitter.setChildrenCollapsible(False)
# set if exactly 1 row is selected; used by the previewer # set if exactly 1 row is selected; used by the previewer
self.card: Card | None = None self.card: Card | None = None
self.current_card: Card | None = None self.current_card: Card | None = None
self.setupSidebar() self.setupSidebar()
# make sure to call restoreState() after QDockWidget is attached to QMainWindow
restoreState(self, "editor")
self.setup_table() self.setup_table()
self.setupMenus() self.setupMenus()
self.setupHooks() self.setupHooks()
self.setupEditor() self.setupEditor()
# restoreXXX() should be called after all child widgets have been created
# and attached to QMainWindow
restoreGeom(self, "editor", 0)
restoreSplitter(self.form.splitter, "editor3")
restoreState(self, "editor")
# responsive layout # responsive layout
self.aspect_ratio = self.width() / self.height() self.aspect_ratio = self.width() / self.height()
self.set_layout(self.mw.pm.browser_layout(), True) self.set_layout(self.mw.pm.browser_layout(), True)

View file

@ -28,7 +28,8 @@ qlineargradient(
def general_styles(tm: ThemeManager) -> str: def general_styles(tm: ThemeManager) -> str:
return f""" return f"""
QFrame {{ QFrame,
QWidget {{
background: none; background: none;
}} }}
QPushButton, QPushButton,
@ -38,7 +39,7 @@ QLineEdit,
QListWidget, QListWidget,
QTreeWidget, QTreeWidget,
QListView {{ QListView {{
border: 1px solid {tm.var(colors.BORDER)}; border: 1px solid {tm.var(colors.BORDER_SUBTLE)};
border-radius: {tm.var(props.BORDER_RADIUS)}; border-radius: {tm.var(props.BORDER_RADIUS)};
}} }}
QLineEdit {{ QLineEdit {{
@ -58,6 +59,47 @@ QSpinBox {{
""" """
def menu_styles(tm: ThemeManager) -> str:
return f"""
QMenuBar {{
border-bottom: 1px solid {tm.var(colors.BORDER_FAINT)};
}}
QMenuBar::item {{
background-color: transparent;
padding: 2px 4px;
border-radius: {tm.var(props.BORDER_RADIUS)};
}}
QMenuBar::item:selected {{
background-color: {tm.var(colors.CANVAS_ELEVATED)};
}}
QMenu {{
background-color: {tm.var(colors.CANVAS_OVERLAY)};
border: 1px solid {tm.var(colors.BORDER_SUBTLE)};
padding: 4px;
}}
QMenu::item {{
background-color: transparent;
padding: 3px 14px;
margin-bottom: 4px;
}}
QMenu::item:selected {{
background-color: {tm.var(colors.CANVAS_INSET)};
color: {tm.var(colors.HIGHLIGHT_FG)};
border-radius: {tm.var(props.BORDER_RADIUS)};
}}
QMenu::separator {{
height: 1px;
background: {tm.var(colors.BORDER_SUBTLE)};
margin: 0 8px 4px 8px;
}}
QMenu::indicator {{
border: 1px solid {tm.var(colors.BORDER)};
margin-left: 6px;
margin-right: -6px;
}}
"""
def button_styles(tm: ThemeManager) -> str: def button_styles(tm: ThemeManager) -> str:
return f""" return f"""
QPushButton {{ QPushButton {{
@ -184,7 +226,7 @@ QTabWidget::pane {{
top: -15px; top: -15px;
padding-top: 1em; padding-top: 1em;
background: {tm.var(colors.CANVAS_ELEVATED)}; background: {tm.var(colors.CANVAS_ELEVATED)};
border: 1px solid {tm.var(colors.BORDER)}; border: 1px solid {tm.var(colors.BORDER_SUBTLE)};
border-radius: {tm.var(props.BORDER_RADIUS)}; border-radius: {tm.var(props.BORDER_RADIUS)};
}} }}
QTabWidget::tab-bar {{ QTabWidget::tab-bar {{
@ -196,7 +238,7 @@ QTabBar::tab {{
min-width: 8ex; min-width: 8ex;
}} }}
QTabBar::tab {{ QTabBar::tab {{
border: 1px solid {tm.var(colors.BORDER)}; border: 1px solid {tm.var(colors.BORDER_SUBTLE)};
}} }}
QTabBar::tab:first {{ QTabBar::tab:first {{
border-top-{tm.left()}-radius: {tm.var(props.BORDER_RADIUS)}; border-top-{tm.left()}-radius: {tm.var(props.BORDER_RADIUS)};
@ -225,7 +267,7 @@ def table_styles(tm: ThemeManager) -> str:
return f""" return f"""
QTableView {{ QTableView {{
border-radius: {tm.var(props.BORDER_RADIUS)}; border-radius: {tm.var(props.BORDER_RADIUS)};
gridline-color: {tm.var(colors.BORDER)}; gridline-color: {tm.var(colors.BORDER_SUBTLE)};
selection-background-color: {tm.var(colors.SELECTED_BG)}; selection-background-color: {tm.var(colors.SELECTED_BG)};
selection-color: {tm.var(colors.SELECTED_FG)}; selection-color: {tm.var(colors.SELECTED_FG)};
}} }}
@ -233,7 +275,7 @@ QHeaderView {{
background: {tm.var(colors.CANVAS)}; background: {tm.var(colors.CANVAS)};
}} }}
QHeaderView::section {{ QHeaderView::section {{
border: 1px solid {tm.var(colors.BORDER)}; border: 1px solid {tm.var(colors.BORDER_SUBTLE)};
background: { background: {
button_gradient( button_gradient(
tm.var(colors.BUTTON_GRADIENT_START), tm.var(colors.BUTTON_GRADIENT_START),
@ -261,19 +303,19 @@ QHeaderView::section:hover {{
}; };
}} }}
QHeaderView::section:first {{ QHeaderView::section:first {{
border-left: 1px solid {tm.var(colors.BORDER)}; border-left: 1px solid {tm.var(colors.BORDER_SUBTLE)};
border-top-left-radius: {tm.var(props.BORDER_RADIUS)}; border-top-left-radius: {tm.var(props.BORDER_RADIUS)};
}} }}
QHeaderView::section:!first {{ QHeaderView::section:!first {{
border-left: none; border-left: none;
}} }}
QHeaderView::section:last {{ QHeaderView::section:last {{
border-right: 1px solid {tm.var(colors.BORDER)}; border-right: 1px solid {tm.var(colors.BORDER_SUBTLE)};
border-top-right-radius: {tm.var(props.BORDER_RADIUS)}; border-top-right-radius: {tm.var(props.BORDER_RADIUS)};
}} }}
QHeaderView::section:only-one {{ QHeaderView::section:only-one {{
border-left: 1px solid {tm.var(colors.BORDER)}; border-left: 1px solid {tm.var(colors.BORDER_SUBTLE)};
border-right: 1px solid {tm.var(colors.BORDER)}; border-right: 1px solid {tm.var(colors.BORDER_SUBTLE)};
border-top-left-radius: {tm.var(props.BORDER_RADIUS)}; border-top-left-radius: {tm.var(props.BORDER_RADIUS)};
border-top-right-radius: {tm.var(props.BORDER_RADIUS)}; border-top-right-radius: {tm.var(props.BORDER_RADIUS)};
}} }}
@ -374,16 +416,16 @@ QRadioButton {{
margin: 2px 0; margin: 2px 0;
}} }}
QCheckBox::indicator, QCheckBox::indicator,
QRadioButton::indicator {{ QRadioButton::indicator,
QMenu::indicator {{
border: 1px solid {tm.var(colors.BORDER_SUBTLE)}; border: 1px solid {tm.var(colors.BORDER_SUBTLE)};
border-radius: {tm.var(props.BORDER_RADIUS)};
background: {tm.var(colors.CANVAS_INSET)}; background: {tm.var(colors.CANVAS_INSET)};
width: 16px; width: 16px;
height: 16px; height: 16px;
}} }}
QCheckBox::indicator {{ QRadioButton::indicator,
border-radius: {tm.var(props.BORDER_RADIUS)}; QMenu::indicator:exclusive {{
}}
QRadioButton::indicator {{
border-radius: 8px; border-radius: 8px;
}} }}
QCheckBox::indicator:hover, QCheckBox::indicator:hover,
@ -395,7 +437,8 @@ QRadioButton::indicator:checked:hover {{
height: 14px; height: 14px;
}} }}
QCheckBox::indicator:checked, QCheckBox::indicator:checked,
QRadioButton::indicator:checked {{ QRadioButton::indicator:checked,
QMenu::indicator:checked {{
image: url({tm.themed_icon("mdi:check")}); image: url({tm.themed_icon("mdi:check")});
}} }}
QCheckBox::indicator:indeterminate {{ QCheckBox::indicator:indeterminate {{
@ -445,20 +488,3 @@ QScrollBar::sub-line {{
background: none; background: none;
}} }}
""" """
def win10_styles(tm: ThemeManager) -> str:
return f"""
/* day mode is missing a bottom border; background must be
also set for border to apply */
QMenuBar {{
border-bottom: 1px solid {tm.var(colors.BORDER)};
background: {tm.var(colors.CANVAS) if tm.night_mode else "white"};
}}
/* qt bug? setting the above changes the browser sidebar
to white as well, so set it back */
QTreeWidget {{
background: {tm.var(colors.CANVAS)};
}}
"""

View file

@ -5,7 +5,6 @@ from __future__ import annotations
import enum import enum
import os import os
import platform
import re import re
import subprocess import subprocess
from dataclasses import dataclass from dataclasses import dataclass
@ -227,29 +226,27 @@ class ThemeManager:
checkbox_styles, checkbox_styles,
combobox_styles, combobox_styles,
general_styles, general_styles,
menu_styles,
scrollbar_styles, scrollbar_styles,
spinbox_styles, spinbox_styles,
table_styles, table_styles,
tabwidget_styles, tabwidget_styles,
win10_styles,
) )
buf += "".join( buf += "".join(
[ [
general_styles(self), general_styles(self),
button_styles(self), button_styles(self),
checkbox_styles(self),
menu_styles(self),
combobox_styles(self), combobox_styles(self),
tabwidget_styles(self), tabwidget_styles(self),
table_styles(self), table_styles(self),
spinbox_styles(self), spinbox_styles(self),
checkbox_styles(self),
scrollbar_styles(self), scrollbar_styles(self),
] ]
) )
if is_win and platform.release() == "10":
buf += win10_styles(self)
# allow addons to modify the styling # allow addons to modify the styling
buf = gui_hooks.style_did_init(buf) buf = gui_hooks.style_did_init(buf)

View file

@ -115,12 +115,12 @@ def register_repos():
################ ################
core_i18n_repo = "anki-core-i18n" core_i18n_repo = "anki-core-i18n"
core_i18n_commit = "71bfeda9ef7667f3454e7341969835168768bd54" core_i18n_commit = "b9d5c896f22fe6e79810194f41222d8638c13e16"
core_i18n_zip_csum = "4b4203d862aa1d90c0c391fac00bdf8ba0a8bffe00e4ba5fd36b2c7b288613c9" core_i18n_zip_csum = "8ef7888373cacf682c17f41056dc1f5348f60a15e1809c8db0f66f4072e7d5fb"
qtftl_i18n_repo = "anki-desktop-ftl" qtftl_i18n_repo = "anki-desktop-ftl"
qtftl_i18n_commit = "aa0e656fa4b0b9c926fc7436cb62418d8995e666" qtftl_i18n_commit = "a8bd0e284e2785421180af2ce10dd1d534b0033d"
qtftl_i18n_zip_csum = "5949b0e19b92f8699e9f1a3ca17abf9249103232aa408b4bb21bb6c5d1ff28bc" qtftl_i18n_zip_csum = "f88398324a64be99521bd5cd7e79e7dda64c31a2cd4e568328a211c7765b23ac"
i18n_build_content = """ i18n_build_content = """
filegroup( filegroup(

View file

@ -31,6 +31,7 @@ cargo_build_script(
], ],
deps = [ deps = [
"//rslib/cargo:prost_build", "//rslib/cargo:prost_build",
"//rslib/cargo:which",
], ],
) )

View file

@ -23,6 +23,7 @@ required-features = ["bench"]
[build-dependencies] [build-dependencies]
prost-build = "0.11.1" prost-build = "0.11.1"
which = "4.3.0"
[dev-dependencies] [dev-dependencies]
env_logger = "0.9.1" env_logger = "0.9.1"

View file

@ -1,7 +1,11 @@
// 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 std::{env, fmt::Write, path::PathBuf}; use std::{
env,
fmt::Write,
path::{Path, PathBuf},
};
struct CustomGenerator {} struct CustomGenerator {}
@ -71,6 +75,7 @@ fn service_generator() -> Box<dyn prost_build::ServiceGenerator> {
} }
pub fn write_backend_proto_rs() { pub fn write_backend_proto_rs() {
maybe_add_protobuf_to_path();
let proto_dir = if let Ok(proto) = env::var("PROTO_TOP") { let proto_dir = if let Ok(proto) = env::var("PROTO_TOP") {
PathBuf::from(proto).parent().unwrap().to_owned() PathBuf::from(proto).parent().unwrap().to_owned()
} else { } else {
@ -122,3 +127,27 @@ pub fn write_backend_proto_rs() {
.compile_protos(paths.as_slice(), &[proto_dir]) .compile_protos(paths.as_slice(), &[proto_dir])
.unwrap(); .unwrap();
} }
/// If PROTOC is not defined, and protoc is not on path, use the protoc
/// fetched by Bazel so that Rust Analyzer does not fail.
fn maybe_add_protobuf_to_path() {
if std::env::var("PROTOC").is_ok() {
return;
}
if which::which("protoc").is_ok() {
return;
}
let path = if cfg!(target_os = "windows") {
let base = std::fs::read_link("../.bazel/out").unwrap();
base.join("../external/protoc_bin_windows/bin/protoc.exe")
} else {
let base = Path::new("../.bazel/out/../external");
let subpath = if cfg!(target_os = "macos") {
"protoc_bin_macos/bin/protoc"
} else {
"protoc_bin_linux_x86_64/bin/protoc"
};
base.join(subpath)
};
std::env::set_var("PROTOC", path.to_str().unwrap());
}

View file

@ -579,6 +579,15 @@ alias(
], ],
) )
alias(
name = "which",
actual = "@raze__which__4_3_0//:which",
tags = [
"cargo-raze",
"manual",
],
)
alias( alias(
name = "zip", name = "zip",
actual = "@raze__zip__0_6_2//:zip", actual = "@raze__zip__0_6_2//:zip",

View file

@ -579,6 +579,15 @@ alias(
], ],
) )
alias(
name = "which",
actual = "@raze__which__4_3_0//:which",
tags = [
"cargo-raze",
"manual",
],
)
alias( alias(
name = "zip", name = "zip",
actual = "@raze__zip__0_6_2//:zip", actual = "@raze__zip__0_6_2//:zip",

View file

@ -579,6 +579,15 @@ alias(
], ],
) )
alias(
name = "which",
actual = "@raze__which__4_3_0//:which",
tags = [
"cargo-raze",
"manual",
],
)
alias( alias(
name = "zip", name = "zip",
actual = "@raze__zip__0_6_2//:zip", actual = "@raze__zip__0_6_2//:zip",

View file

@ -579,6 +579,15 @@ alias(
], ],
) )
alias(
name = "which",
actual = "@raze__which__4_3_0//:which",
tags = [
"cargo-raze",
"manual",
],
)
alias( alias(
name = "zip", name = "zip",
actual = "@raze__zip__0_6_2//:zip", actual = "@raze__zip__0_6_2//:zip",

View file

@ -89,11 +89,14 @@ $vars: (
), ),
), ),
overlay: ( overlay: (
"Background of floating elements (menus, tooltips)", <<<<<<< HEAD "Background of floating elements (menus, tooltips)",
( (
light: white, light: white,
dark: black, dark: black,
), ),
======= light: palette(lightgray, 0),
dark: palette(darkgray, 5),
>>>>>>> main,
), ),
code: ( code: (
"Background of code editors", "Background of code editors",
@ -105,7 +108,8 @@ $vars: (
), ),
border: ( border: (
default: ( default: (
"Border color with medium contrast against window background", <<<<<<< HEAD
"Border color with medium contrast against window background",
( (
light: palette(lightgray, 6), light: palette(lightgray, 6),
dark: palette(darkgray, 7), dark: palette(darkgray, 7),
@ -117,6 +121,9 @@ $vars: (
light: palette(lightgray, 5), light: palette(lightgray, 5),
dark: palette(darkgray, 6), dark: palette(darkgray, 6),
), ),
======= light: palette(lightgray, 6),
dark: palette(darkgray, 4),
>>>>>>> main,
), ),
strong: ( strong: (
"Border color with high contrast against window background", "Border color with high contrast against window background",
@ -125,6 +132,14 @@ $vars: (
dark: palette(darkgray, 1), dark: palette(darkgray, 1),
), ),
), ),
subtle: (
light: palette(lightgray, 5),
dark: palette(darkgray, 7),
),
faint: (
light: palette(lightgray, 4),
dark: palette(darkgray, 6),
),
focus: ( focus: (
"Border color of focused input elements", "Border color of focused input elements",
( (
@ -402,8 +417,8 @@ $vars: (
bg: ( bg: (
"Background color of highlighted items", "Background color of highlighted items",
( (
light: palette(blue, 3), light: color.scale(palette(blue, 3), $alpha: -33%),
dark: palette(blue, 4), dark: color.scale(palette(blue, 4), $alpha: -33%),
), ),
), ),
fg: ( fg: (

View file

@ -3,6 +3,9 @@ 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
--> -->
<script lang="ts"> <script lang="ts">
import { createEventDispatcher } from "svelte";
import { fly } from "svelte/transition";
import { on } from "../lib/events"; import { on } from "../lib/events";
import { Callback, singleCallback } from "../lib/typing"; import { Callback, singleCallback } from "../lib/typing";
import IconConstrain from "./IconConstrain.svelte"; import IconConstrain from "./IconConstrain.svelte";
@ -12,8 +15,13 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
export let panes: ResizablePane[]; export let panes: ResizablePane[];
export let index = 0; export let index = 0;
export let tip = ""; export let tip = "";
export let showIndicator = false;
export let clientHeight: number; export let clientHeight: number;
const rtl = window.getComputedStyle(document.body).direction == "rtl";
const dispatch = createEventDispatcher();
let destroy: Callback; let destroy: Callback;
let before: ResizablePane; let before: ResizablePane;
@ -77,18 +85,31 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
destroy = singleCallback( destroy = singleCallback(
on(window, "pointermove", onMove), on(window, "pointermove", onMove),
on(window, "pointerup", releasePointer), on(window, "pointerup", () => {
releasePointer.call(window);
dispatch("release");
}),
); );
} }
</script> </script>
<div <div
class="horizontal-resizer" class="horizontal-resizer"
class:rtl
title={tip} title={tip}
bind:clientHeight={resizerHeight} bind:clientHeight={resizerHeight}
on:pointerdown|preventDefault={lockPointer} on:pointerdown|preventDefault={lockPointer}
on:dblclick on:dblclick|preventDefault
> >
{#if showIndicator}
<div
class="resize-indicator"
transition:fly={{ x: rtl ? 25 : -25, duration: 200 }}
>
<slot />
</div>
{/if}
<div class="drag-handle"> <div class="drag-handle">
<IconConstrain iconSize={80}>{@html horizontalHandle}</IconConstrain> <IconConstrain iconSize={80}>{@html horizontalHandle}</IconConstrain>
</div> </div>
@ -99,7 +120,7 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
width: 100%; width: 100%;
cursor: row-resize; cursor: row-resize;
position: relative; position: relative;
height: 10px; height: 25px;
border-top: 1px solid var(--border); border-top: 1px solid var(--border);
z-index: 20; z-index: 20;
@ -113,5 +134,15 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
&:hover .drag-handle { &:hover .drag-handle {
opacity: 0.8; opacity: 0.8;
} }
.resize-indicator {
position: absolute;
font-size: small;
bottom: 0;
}
&.rtl .resize-indicator {
padding: 0.5rem 0 0 0.5rem;
right: 0;
}
} }
</style> </style>

View file

@ -59,5 +59,6 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
.pane { .pane {
@include panes.resizable(column, true, true); @include panes.resizable(column, true, true);
opacity: var(--opacity, 1);
} }
</style> </style>

View file

@ -6,7 +6,7 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
import IconConstrain from "./IconConstrain.svelte"; import IconConstrain from "./IconConstrain.svelte";
import { chevronLeft, chevronRight } from "./icons"; import { chevronLeft, chevronRight } from "./icons";
export let value = 1; export let value: number;
export let step = 1; export let step = 1;
export let min = 1; export let min = 1;
export let max = 9999; export let max = 9999;
@ -22,7 +22,7 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
} }
let stringValue: string; let stringValue: string;
$: stringValue = value.toFixed(decimalPlaces(step)); $: if (value) stringValue = value.toFixed(decimalPlaces(step));
function update(this: HTMLInputElement): void { function update(this: HTMLInputElement): void {
value = Math.min(max, Math.max(min, parseFloat(this.value))); value = Math.min(max, Math.max(min, parseFloat(this.value)));

View file

@ -355,12 +355,28 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
$: tagAmount = $tags.length; $: tagAmount = $tags.length;
let snapTags = $tagsCollapsed;
function collapseTags(): void { function collapseTags(): void {
lowerResizer.move([tagsPane, fieldsPane], tagsPane.minHeight); lowerResizer.move([tagsPane, fieldsPane], tagsPane.minHeight);
$tagsCollapsed = snapTags = true;
} }
function expandTags(): void { function expandTags(): void {
lowerResizer.move([tagsPane, fieldsPane], tagsPane.maxHeight); lowerResizer.move([tagsPane, fieldsPane], tagsPane.maxHeight);
$tagsCollapsed = snapTags = false;
}
window.addEventListener("resize", () => snapResizer(snapTags));
function snapResizer(collapse: boolean): void {
if (collapse) {
collapseTags();
bridgeCommand("collapseTags");
} else {
expandTags();
bridgeCommand("expandTags");
}
} }
</script> </script>
@ -390,7 +406,9 @@ the AddCards dialog) should be implemented in the user of this component.
<Pane <Pane
bind:this={fieldsPane.resizable} bind:this={fieldsPane.resizable}
on:resize={(e) => (fieldsPane.height = e.detail.height)} on:resize={(e) => {
fieldsPane.height = e.detail.height;
}}
> >
<PaneContent> <PaneContent>
<Fields> <Fields>
@ -553,7 +571,17 @@ the AddCards dialog) should be implemented in the user of this component.
</PaneContent> </PaneContent>
</Pane> </Pane>
{#if $tagsCollapsed} <HorizontalResizer
panes={[fieldsPane, tagsPane]}
showIndicator={$tagsCollapsed || snapTags}
tip={`Double click to ${$tagsCollapsed ? "expand" : "collapse"} tag editor`}
{clientHeight}
bind:this={lowerResizer}
on:dblclick={() => snapResizer(!$tagsCollapsed)}
on:release={() => {
snapResizer(snapTags);
}}
>
<div class="tags-expander"> <div class="tags-expander">
<TagAddButton <TagAddButton
on:tagappend={() => { on:tagappend={() => {
@ -564,36 +592,28 @@ the AddCards dialog) should be implemented in the user of this component.
{@html tagAmount > 0 ? `${tagAmount} Tags` : ""} {@html tagAmount > 0 ? `${tagAmount} Tags` : ""}
</TagAddButton> </TagAddButton>
</div> </div>
{/if} </HorizontalResizer>
<HorizontalResizer
panes={[fieldsPane, tagsPane]}
tip={`Double click to ${$tagsCollapsed ? "expand" : "collapse"} tag editor`}
{clientHeight}
bind:this={lowerResizer}
on:dblclick={() => {
if ($tagsCollapsed) {
expandTags();
bridgeCommand("expandTags");
$tagsCollapsed = false;
} else {
collapseTags();
bridgeCommand("collapseTags");
$tagsCollapsed = true;
}
}}
/>
<Pane <Pane
bind:this={tagsPane.resizable} bind:this={tagsPane.resizable}
on:resize={(e) => { on:resize={(e) => {
tagsPane.height = e.detail.height; tagsPane.height = e.detail.height;
$tagsCollapsed = tagsPane.height == 0; if (tagsPane.maxHeight > 0) {
snapTags = tagsPane.height < tagsPane.maxHeight / 2;
}
}} }}
--opacity={(() => {
if (!$tagsCollapsed) {
return 1;
} else {
return snapTags ? tagsPane.height / tagsPane.maxHeight : 1;
}
})()}
> >
<PaneContent scroll={false}> <PaneContent scroll={false}>
<TagEditor <TagEditor
{tags} {tags}
--button-opacity={snapTags ? 0 : 1}
bind:this={tagEditor} bind:this={tagEditor}
on:tagsupdate={saveTags} on:tagsupdate={saveTags}
on:tagsFocused={() => { on:tagsFocused={() => {
@ -617,7 +637,4 @@ the AddCards dialog) should be implemented in the user of this component.
flex-direction: column; flex-direction: column;
height: 100%; height: 100%;
} }
.tags-expander {
margin-top: 0.5rem;
}
</style> </style>

View file

@ -9,7 +9,6 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
import Shortcut from "../../components/Shortcut.svelte"; import Shortcut from "../../components/Shortcut.svelte";
import WithFloating from "../../components/WithFloating.svelte"; import WithFloating from "../../components/WithFloating.svelte";
import { mathjaxConfig } from "../../editable/mathjax-element"; import { mathjaxConfig } from "../../editable/mathjax-element";
import { bridgeCommand } from "../../lib/bridgecommand";
import * as tr from "../../lib/ftl"; import * as tr from "../../lib/ftl";
import { getPlatformString } from "../../lib/shortcuts"; import { getPlatformString } from "../../lib/shortcuts";
import { wrapInternal } from "../../lib/wrap"; import { wrapInternal } from "../../lib/wrap";
@ -27,15 +26,27 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
} }
function onMathjaxInline(): void { function onMathjaxInline(): void {
surround("<anki-mathjax focusonmount>", "</anki-mathjax>"); if (mathjaxConfig.enabled) {
surround("<anki-mathjax focusonmount>", "</anki-mathjax>");
} else {
surround("\\(", "\\)");
}
} }
function onMathjaxBlock(): void { function onMathjaxBlock(): void {
surround('<anki-mathjax block="true" focusonmount>', "</anki-matjax>"); if (mathjaxConfig.enabled) {
surround('<anki-mathjax block="true" focusonmount>', "</anki-matjax>");
} else {
surround("\\[", "\\]");
}
} }
function onMathjaxChemistry(): void { function onMathjaxChemistry(): void {
surround('<anki-mathjax focusonmount="0,4">\\ce{', "}</anki-mathjax>"); if (mathjaxConfig.enabled) {
surround('<anki-mathjax focusonmount="0,4">\\ce{', "}</anki-mathjax>");
} else {
surround("\\(\\ce{", "}\\)");
}
} }
function onLatex(): void { function onLatex(): void {
@ -50,11 +61,6 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
surround("[$$]", "[/$$]"); surround("[$$]", "[/$$]");
} }
function toggleShowMathjax(): void {
mathjaxConfig.enabled = !mathjaxConfig.enabled;
bridgeCommand("toggleMathjax");
}
type LatexItem = [() => void, string, string]; type LatexItem = [() => void, string, string];
const dropdownItems: LatexItem[] = [ const dropdownItems: LatexItem[] = [
@ -94,10 +100,6 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
> >
</DropdownItem> </DropdownItem>
{/each} {/each}
<DropdownItem on:click={toggleShowMathjax}>
<span>{tr.editingToggleMathjaxRendering()}</span>
</DropdownItem>
</Popover> </Popover>
</WithFloating> </WithFloating>

View file

@ -8,6 +8,7 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
import IconButton from "../../components/IconButton.svelte"; import IconButton from "../../components/IconButton.svelte";
import Popover from "../../components/Popover.svelte"; import Popover from "../../components/Popover.svelte";
import WithFloating from "../../components/WithFloating.svelte"; import WithFloating from "../../components/WithFloating.svelte";
import { mathjaxConfig } from "../../editable/mathjax-element";
import { bridgeCommand } from "../../lib/bridgecommand"; import { bridgeCommand } from "../../lib/bridgecommand";
import * as tr from "../../lib/ftl"; import * as tr from "../../lib/ftl";
import { shrinkImagesByDefault } from "../image-overlay/ImageOverlay.svelte"; import { shrinkImagesByDefault } from "../image-overlay/ImageOverlay.svelte";
@ -22,6 +23,11 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
showFloating = false; showFloating = false;
} }
function toggleShowMathjax(_evt: MouseEvent): void {
mathjaxConfig.enabled = !mathjaxConfig.enabled;
bridgeCommand("toggleMathjax");
}
function toggleCloseHTMLTags(_evt: MouseEvent): void { function toggleCloseHTMLTags(_evt: MouseEvent): void {
$closeHTMLTags = !$closeHTMLTags; $closeHTMLTags = !$closeHTMLTags;
bridgeCommand("toggleCloseHTMLTags"); bridgeCommand("toggleCloseHTMLTags");
@ -50,6 +56,10 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
<CheckBox value={$shrinkImagesByDefault} /> <CheckBox value={$shrinkImagesByDefault} />
<span class="d-flex-inline ps-3">{tr.editingShrinkImages()}</span> <span class="d-flex-inline ps-3">{tr.editingShrinkImages()}</span>
</DropdownItem> </DropdownItem>
<DropdownItem on:click={toggleShowMathjax}>
<CheckBox value={mathjaxConfig.enabled} />
<span class="d-flex-inline ps-3">{tr.editingMathjaxPreview()}</span>
</DropdownItem>
<DropdownItem on:click={toggleCloseHTMLTags}> <DropdownItem on:click={toggleCloseHTMLTags}>
<CheckBox value={$closeHTMLTags} /> <CheckBox value={$closeHTMLTags} />
<span class="d-flex-inline ps-3">{tr.editingCloseHtmlTags()}</span> <span class="d-flex-inline ps-3">{tr.editingCloseHtmlTags()}</span>

View file

@ -99,7 +99,7 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
{configuration} {configuration}
bind:api={codeMirror} bind:api={codeMirror}
on:change={({ detail: mathjaxText }) => code.set(mathjaxText)} on:change={({ detail: mathjaxText }) => code.set(mathjaxText)}
on:tab on:blur
/> />
</div> </div>

View file

@ -65,14 +65,18 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
} }
} }
function clear(): void {
unsubscribe();
activeImage = null;
mathjaxElement = null;
}
async function resetHandle(): Promise<void> { async function resetHandle(): Promise<void> {
selectAll = false; selectAll = false;
position = undefined; position = undefined;
if (activeImage && mathjaxElement) { if (activeImage && mathjaxElement) {
unsubscribe(); clear();
activeImage = null;
mathjaxElement = null;
} }
allow(); allow();
@ -188,10 +192,7 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
placeHandle(true); placeHandle(true);
await resetHandle(); await resetHandle();
}} }}
on:tab={async () => { on:blur={async () => {
// Instead of resetting on blur, we reset on tab
// Otherwise, when clicking from Mathjax element to another,
// the user has to click twice (focus is called before blur?)
await resetHandle(); await resetHandle();
}} }}
let:editor={mathjaxEditor} let:editor={mathjaxEditor}
@ -220,8 +221,8 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
}} }}
on:delete={async () => { on:delete={async () => {
placeCaretAfter(activeImage); placeCaretAfter(activeImage);
activeImage.remove(); mathjaxElement?.remove();
await resetHandle(); clear();
}} }}
on:surround={async ({ detail }) => { on:surround={async ({ detail }) => {
const editor = await mathjaxEditor.editor; const editor = await mathjaxEditor.editor;

View file

@ -6,7 +6,7 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
export let selected = false; export let selected = false;
export let active = false; export let active = false;
let buttonRef: HTMLButtonElement; let buttonRef: HTMLElement;
$: if (selected && buttonRef) { $: if (selected && buttonRef) {
/* buttonRef.scrollIntoView({ behavior: "smooth", block: "start" }); */ /* buttonRef.scrollIntoView({ behavior: "smooth", block: "start" }); */
@ -18,7 +18,7 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
} }
</script> </script>
<button <div
bind:this={buttonRef} bind:this={buttonRef}
tabindex="-1" tabindex="-1"
class="autocomplete-item" class="autocomplete-item"
@ -30,20 +30,26 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
on:mouseleave on:mouseleave
> >
<slot /> <slot />
</button> </div>
<style lang="scss"> <style lang="scss">
@use "sass/button-mixins" as button; @use "sass/button-mixins" as button;
.autocomplete-item { .autocomplete-item {
@include button.base($with-disabled: false, $active-class: active); padding: 4px 8px;
padding: 1px 7px 2px;
text-align: start; text-align: start;
white-space: nowrap; white-space: nowrap;
flex-grow: 1; flex-grow: 1;
border-radius: 0; border-radius: 0;
border: 1px solid transparent;
&:not(:first-child) {
border-top-color: var(--border-subtle);
}
&:hover {
@include button.base($with-disabled: false, $active-class: active);
}
&.selected { &.selected {
@include button.base( @include button.base(
$primary: true, $primary: true,

View file

@ -490,6 +490,9 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
display: flex; display: flex;
flex-flow: row wrap; flex-flow: row wrap;
align-items: flex-end; align-items: flex-end;
background: var(--canvas-inset);
border-radius: var(--border-radius);
padding: 6px;
} }
.tag-relative { .tag-relative {

View file

@ -186,9 +186,9 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
flex-flow: column nowrap; flex-flow: column nowrap;
width: 80vw; width: 80vw;
max-height: 7rem; max-height: 30vh;
font-size: 11px; font-size: 13px;
overflow-x: hidden; overflow-x: hidden;
text-overflow: ellipsis; text-overflow: ellipsis;
overflow-y: auto; overflow-y: auto;

View file

@ -25,6 +25,7 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
<style lang="scss"> <style lang="scss">
.tag-options-button { .tag-options-button {
padding: 6px 3px 0; transition: opacity 0.2s linear;
opacity: var(--button-opacity, 1);
} }
</style> </style>