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:
Matthias Metelka 2022-08-31 15:34:39 +02:00 committed by GitHub
parent 65601196ee
commit d110c4916c
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
14 changed files with 248 additions and 83 deletions

View file

@ -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

View file

@ -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

View file

@ -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;
} }

View file

@ -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()),

View file

@ -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

View file

@ -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>

View file

@ -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![],
}, },
} }

View file

@ -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,
} }
} }

View file

@ -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>

View file

@ -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>

View file

@ -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;

View 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>

View file

@ -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();
}

View file

@ -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) {