mirror of
https://github.com/ankitects/anki.git
synced 2025-09-25 01:06:35 -04:00
Merge branch 'main' into color-palette
This commit is contained in:
commit
e4f3f9bf41
21 changed files with 272 additions and 88 deletions
|
@ -53,6 +53,7 @@ editing-text-color = Text color
|
||||||
editing-text-highlight-color = Text highlight color
|
editing-text-highlight-color = Text highlight color
|
||||||
editing-to-make-a-cloze-deletion-on = To make a cloze deletion on an existing note, you need to change it to a cloze type first, via 'Notes>Change Note Type'
|
editing-to-make-a-cloze-deletion-on = To make a cloze deletion on an existing note, you need to change it to a cloze type first, via 'Notes>Change Note Type'
|
||||||
editing-toggle-html-editor = Toggle HTML Editor
|
editing-toggle-html-editor = Toggle HTML Editor
|
||||||
|
editing-toggle-visual-editor = Toggle Visual Editor
|
||||||
editing-toggle-sticky = Toggle sticky
|
editing-toggle-sticky = Toggle sticky
|
||||||
editing-expand-field = Expand field
|
editing-expand-field = Expand field
|
||||||
editing-collapse-field = Collapse field
|
editing-collapse-field = Collapse field
|
||||||
|
|
|
@ -10,6 +10,7 @@ fields-font = Font:
|
||||||
fields-new-position-1 = New position (1...{ $val }):
|
fields-new-position-1 = New position (1...{ $val }):
|
||||||
fields-notes-require-at-least-one-field = Notes require at least one field.
|
fields-notes-require-at-least-one-field = Notes require at least one field.
|
||||||
fields-reverse-text-direction-rtl = Reverse text direction (RTL)
|
fields-reverse-text-direction-rtl = Reverse text direction (RTL)
|
||||||
|
fields-collapse-by-default = Collapse by default
|
||||||
fields-html-by-default = Use HTML editor by default
|
fields-html-by-default = Use HTML editor by default
|
||||||
fields-size = Size:
|
fields-size = Size:
|
||||||
fields-sort-by-this-field-in-the = Sort by this field in the browser
|
fields-sort-by-this-field-in-the = Sort by this field in the browser
|
||||||
|
|
|
@ -73,6 +73,7 @@ message Notetype {
|
||||||
uint32 font_size = 4;
|
uint32 font_size = 4;
|
||||||
string description = 5;
|
string description = 5;
|
||||||
bool plain_text = 6;
|
bool plain_text = 6;
|
||||||
|
bool collapsed = 7;
|
||||||
|
|
||||||
bytes other = 255;
|
bytes other = 255;
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,7 +17,18 @@ from anki.utils import call, is_mac, namedtmp, tmpdir
|
||||||
|
|
||||||
pngCommands = [
|
pngCommands = [
|
||||||
["latex", "-interaction=nonstopmode", "tmp.tex"],
|
["latex", "-interaction=nonstopmode", "tmp.tex"],
|
||||||
["dvipng", "-D", "200", "-T", "tight", "tmp.dvi", "-o", "tmp.png"],
|
[
|
||||||
|
"dvipng",
|
||||||
|
"-bg",
|
||||||
|
"Transparent",
|
||||||
|
"-D",
|
||||||
|
"200",
|
||||||
|
"-T",
|
||||||
|
"tight",
|
||||||
|
"tmp.dvi",
|
||||||
|
"-o",
|
||||||
|
"tmp.png",
|
||||||
|
],
|
||||||
]
|
]
|
||||||
|
|
||||||
svgCommands = [
|
svgCommands = [
|
||||||
|
|
|
@ -66,6 +66,7 @@ except AttributeError:
|
||||||
|
|
||||||
appVersion = _version
|
appVersion = _version
|
||||||
appWebsite = "https://apps.ankiweb.net/"
|
appWebsite = "https://apps.ankiweb.net/"
|
||||||
|
appWebsiteDownloadSection = "https://apps.ankiweb.net/#download"
|
||||||
appDonate = "https://apps.ankiweb.net/support/"
|
appDonate = "https://apps.ankiweb.net/support/"
|
||||||
appShared = "https://ankiweb.net/shared/"
|
appShared = "https://ankiweb.net/shared/"
|
||||||
appUpdate = "https://ankiweb.net/update/desktop"
|
appUpdate = "https://ankiweb.net/update/desktop"
|
||||||
|
|
|
@ -21,6 +21,7 @@ from aqt.qt import *
|
||||||
from aqt.sound import av_player
|
from aqt.sound import av_player
|
||||||
from aqt.utils import (
|
from aqt.utils import (
|
||||||
HelpPage,
|
HelpPage,
|
||||||
|
add_close_shortcut,
|
||||||
askUser,
|
askUser,
|
||||||
downArrow,
|
downArrow,
|
||||||
openHelp,
|
openHelp,
|
||||||
|
@ -48,6 +49,7 @@ class AddCards(QMainWindow):
|
||||||
self.setup_choosers()
|
self.setup_choosers()
|
||||||
self.setupEditor()
|
self.setupEditor()
|
||||||
self.setupButtons()
|
self.setupButtons()
|
||||||
|
add_close_shortcut(self)
|
||||||
self._load_new_note()
|
self._load_new_note()
|
||||||
self.history: list[NoteId] = []
|
self.history: list[NoteId] = []
|
||||||
self._last_added_note: Optional[Note] = None
|
self._last_added_note: Optional[Note] = None
|
||||||
|
|
|
@ -501,6 +501,7 @@ require("anki/ui").loaded.then(() => require("anki/NoteEditor").instances[0].too
|
||||||
]
|
]
|
||||||
|
|
||||||
flds = self.note.note_type()["flds"]
|
flds = self.note.note_type()["flds"]
|
||||||
|
collapsed = [fld["collapsed"] for fld in flds]
|
||||||
plain_texts = [fld.get("plainText", False) for fld in flds]
|
plain_texts = [fld.get("plainText", False) for fld in flds]
|
||||||
descriptions = [fld.get("description", "") for fld in flds]
|
descriptions = [fld.get("description", "") for fld in flds]
|
||||||
|
|
||||||
|
@ -524,6 +525,7 @@ require("anki/ui").loaded.then(() => require("anki/NoteEditor").instances[0].too
|
||||||
|
|
||||||
js = """
|
js = """
|
||||||
setFields({});
|
setFields({});
|
||||||
|
setCollapsed({});
|
||||||
setPlainTexts({});
|
setPlainTexts({});
|
||||||
setDescriptions({});
|
setDescriptions({});
|
||||||
setFonts({});
|
setFonts({});
|
||||||
|
@ -534,6 +536,7 @@ require("anki/ui").loaded.then(() => require("anki/NoteEditor").instances[0].too
|
||||||
setMathjaxEnabled({});
|
setMathjaxEnabled({});
|
||||||
""".format(
|
""".format(
|
||||||
json.dumps(data),
|
json.dumps(data),
|
||||||
|
json.dumps(collapsed),
|
||||||
json.dumps(plain_texts),
|
json.dumps(plain_texts),
|
||||||
json.dumps(descriptions),
|
json.dumps(descriptions),
|
||||||
json.dumps(self.fonts()),
|
json.dumps(self.fonts()),
|
||||||
|
|
|
@ -244,6 +244,7 @@ class FieldDialog(QDialog):
|
||||||
f.sortField.setChecked(self.model["sortf"] == fld["ord"])
|
f.sortField.setChecked(self.model["sortf"] == fld["ord"])
|
||||||
f.rtl.setChecked(fld["rtl"])
|
f.rtl.setChecked(fld["rtl"])
|
||||||
f.plainTextByDefault.setChecked(fld["plainText"])
|
f.plainTextByDefault.setChecked(fld["plainText"])
|
||||||
|
f.collapseByDefault.setChecked(fld["collapsed"])
|
||||||
f.fieldDescription.setText(fld.get("description", ""))
|
f.fieldDescription.setText(fld.get("description", ""))
|
||||||
|
|
||||||
def saveField(self) -> None:
|
def saveField(self) -> None:
|
||||||
|
@ -269,6 +270,9 @@ class FieldDialog(QDialog):
|
||||||
if fld["plainText"] != plain_text:
|
if fld["plainText"] != plain_text:
|
||||||
fld["plainText"] = plain_text
|
fld["plainText"] = plain_text
|
||||||
self.change_tracker.mark_basic()
|
self.change_tracker.mark_basic()
|
||||||
|
collapsed = f.collapseByDefault.isChecked()
|
||||||
|
if fld["collapsed"] != collapsed:
|
||||||
|
fld["collapsed"] = collapsed
|
||||||
desc = f.fieldDescription.text()
|
desc = f.fieldDescription.text()
|
||||||
if fld.get("description", "") != desc:
|
if fld.get("description", "") != desc:
|
||||||
fld["description"] = desc
|
fld["description"] = desc
|
||||||
|
|
|
@ -6,8 +6,8 @@
|
||||||
<rect>
|
<rect>
|
||||||
<x>0</x>
|
<x>0</x>
|
||||||
<y>0</y>
|
<y>0</y>
|
||||||
<width>598</width>
|
<width>567</width>
|
||||||
<height>378</height>
|
<height>438</height>
|
||||||
</rect>
|
</rect>
|
||||||
</property>
|
</property>
|
||||||
<property name="windowTitle">
|
<property name="windowTitle">
|
||||||
|
@ -84,44 +84,6 @@
|
||||||
</item>
|
</item>
|
||||||
<item>
|
<item>
|
||||||
<layout class="QGridLayout" name="_2">
|
<layout class="QGridLayout" name="_2">
|
||||||
<item row="3" column="1">
|
|
||||||
<widget class="QCheckBox" name="rtl">
|
|
||||||
<property name="text">
|
|
||||||
<string>fields_reverse_text_direction_rtl</string>
|
|
||||||
</property>
|
|
||||||
</widget>
|
|
||||||
</item>
|
|
||||||
<item row="1" column="1">
|
|
||||||
<widget class="QFontComboBox" name="fontFamily">
|
|
||||||
<property name="minimumSize">
|
|
||||||
<size>
|
|
||||||
<width>0</width>
|
|
||||||
<height>25</height>
|
|
||||||
</size>
|
|
||||||
</property>
|
|
||||||
</widget>
|
|
||||||
</item>
|
|
||||||
<item row="1" column="0">
|
|
||||||
<widget class="QLabel" name="label_font">
|
|
||||||
<property name="text">
|
|
||||||
<string>fields_editing_font</string>
|
|
||||||
</property>
|
|
||||||
</widget>
|
|
||||||
</item>
|
|
||||||
<item row="0" column="1" colspan="2">
|
|
||||||
<widget class="QLineEdit" name="fieldDescription">
|
|
||||||
<property name="placeholderText">
|
|
||||||
<string>fields_description_placeholder</string>
|
|
||||||
</property>
|
|
||||||
</widget>
|
|
||||||
</item>
|
|
||||||
<item row="2" column="0">
|
|
||||||
<widget class="QLabel" name="label_sort">
|
|
||||||
<property name="text">
|
|
||||||
<string>actions_options</string>
|
|
||||||
</property>
|
|
||||||
</widget>
|
|
||||||
</item>
|
|
||||||
<item row="0" column="0">
|
<item row="0" column="0">
|
||||||
<widget class="QLabel" name="label_description">
|
<widget class="QLabel" name="label_description">
|
||||||
<property name="sizePolicy">
|
<property name="sizePolicy">
|
||||||
|
@ -135,6 +97,13 @@
|
||||||
</property>
|
</property>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
|
<item row="1" column="0">
|
||||||
|
<widget class="QLabel" name="label_font">
|
||||||
|
<property name="text">
|
||||||
|
<string>fields_editing_font</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
<item row="1" column="2">
|
<item row="1" column="2">
|
||||||
<widget class="QSpinBox" name="fontSize">
|
<widget class="QSpinBox" name="fontSize">
|
||||||
<property name="minimum">
|
<property name="minimum">
|
||||||
|
@ -145,10 +114,27 @@
|
||||||
</property>
|
</property>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
<item row="2" column="1">
|
<item row="2" column="0">
|
||||||
<widget class="QRadioButton" name="sortField">
|
<widget class="QLabel" name="label_sort">
|
||||||
<property name="text">
|
<property name="text">
|
||||||
<string>fields_sort_by_this_field_in_the</string>
|
<string>actions_options</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item row="1" column="1">
|
||||||
|
<widget class="QFontComboBox" name="fontFamily">
|
||||||
|
<property name="minimumSize">
|
||||||
|
<size>
|
||||||
|
<width>0</width>
|
||||||
|
<height>25</height>
|
||||||
|
</size>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item row="3" column="1">
|
||||||
|
<widget class="QCheckBox" name="rtl">
|
||||||
|
<property name="text">
|
||||||
|
<string>fields_reverse_text_direction_rtl</string>
|
||||||
</property>
|
</property>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
|
@ -162,6 +148,30 @@
|
||||||
</property>
|
</property>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
|
<item row="0" column="1" colspan="2">
|
||||||
|
<widget class="QLineEdit" name="fieldDescription">
|
||||||
|
<property name="placeholderText">
|
||||||
|
<string>fields_description_placeholder</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item row="2" column="1">
|
||||||
|
<widget class="QRadioButton" name="sortField">
|
||||||
|
<property name="text">
|
||||||
|
<string>fields_sort_by_this_field_in_the</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item row="5" column="1">
|
||||||
|
<widget class="QCheckBox" name="collapseByDefault">
|
||||||
|
<property name="enabled">
|
||||||
|
<bool>true</bool>
|
||||||
|
</property>
|
||||||
|
<property name="text">
|
||||||
|
<string>fields_collapse_by_default</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
</layout>
|
</layout>
|
||||||
</item>
|
</item>
|
||||||
<item>
|
<item>
|
||||||
|
|
|
@ -70,7 +70,7 @@ def askAndUpdate(mw: aqt.AnkiQt, ver: str) -> None:
|
||||||
# ignore this update
|
# ignore this update
|
||||||
mw.pm.meta["suppressUpdate"] = ver
|
mw.pm.meta["suppressUpdate"] = ver
|
||||||
elif ret == QMessageBox.StandardButton.Yes:
|
elif ret == QMessageBox.StandardButton.Yes:
|
||||||
openLink(aqt.appWebsite)
|
openLink(aqt.appWebsiteDownloadSection)
|
||||||
|
|
||||||
|
|
||||||
def showMessages(mw: aqt.AnkiQt, data: dict) -> None:
|
def showMessages(mw: aqt.AnkiQt, data: dict) -> None:
|
||||||
|
|
|
@ -848,6 +848,13 @@ def addCloseShortcut(widg: QDialog) -> None:
|
||||||
setattr(widg, "_closeShortcut", shortcut)
|
setattr(widg, "_closeShortcut", shortcut)
|
||||||
|
|
||||||
|
|
||||||
|
def add_close_shortcut(widg: QWidget) -> None:
|
||||||
|
if not is_mac:
|
||||||
|
return
|
||||||
|
shortcut = QShortcut(QKeySequence("Ctrl+W"), widg)
|
||||||
|
qconnect(shortcut.activated, widg.close)
|
||||||
|
|
||||||
|
|
||||||
def downArrow() -> str:
|
def downArrow() -> str:
|
||||||
if is_win:
|
if is_win:
|
||||||
return "▼"
|
return "▼"
|
||||||
|
|
|
@ -46,6 +46,7 @@ impl NoteField {
|
||||||
font_name: "Arial".into(),
|
font_name: "Arial".into(),
|
||||||
font_size: 20,
|
font_size: 20,
|
||||||
description: "".into(),
|
description: "".into(),
|
||||||
|
collapsed: false,
|
||||||
other: vec![],
|
other: vec![],
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
|
@ -161,7 +161,7 @@ impl From<Notetype> for NotetypeSchema11 {
|
||||||
|
|
||||||
/// See [crate::deckconfig::schema11::clear_other_duplicates()].
|
/// See [crate::deckconfig::schema11::clear_other_duplicates()].
|
||||||
fn clear_other_field_duplicates(other: &mut HashMap<String, Value>) {
|
fn clear_other_field_duplicates(other: &mut HashMap<String, Value>) {
|
||||||
for key in &["description", "plainText"] {
|
for key in &["description", "plainText", "collapsed"] {
|
||||||
other.remove(*key);
|
other.remove(*key);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -215,6 +215,9 @@ pub struct NoteFieldSchema11 {
|
||||||
#[serde(default, deserialize_with = "default_on_invalid")]
|
#[serde(default, deserialize_with = "default_on_invalid")]
|
||||||
pub(crate) plain_text: bool,
|
pub(crate) plain_text: bool,
|
||||||
|
|
||||||
|
#[serde(default, deserialize_with = "default_on_invalid")]
|
||||||
|
pub(crate) collapsed: bool,
|
||||||
|
|
||||||
#[serde(flatten)]
|
#[serde(flatten)]
|
||||||
pub(crate) other: HashMap<String, Value>,
|
pub(crate) other: HashMap<String, Value>,
|
||||||
}
|
}
|
||||||
|
@ -230,6 +233,7 @@ impl Default for NoteFieldSchema11 {
|
||||||
font: "Arial".to_string(),
|
font: "Arial".to_string(),
|
||||||
size: 20,
|
size: 20,
|
||||||
description: String::new(),
|
description: String::new(),
|
||||||
|
collapsed: false,
|
||||||
other: Default::default(),
|
other: Default::default(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -247,6 +251,7 @@ impl From<NoteFieldSchema11> for NoteField {
|
||||||
font_name: f.font,
|
font_name: f.font,
|
||||||
font_size: f.size as u32,
|
font_size: f.size as u32,
|
||||||
description: f.description,
|
description: f.description,
|
||||||
|
collapsed: f.collapsed,
|
||||||
other: other_to_bytes(&f.other),
|
other: other_to_bytes(&f.other),
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
@ -269,6 +274,7 @@ impl From<NoteField> for NoteFieldSchema11 {
|
||||||
font: conf.font_name,
|
font: conf.font_name,
|
||||||
size: conf.font_size as u16,
|
size: conf.font_size as u16,
|
||||||
description: conf.description,
|
description: conf.description,
|
||||||
|
collapsed: conf.collapsed,
|
||||||
other,
|
other,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,12 +10,12 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
||||||
export { className as class };
|
export { className as class };
|
||||||
|
|
||||||
export let collapsed = false;
|
export let collapsed = false;
|
||||||
|
let isCollapsed = false;
|
||||||
|
let hidden = collapsed;
|
||||||
|
|
||||||
const [outerPromise, outerResolve] = promiseWithResolver<HTMLElement>();
|
const [outerPromise, outerResolve] = promiseWithResolver<HTMLElement>();
|
||||||
const [innerPromise, innerResolve] = promiseWithResolver<HTMLElement>();
|
const [innerPromise, innerResolve] = promiseWithResolver<HTMLElement>();
|
||||||
|
|
||||||
let isCollapsed = false;
|
|
||||||
|
|
||||||
let style: string;
|
let style: string;
|
||||||
function setStyle(height: number, duration: number) {
|
function setStyle(height: number, duration: number) {
|
||||||
style = `--collapse-height: -${height}px; --duration: ${duration}ms`;
|
style = `--collapse-height: -${height}px; --duration: ${duration}ms`;
|
||||||
|
@ -60,18 +60,18 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
||||||
() => {
|
() => {
|
||||||
inner.toggleAttribute("hidden", collapse);
|
inner.toggleAttribute("hidden", collapse);
|
||||||
outer.style.removeProperty("overflow");
|
outer.style.removeProperty("overflow");
|
||||||
|
hidden = collapse;
|
||||||
},
|
},
|
||||||
{ once: true },
|
{ once: true },
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* prevent transition on mount for performance reasons */
|
/* prevent transition on mount for performance reasons */
|
||||||
let blockTransition = true;
|
let firstTransition = true;
|
||||||
|
|
||||||
$: if (blockTransition) {
|
$: {
|
||||||
blockTransition = false;
|
|
||||||
} else {
|
|
||||||
transition(collapsed);
|
transition(collapsed);
|
||||||
|
firstTransition = false;
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
@ -79,10 +79,11 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
||||||
<div
|
<div
|
||||||
class="collapsible-inner"
|
class="collapsible-inner"
|
||||||
class:collapsed={isCollapsed}
|
class:collapsed={isCollapsed}
|
||||||
|
class:no-transition={firstTransition}
|
||||||
use:innerResolve
|
use:innerResolve
|
||||||
{style}
|
{style}
|
||||||
>
|
>
|
||||||
<slot />
|
<slot {hidden} />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
@ -96,5 +97,8 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
||||||
&.collapsed {
|
&.collapsed {
|
||||||
margin-top: var(--collapse-height);
|
margin-top: var(--collapse-height);
|
||||||
}
|
}
|
||||||
|
&.no-transition {
|
||||||
|
transition: none;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
|
@ -14,6 +14,7 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
||||||
direction: "ltr" | "rtl";
|
direction: "ltr" | "rtl";
|
||||||
plainText: boolean;
|
plainText: boolean;
|
||||||
description: string;
|
description: string;
|
||||||
|
collapsed: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface EditorFieldAPI {
|
export interface EditorFieldAPI {
|
||||||
|
@ -54,6 +55,7 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
||||||
export let content: Writable<string>;
|
export let content: Writable<string>;
|
||||||
export let field: FieldData;
|
export let field: FieldData;
|
||||||
export let collapsed = false;
|
export let collapsed = false;
|
||||||
|
export let flipInputs = false;
|
||||||
|
|
||||||
const directionStore = writable<"ltr" | "rtl">();
|
const directionStore = writable<"ltr" | "rtl">();
|
||||||
setContext(directionKey, directionStore);
|
setContext(directionKey, directionStore);
|
||||||
|
@ -101,7 +103,13 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
||||||
fontSize={field.fontSize}
|
fontSize={field.fontSize}
|
||||||
api={editingArea}
|
api={editingArea}
|
||||||
>
|
>
|
||||||
<slot name="editing-inputs" />
|
{#if flipInputs}
|
||||||
|
<slot name="plain-text-input" />
|
||||||
|
<slot name="rich-text-input" />
|
||||||
|
{:else}
|
||||||
|
<slot name="rich-text-input" />
|
||||||
|
<slot name="plain-text-input" />
|
||||||
|
{/if}
|
||||||
</EditingArea>
|
</EditingArea>
|
||||||
</Collapsible>
|
</Collapsible>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -40,9 +40,6 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
||||||
opacity: 0.4;
|
opacity: 0.4;
|
||||||
pointer-events: none;
|
pointer-events: none;
|
||||||
|
|
||||||
/* same as in ContentEditable */
|
|
||||||
padding: 6px;
|
|
||||||
|
|
||||||
/* stay a on single line */
|
/* stay a on single line */
|
||||||
overflow-x: hidden;
|
overflow-x: hidden;
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
|
|
|
@ -66,6 +66,7 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
||||||
import PlainTextInput from "./plain-text-input";
|
import PlainTextInput from "./plain-text-input";
|
||||||
import PlainTextBadge from "./PlainTextBadge.svelte";
|
import PlainTextBadge from "./PlainTextBadge.svelte";
|
||||||
import RichTextInput, { editingInputIsRichText } from "./rich-text-input";
|
import RichTextInput, { editingInputIsRichText } from "./rich-text-input";
|
||||||
|
import RichTextBadge from "./RichTextBadge.svelte";
|
||||||
|
|
||||||
function quoteFontFamily(fontFamily: string): string {
|
function quoteFontFamily(fontFamily: string): string {
|
||||||
// generic families (e.g. sans-serif) must not be quoted
|
// generic families (e.g. sans-serif) must not be quoted
|
||||||
|
@ -113,13 +114,19 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
||||||
fieldNames = newFieldNames;
|
fieldNames = newFieldNames;
|
||||||
}
|
}
|
||||||
|
|
||||||
let plainTexts: boolean[] = [];
|
let fieldsCollapsed: boolean[] = [];
|
||||||
|
export function setCollapsed(fs: boolean[]): void {
|
||||||
|
fieldsCollapsed = fs;
|
||||||
|
}
|
||||||
|
|
||||||
let richTextsHidden: boolean[] = [];
|
let richTextsHidden: boolean[] = [];
|
||||||
let plainTextsHidden: boolean[] = [];
|
let plainTextsHidden: boolean[] = [];
|
||||||
|
let plainTextDefaults: boolean[] = [];
|
||||||
|
|
||||||
export function setPlainTexts(fs: boolean[]): void {
|
export function setPlainTexts(fs: boolean[]): void {
|
||||||
richTextsHidden = plainTexts = fs;
|
richTextsHidden = fs;
|
||||||
plainTextsHidden = Array.from(fs, (v) => !v);
|
plainTextsHidden = Array.from(fs, (v) => !v);
|
||||||
|
plainTextDefaults = [...richTextsHidden];
|
||||||
}
|
}
|
||||||
|
|
||||||
function setMathjaxEnabled(enabled: boolean): void {
|
function setMathjaxEnabled(enabled: boolean): void {
|
||||||
|
@ -133,13 +140,10 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
||||||
|
|
||||||
let fonts: [string, number, boolean][] = [];
|
let fonts: [string, number, boolean][] = [];
|
||||||
|
|
||||||
let fieldsCollapsed: boolean[] = [];
|
|
||||||
|
|
||||||
const fields = clearableArray<EditorFieldAPI>();
|
const fields = clearableArray<EditorFieldAPI>();
|
||||||
|
|
||||||
export function setFonts(fs: [string, number, boolean][]): void {
|
export function setFonts(fs: [string, number, boolean][]): void {
|
||||||
fonts = fs;
|
fonts = fs;
|
||||||
fieldsCollapsed = fonts.map((_, index) => fieldsCollapsed[index] ?? false);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function focusField(index: number | null): void {
|
export function focusField(index: number | null): void {
|
||||||
|
@ -187,11 +191,12 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
||||||
|
|
||||||
$: fieldsData = fieldNames.map((name, index) => ({
|
$: fieldsData = fieldNames.map((name, index) => ({
|
||||||
name,
|
name,
|
||||||
plainText: plainTexts[index],
|
plainText: plainTextDefaults[index],
|
||||||
description: fieldDescriptions[index],
|
description: fieldDescriptions[index],
|
||||||
fontFamily: quoteFontFamily(fonts[index][0]),
|
fontFamily: quoteFontFamily(fonts[index][0]),
|
||||||
fontSize: fonts[index][1],
|
fontSize: fonts[index][1],
|
||||||
direction: fonts[index][2] ? "rtl" : "ltr",
|
direction: fonts[index][2] ? "rtl" : "ltr",
|
||||||
|
collapsed: fieldsCollapsed[index],
|
||||||
})) as FieldData[];
|
})) as FieldData[];
|
||||||
|
|
||||||
function saveTags({ detail }: CustomEvent): void {
|
function saveTags({ detail }: CustomEvent): void {
|
||||||
|
@ -242,6 +247,7 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
||||||
|
|
||||||
import { mathjaxConfig } from "../editable/mathjax-element";
|
import { mathjaxConfig } from "../editable/mathjax-element";
|
||||||
import { wrapInternal } from "../lib/wrap";
|
import { wrapInternal } from "../lib/wrap";
|
||||||
|
import { refocusInput } from "./helpers";
|
||||||
import * as oldEditorAdapter from "./old-editor-adapter";
|
import * as oldEditorAdapter from "./old-editor-adapter";
|
||||||
|
|
||||||
onMount(() => {
|
onMount(() => {
|
||||||
|
@ -257,6 +263,7 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
||||||
|
|
||||||
Object.assign(globalThis, {
|
Object.assign(globalThis, {
|
||||||
setFields,
|
setFields,
|
||||||
|
setCollapsed,
|
||||||
setPlainTexts,
|
setPlainTexts,
|
||||||
setDescriptions,
|
setDescriptions,
|
||||||
setFonts,
|
setFonts,
|
||||||
|
@ -330,6 +337,7 @@ the AddCards dialog) should be implemented in the user of this component.
|
||||||
<EditorField
|
<EditorField
|
||||||
{field}
|
{field}
|
||||||
{content}
|
{content}
|
||||||
|
flipInputs={plainTextDefaults[index]}
|
||||||
api={fields[index]}
|
api={fields[index]}
|
||||||
on:focusin={() => {
|
on:focusin={() => {
|
||||||
$focusedField = fields[index];
|
$focusedField = fields[index];
|
||||||
|
@ -358,11 +366,16 @@ the AddCards dialog) should be implemented in the user of this component.
|
||||||
on:toggle={async () => {
|
on:toggle={async () => {
|
||||||
fieldsCollapsed[index] = !fieldsCollapsed[index];
|
fieldsCollapsed[index] = !fieldsCollapsed[index];
|
||||||
|
|
||||||
|
const defaultInput = !plainTextDefaults[index]
|
||||||
|
? richTextInputs[index]
|
||||||
|
: plainTextInputs[index];
|
||||||
|
|
||||||
if (!fieldsCollapsed[index]) {
|
if (!fieldsCollapsed[index]) {
|
||||||
await tick();
|
refocusInput(defaultInput.api);
|
||||||
richTextInputs[index].api.refocus();
|
} else if (!plainTextDefaults[index]) {
|
||||||
} else {
|
|
||||||
plainTextsHidden[index] = true;
|
plainTextsHidden[index] = true;
|
||||||
|
} else {
|
||||||
|
richTextsHidden[index] = true;
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
|
@ -375,21 +388,41 @@ the AddCards dialog) should be implemented in the user of this component.
|
||||||
{#if cols[index] === "dupe"}
|
{#if cols[index] === "dupe"}
|
||||||
<DuplicateLink />
|
<DuplicateLink />
|
||||||
{/if}
|
{/if}
|
||||||
<PlainTextBadge
|
{#if plainTextDefaults[index]}
|
||||||
visible={!fieldsCollapsed[index] &&
|
<RichTextBadge
|
||||||
(fields[index] === $hoveredField ||
|
visible={!fieldsCollapsed[index] &&
|
||||||
fields[index] === $focusedField)}
|
(fields[index] === $hoveredField ||
|
||||||
bind:off={plainTextsHidden[index]}
|
fields[index] === $focusedField)}
|
||||||
on:toggle={async () => {
|
bind:off={richTextsHidden[index]}
|
||||||
plainTextsHidden[index] =
|
on:toggle={async () => {
|
||||||
!plainTextsHidden[index];
|
richTextsHidden[index] =
|
||||||
|
!richTextsHidden[index];
|
||||||
|
|
||||||
if (!plainTextsHidden[index]) {
|
if (!richTextsHidden[index]) {
|
||||||
await tick();
|
refocusInput(
|
||||||
plainTextInputs[index].api.refocus();
|
richTextInputs[index].api,
|
||||||
}
|
);
|
||||||
}}
|
}
|
||||||
/>
|
}}
|
||||||
|
/>
|
||||||
|
{:else}
|
||||||
|
<PlainTextBadge
|
||||||
|
visible={!fieldsCollapsed[index] &&
|
||||||
|
(fields[index] === $hoveredField ||
|
||||||
|
fields[index] === $focusedField)}
|
||||||
|
bind:off={plainTextsHidden[index]}
|
||||||
|
on:toggle={async () => {
|
||||||
|
plainTextsHidden[index] =
|
||||||
|
!plainTextsHidden[index];
|
||||||
|
|
||||||
|
if (!plainTextsHidden[index]) {
|
||||||
|
refocusInput(
|
||||||
|
plainTextInputs[index].api,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
{/if}
|
||||||
<slot
|
<slot
|
||||||
name="field-state"
|
name="field-state"
|
||||||
{field}
|
{field}
|
||||||
|
@ -400,10 +433,10 @@ the AddCards dialog) should be implemented in the user of this component.
|
||||||
</FieldState>
|
</FieldState>
|
||||||
</LabelContainer>
|
</LabelContainer>
|
||||||
</svelte:fragment>
|
</svelte:fragment>
|
||||||
<svelte:fragment slot="editing-inputs">
|
<svelte:fragment slot="rich-text-input">
|
||||||
<Collapsible collapsed={richTextsHidden[index]}>
|
<Collapsible collapsed={richTextsHidden[index]} let:hidden>
|
||||||
<RichTextInput
|
<RichTextInput
|
||||||
bind:hidden={richTextsHidden[index]}
|
{hidden}
|
||||||
on:focusout={() => {
|
on:focusout={() => {
|
||||||
saveFieldNow();
|
saveFieldNow();
|
||||||
$focusedInput = null;
|
$focusedInput = null;
|
||||||
|
@ -417,10 +450,13 @@ the AddCards dialog) should be implemented in the user of this component.
|
||||||
</FieldDescription>
|
</FieldDescription>
|
||||||
</RichTextInput>
|
</RichTextInput>
|
||||||
</Collapsible>
|
</Collapsible>
|
||||||
|
</svelte:fragment>
|
||||||
<Collapsible collapsed={plainTextsHidden[index]}>
|
<svelte:fragment slot="plain-text-input">
|
||||||
|
<Collapsible collapsed={plainTextsHidden[index]} let:hidden>
|
||||||
<PlainTextInput
|
<PlainTextInput
|
||||||
bind:hidden={plainTextsHidden[index]}
|
{hidden}
|
||||||
|
isDefault={plainTextDefaults[index]}
|
||||||
|
richTextHidden={richTextsHidden[index]}
|
||||||
on:focusout={() => {
|
on:focusout={() => {
|
||||||
saveFieldNow();
|
saveFieldNow();
|
||||||
$focusedInput = null;
|
$focusedInput = null;
|
||||||
|
|
59
ts/editor/RichTextBadge.svelte
Normal file
59
ts/editor/RichTextBadge.svelte
Normal file
|
@ -0,0 +1,59 @@
|
||||||
|
<!--
|
||||||
|
Copyright: Ankitects Pty Ltd and contributors
|
||||||
|
License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
||||||
|
-->
|
||||||
|
<script lang="ts">
|
||||||
|
import { createEventDispatcher, onMount } from "svelte";
|
||||||
|
|
||||||
|
import Badge from "../components/Badge.svelte";
|
||||||
|
import * as tr from "../lib/ftl";
|
||||||
|
import { getPlatformString, registerShortcut } from "../lib/shortcuts";
|
||||||
|
import { context as editorFieldContext } from "./EditorField.svelte";
|
||||||
|
import { richTextIcon } from "./icons";
|
||||||
|
|
||||||
|
const editorField = editorFieldContext.get();
|
||||||
|
const keyCombination = "Control+Shift+X";
|
||||||
|
const dispatch = createEventDispatcher();
|
||||||
|
|
||||||
|
export let visible = false;
|
||||||
|
export let off = false;
|
||||||
|
|
||||||
|
function toggle() {
|
||||||
|
dispatch("toggle");
|
||||||
|
}
|
||||||
|
|
||||||
|
function shortcut(target: HTMLElement): () => void {
|
||||||
|
return registerShortcut(toggle, keyCombination, { target });
|
||||||
|
}
|
||||||
|
|
||||||
|
onMount(() => editorField.element.then(shortcut));
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<span
|
||||||
|
class="plain-text-badge"
|
||||||
|
class:visible
|
||||||
|
class:highlighted={!off}
|
||||||
|
on:click|stopPropagation={toggle}
|
||||||
|
>
|
||||||
|
<Badge
|
||||||
|
tooltip="{tr.editingToggleVisualEditor()} ({getPlatformString(keyCombination)})"
|
||||||
|
iconSize={80}>{@html richTextIcon}</Badge
|
||||||
|
>
|
||||||
|
</span>
|
||||||
|
|
||||||
|
<style lang="scss">
|
||||||
|
span {
|
||||||
|
cursor: pointer;
|
||||||
|
opacity: 0;
|
||||||
|
|
||||||
|
&.visible {
|
||||||
|
opacity: 0.4;
|
||||||
|
&:hover {
|
||||||
|
opacity: 0.8;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
&.highlighted {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -1,6 +1,9 @@
|
||||||
// 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
|
||||||
|
|
||||||
|
import type { PlainTextInputAPI } from "./plain-text-input";
|
||||||
|
import type { RichTextInputAPI } from "./rich-text-input";
|
||||||
|
|
||||||
function isFontElement(element: Element): element is HTMLFontElement {
|
function isFontElement(element: Element): element is HTMLFontElement {
|
||||||
return element.tagName === "FONT";
|
return element.tagName === "FONT";
|
||||||
}
|
}
|
||||||
|
@ -19,3 +22,15 @@ export function withFontColor(
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/***
|
||||||
|
* Required for field inputs wrapped in Collapsible
|
||||||
|
*/
|
||||||
|
export async function refocusInput(
|
||||||
|
api: RichTextInputAPI | PlainTextInputAPI,
|
||||||
|
): Promise<void> {
|
||||||
|
do {
|
||||||
|
await new Promise(window.requestAnimationFrame);
|
||||||
|
} while (!api.focusable);
|
||||||
|
api.refocus();
|
||||||
|
}
|
||||||
|
|
|
@ -39,7 +39,9 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
||||||
import removeProhibitedTags from "./remove-prohibited";
|
import removeProhibitedTags from "./remove-prohibited";
|
||||||
import { storedToUndecorated, undecoratedToStored } from "./transform";
|
import { storedToUndecorated, undecoratedToStored } from "./transform";
|
||||||
|
|
||||||
|
export let isDefault: boolean;
|
||||||
export let hidden: boolean;
|
export let hidden: boolean;
|
||||||
|
export let richTextHidden: boolean;
|
||||||
|
|
||||||
const configuration = {
|
const configuration = {
|
||||||
mode: htmlanki,
|
mode: htmlanki,
|
||||||
|
@ -143,6 +145,8 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
||||||
<div
|
<div
|
||||||
class="plain-text-input"
|
class="plain-text-input"
|
||||||
class:light-theme={!$pageTheme.isDark}
|
class:light-theme={!$pageTheme.isDark}
|
||||||
|
class:is-default={isDefault}
|
||||||
|
class:alone={richTextHidden}
|
||||||
on:focusin={() => ($focusedInput = api)}
|
on:focusin={() => ($focusedInput = api)}
|
||||||
>
|
>
|
||||||
<CodeMirror
|
<CodeMirror
|
||||||
|
@ -156,7 +160,20 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
||||||
|
|
||||||
<style lang="scss">
|
<style lang="scss">
|
||||||
.plain-text-input {
|
.plain-text-input {
|
||||||
overflow-x: hidden;
|
overflow: hidden;
|
||||||
|
|
||||||
|
border-top: 1px solid var(--border);
|
||||||
|
border-radius: 0 0 5px 5px;
|
||||||
|
|
||||||
|
&.is-default {
|
||||||
|
border-top: none;
|
||||||
|
border-bottom: 1px solid var(--border);
|
||||||
|
border-radius: 5px 5px 0 0;
|
||||||
|
}
|
||||||
|
&.alone {
|
||||||
|
border: none;
|
||||||
|
border-radius: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
:global(.CodeMirror) {
|
:global(.CodeMirror) {
|
||||||
border-radius: 0 0 5px 5px;
|
border-radius: 0 0 5px 5px;
|
||||||
|
|
|
@ -244,7 +244,7 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
||||||
<style lang="scss">
|
<style lang="scss">
|
||||||
.rich-text-input {
|
.rich-text-input {
|
||||||
position: relative;
|
position: relative;
|
||||||
padding: 6px;
|
margin: 6px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.hidden {
|
.hidden {
|
||||||
|
|
Loading…
Reference in a new issue