mirror of
https://github.com/ankitects/anki.git
synced 2025-09-18 22:12:21 -04:00
Introduce setting to collapse field by default (#1990)
* Introduce setting to collapse field by default * Fix schema order * Change wording from adjective to imperative sounds a bit less clunky * Update rslib/src/notetype/schema11.rs (dae) * Keep settings in single column * Add back Toggle Visual Editor string * Add RichTextBadge component and show it conditionally * Reverse input order depending on default setting * Make PlainTextInput border-radius responsive to toggle states * Prevent first Collapsible transition differently * Focus inputs after Collapsible transition The double tick calls are just a temporary solution until I find the exact moment an input is focusable again. * Use requestAnimationFrame to await focusable state Note: Svelte tick doesn't seem to work in this scenario.
This commit is contained in:
parent
65601196ee
commit
d110c4916c
14 changed files with 248 additions and 83 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;
|
||||||
}
|
}
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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 {
|
||||||
|
@ -132,13 +139,11 @@ 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 {
|
||||||
|
@ -186,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 {
|
||||||
|
@ -241,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(() => {
|
||||||
|
@ -256,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,
|
||||||
|
@ -329,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];
|
||||||
|
@ -357,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;
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
|
@ -374,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}
|
||||||
|
@ -399,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;
|
||||||
|
@ -416,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,11 +160,22 @@ 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-top: 1px solid var(--border);
|
|
||||||
background: var(--code-bg);
|
background: var(--code-bg);
|
||||||
}
|
}
|
||||||
:global(.CodeMirror-lines) {
|
:global(.CodeMirror-lines) {
|
||||||
|
|
Loading…
Reference in a new issue