Simplify NoteEditor by replacing Pane components with Collapsible (#2395)

* Remove Pane components and use Collapsible for TagEditor

* Update translations

* Give TagEditor border and focus outline

* Use ScrollArea from #2248 for fields

* Refactor ScrollArea

* Fix error caused by calling bridgeCommand when it's not available

* Make sure tag editor fills whole width of container

which is important for the CSV import page.

* Update NoteEditor.svelte

* Add back removed ftl strings

* Fix tests (dae)
This commit is contained in:
Matthias Metelka 2023-02-27 07:23:19 +01:00 committed by GitHub
parent d849abace2
commit b12476de9a
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
14 changed files with 371 additions and 689 deletions

View file

@ -56,6 +56,8 @@ editing-to-make-a-cloze-deletion-on = To make a cloze deletion on an existing no
editing-toggle-html-editor = Toggle HTML Editor editing-toggle-html-editor = Toggle HTML Editor
editing-toggle-visual-editor = Toggle Visual Editor editing-toggle-visual-editor = Toggle Visual Editor
editing-toggle-sticky = Toggle sticky editing-toggle-sticky = Toggle sticky
editing-expand = Expand
editing-collapse = Collapse
editing-expand-field = Expand field editing-expand-field = Expand field
editing-collapse-field = Collapse field editing-collapse-field = Collapse field
editing-underline-text = Underline text editing-underline-text = Underline text

View file

@ -465,6 +465,11 @@ require("anki/ui").loaded.then(() => require("anki/NoteEditor").instances[0].too
if not self.addMode: if not self.addMode:
self._save_current_note() self._save_current_note()
elif cmd.startswith("setTagsCollapsed"):
(type, collapsed_string) = cmd.split(":", 1)
collapsed = collapsed_string == "true"
self.setTagsCollapsed(collapsed)
elif cmd in self._links: elif cmd in self._links:
return self._links[cmd](self) return self._links[cmd](self)
@ -1165,11 +1170,8 @@ require("anki/ui").loaded.then(() => require("anki/NoteEditor").instances[0].too
not self.mw.col.get_config("closeHTMLTags", True), not self.mw.col.get_config("closeHTMLTags", True),
) )
def collapseTags(self) -> None: def setTagsCollapsed(self, collapsed: bool) -> None:
aqt.mw.pm.set_tags_collapsed(self.editorMode, True) aqt.mw.pm.set_tags_collapsed(self.editorMode, collapsed)
def expandTags(self) -> None:
aqt.mw.pm.set_tags_collapsed(self.editorMode, False)
# Links from HTML # Links from HTML
###################################################################### ######################################################################
@ -1200,8 +1202,6 @@ require("anki/ui").loaded.then(() => require("anki/NoteEditor").instances[0].too
toggleMathjax=Editor.toggleMathjax, toggleMathjax=Editor.toggleMathjax,
toggleShrinkImages=Editor.toggleShrinkImages, toggleShrinkImages=Editor.toggleShrinkImages,
toggleCloseHTMLTags=Editor.toggleCloseHTMLTags, toggleCloseHTMLTags=Editor.toggleCloseHTMLTags,
expandTags=Editor.expandTags,
collapseTags=Editor.collapseTags,
) )

View file

@ -1,149 +0,0 @@
<!--
Copyright: Ankitects Pty Ltd and contributors
License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
-->
<script lang="ts">
import { on } from "@tslib/events";
import type { Callback } from "@tslib/typing";
import { singleCallback } from "@tslib/typing";
import { createEventDispatcher } from "svelte";
import { fly } from "svelte/transition";
import IconConstrain from "./IconConstrain.svelte";
import { horizontalHandle } from "./icons";
import type { ResizablePane } from "./types";
export let panes: ResizablePane[];
export let index = 0;
export let tip = "";
export let showIndicator = false;
export let clientHeight: number;
const rtl = window.getComputedStyle(document.body).direction == "rtl";
const dispatch = createEventDispatcher();
let destroy: Callback;
let before: ResizablePane;
let after: ResizablePane;
$: resizerAmount = panes.length - 1;
$: componentsHeight = clientHeight - resizerHeight * resizerAmount;
export function move(targets: ResizablePane[], targetHeight: number): void {
const [resizeTarget, resizePartner] = targets;
if (targetHeight <= resizeTarget.maxHeight) {
resizeTarget.resizable.getHeightResizer().setSize(targetHeight);
resizePartner.resizable
.getHeightResizer()
.setSize(componentsHeight - targetHeight);
}
}
function onMove(this: Window, { movementY }: PointerEvent): void {
if (movementY < 0) {
if (after.height - movementY <= after.maxHeight) {
const resized = before.resizable.getHeightResizer().resize(movementY);
after.resizable.getHeightResizer().resize(-resized);
} else {
const resized = before.resizable
.getHeightResizer()
.resize(after.height - after.maxHeight);
after.resizable.getHeightResizer().resize(-resized);
}
} else if (before.height + movementY <= before.maxHeight) {
const resized = after.resizable.getHeightResizer().resize(-movementY);
before.resizable.getHeightResizer().resize(-resized);
} else {
const resized = after.resizable
.getHeightResizer()
.resize(before.height - before.maxHeight);
before.resizable.getHeightResizer().resize(-resized);
}
}
let resizerHeight: number;
function releasePointer(this: Window): void {
destroy();
document.exitPointerLock();
for (const pane of panes) {
pane.resizable.getHeightResizer().stop(componentsHeight, panes.length);
}
}
function lockPointer(this: HTMLDivElement) {
this.requestPointerLock();
before = panes[index];
after = panes[index + 1];
for (const pane of panes) {
pane.resizable.getHeightResizer().start();
}
destroy = singleCallback(
on(window, "pointermove", onMove),
on(window, "pointerup", () => {
releasePointer.call(window);
dispatch("release");
}),
);
}
</script>
<div
class="horizontal-resizer"
class:rtl
title={tip}
bind:clientHeight={resizerHeight}
on:pointerdown|preventDefault={lockPointer}
on:dblclick|preventDefault
>
{#if showIndicator}
<div
class="resize-indicator"
transition:fly={{ x: rtl ? 25 : -25, duration: 200 }}
>
<slot />
</div>
{/if}
<div class="drag-handle">
<IconConstrain iconSize={80}>{@html horizontalHandle}</IconConstrain>
</div>
</div>
<style lang="scss">
.horizontal-resizer {
width: 100%;
cursor: row-resize;
position: relative;
height: 25px;
border-top: 1px solid var(--border);
z-index: 20;
.drag-handle {
position: absolute;
left: 50%;
top: 50%;
transform: translate(-50%, -50%);
opacity: 0.4;
}
&:hover .drag-handle {
opacity: 0.8;
}
.resize-indicator {
position: absolute;
font-size: small;
bottom: 0;
}
&.rtl .resize-indicator {
padding: 0.5rem 0 0 0.5rem;
right: 0;
}
}
</style>

View file

@ -1,65 +0,0 @@
<!--
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 } from "svelte";
import { writable } from "svelte/store";
import type { Resizer } from "./resizable";
import { resizable } from "./resizable";
export let baseSize = 600;
const resizes = writable(false);
const paneSize = writable(baseSize);
const [
{ resizesDimension: resizesWidth, resizedDimension: resizedWidth },
widthAction,
widthResizer,
] = resizable(baseSize, resizes, paneSize);
const [
{ resizesDimension: resizesHeight, resizedDimension: resizedHeight },
heightAction,
heightResizer,
] = resizable(baseSize, resizes, paneSize);
const dispatch = createEventDispatcher();
$: resizeArgs = { width: $resizedWidth, height: $resizedHeight };
$: dispatch("resize", resizeArgs);
export function getHeightResizer(): Resizer {
return heightResizer;
}
export function getWidthResizer(): Resizer {
return widthResizer;
}
</script>
<div
class="pane"
class:resize={$resizes}
class:resize-width={$resizesWidth}
class:resize-height={$resizesHeight}
style:--pane-size={$paneSize}
style:--resized-width="{$resizedWidth}px"
style:--resized-height="{$resizedHeight}px"
on:focusin
on:pointerdown
use:widthAction={(element) => element.offsetWidth}
use:heightAction={(element) => element.offsetHeight}
>
<slot />
</div>
<style lang="scss">
@use "sass/panes" as panes;
.pane {
@include panes.resizable(column, true, true);
opacity: var(--opacity, 1);
}
</style>

View file

@ -1,81 +0,0 @@
<!--
Copyright: Ankitects Pty Ltd and contributors
License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
-->
<script lang="ts">
import { promiseWithResolver } from "@tslib/promise";
export let scroll = true;
const [element, elementResolve] = promiseWithResolver<HTMLElement>();
let clientWidth = 0;
let clientHeight = 0;
let scrollWidth = 0;
let scrollHeight = 0;
let scrollTop = 0;
let scrollLeft = 0;
$: overflowTop = scrollTop > 0;
$: overflowBottom = scrollTop < scrollHeight - clientHeight;
$: overflowLeft = scrollLeft > 0;
$: overflowRight = scrollLeft < scrollWidth - clientWidth;
$: shadows = {
top: overflowTop ? "0 5px" : null,
bottom: overflowBottom ? "0 -5px" : null,
left: overflowLeft ? "5px 0" : null,
right: overflowRight ? "-5px 0" : null,
};
const rest = "5px -5px var(--shadow)";
$: shadow = Array.from(
Object.values(shadows).filter((v) => v != null),
(v) => `inset ${v} ${rest}`,
).join(", ");
async function updateScrollState(): Promise<void> {
const el = await element;
scrollHeight = el.scrollHeight;
scrollWidth = el.scrollWidth;
scrollTop = el.scrollTop;
scrollLeft = el.scrollLeft;
}
</script>
<div
class="pane-content"
class:scroll
style:--box-shadow={shadow}
style:--client-height="{clientHeight}px"
use:elementResolve
bind:clientHeight
bind:clientWidth
on:scroll={updateScrollState}
on:resize={updateScrollState}
>
<slot />
</div>
<style lang="scss">
.pane-content {
display: flex;
flex-direction: column;
flex-grow: 1;
overflow: hidden;
&.scroll {
overflow: auto;
}
/* force box-shadow to be rendered above children */
&::before {
content: "";
position: fixed;
pointer-events: none;
left: 0;
right: 0;
z-index: 4;
height: var(--client-height);
box-shadow: var(--box-shadow);
transition: box-shadow var(--transition) ease-in-out;
}
}
</style>

View file

@ -0,0 +1,147 @@
<!--
Copyright: Ankitects Pty Ltd and contributors
License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
-->
<script lang="ts">
let className: string = "";
export { className as class };
export let scrollX = false;
export let scrollY = false;
let scrollBarWidth = 0;
let scrollBarHeight = 0;
let measuring = true;
const scrollStates = {
top: false,
right: false,
bottom: false,
left: false,
};
function measureScrollbar(el: HTMLDivElement) {
scrollBarWidth = el.offsetWidth - el.clientWidth;
scrollBarHeight = el.offsetHeight - el.clientHeight;
measuring = false;
}
const callback = (entries: IntersectionObserverEntry[]) => {
entries.forEach((entry) => {
scrollStates[entry.target.getAttribute("data-edge")!] =
!entry.isIntersecting;
});
};
let observer: IntersectionObserver;
function initObserver(el: HTMLDivElement) {
observer = new IntersectionObserver(callback, { root: el });
for (const edge of el.getElementsByClassName("scroll-edge")) {
observer.observe(edge);
}
}
</script>
<div class="scroll-area-relative">
<div class="scroll-area-wrapper {className}">
<div
class="scroll-area"
class:measuring
class:scroll-x={scrollX}
class:scroll-y={scrollY}
style:--scrollbar-height="{scrollBarHeight}px"
use:measureScrollbar
use:initObserver
>
<div class="d-flex flex-column flex-grow-1">
<div class="scroll-edge" data-edge="top" />
<div class="d-flex flex-row flex-grow-1">
<div class="scroll-edge" data-edge="left" />
<div class="scroll-content flex-grow-1">
<slot />
</div>
<div class="scroll-edge" data-edge="right" />
</div>
<div class="scroll-edge" data-edge="bottom" />
</div>
</div>
{#if scrollStates.top} <div class="scroll-shadow top-0" /> {/if}
{#if scrollStates.bottom} <div class="scroll-shadow bottom-0" /> {/if}
{#if scrollStates.left} <div class="scroll-shadow start-0" /> {/if}
{#if scrollStates.right} <div class="scroll-shadow end-0" /> {/if}
</div>
</div>
<style lang="scss">
$shadow-top: inset 0 5px 5px -5px var(--shadow);
$shadow-bottom: inset 0 -5px 5px -5px var(--shadow);
$shadow-left: inset 5px 0 5px -5px var(--shadow);
$shadow-right: inset -5px 0 5px -5px var(--shadow);
.scroll-area-relative {
height: calc(var(--height) + var(--scrollbar-height));
flex-grow: 1;
position: relative;
}
.scroll-area {
position: absolute;
height: 100%;
width: 100%;
display: flex;
flex-direction: column;
overscroll-behavior: none;
overflow: auto;
&.scroll-x {
overflow-x: auto;
overflow-y: hidden;
overscroll-behavior-y: auto;
}
&.scroll-y {
overflow-y: auto;
overflow-x: hidden;
overscroll-behavior-x: none;
}
&.measuring {
visibility: hidden;
overflow: scroll;
}
}
.scroll-edge {
&[data-edge="top"],
&[data-edge="bottom"] {
height: 1px;
}
&[data-edge="left"],
&[data-edge="right"] {
width: 1px;
}
}
.scroll-shadow {
position: absolute;
pointer-events: none;
// z-index between LabelContainer (editor) and FloatingArrow
z-index: 55;
&.top-0,
&.bottom-0 {
left: 0;
right: 0;
height: 5px;
}
&.start-0,
&.end-0 {
top: 0;
bottom: 0;
width: 5px;
}
&.top-0 {
box-shadow: $shadow-top;
}
&.bottom-0 {
box-shadow: $shadow-bottom;
}
&.start-0 {
box-shadow: $shadow-left;
}
&.end-0 {
box-shadow: $shadow-right;
}
}
</style>

View file

@ -1,103 +0,0 @@
<!--
Copyright: Ankitects Pty Ltd and contributors
License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
-->
<script lang="ts">
import { on } from "@tslib/events";
import type { Callback } from "@tslib/typing";
import { singleCallback } from "@tslib/typing";
import IconConstrain from "./IconConstrain.svelte";
import { verticalHandle } from "./icons";
import type { ResizablePane } from "./types";
export let components: ResizablePane[];
export let index = 0;
export let clientWidth: number;
let destroy: Callback;
let before: ResizablePane;
let after: ResizablePane;
function onMove(this: Window, { movementX }: PointerEvent): void {
if (movementX < 0) {
const resized = before.resizable.getWidthResizer().resize(movementX);
after.resizable.getWidthResizer().resize(-resized);
} else if (movementX > 0) {
const resized = after.resizable.getWidthResizer().resize(-movementX);
before.resizable.getWidthResizer().resize(-resized);
}
}
let minWidth: number;
function releasePointer(this: Window): void {
destroy();
document.exitPointerLock();
const resizerAmount = components.length - 1;
const componentsWidth = clientWidth - minWidth * resizerAmount;
for (const component of components) {
component.resizable
.getWidthResizer()
.stop(componentsWidth, components.length);
}
}
function lockPointer(this: HTMLHRElement) {
this.requestPointerLock();
before = components[index];
after = components[index + 1];
for (const component of components) {
component.resizable.getWidthResizer().start();
}
destroy = singleCallback(
on(window, "pointermove", onMove),
on(window, "pointerup", releasePointer),
);
}
</script>
<div
bind:clientWidth={minWidth}
class="vertical-resizer"
on:pointerdown|preventDefault={lockPointer}
>
<div class="drag-handle">
<IconConstrain iconSize={80}>{@html verticalHandle}</IconConstrain>
</div>
</div>
<style lang="scss">
.vertical-resizer {
height: 100%;
cursor: col-resize;
position: relative;
z-index: 20;
.drag-handle {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
opacity: 0.4;
}
&::before {
content: "";
position: absolute;
width: 10px;
left: -5px;
top: 0;
height: 100%;
}
&:hover .drag-handle {
opacity: 0.8;
}
}
</style>

View file

@ -7,10 +7,9 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
import { chevronDown } from "./icons"; import { chevronDown } from "./icons";
export let collapsed = false; export let collapsed = false;
export let highlighted = false;
</script> </script>
<div class="collapse-badge" class:collapsed class:highlighted> <div class="collapse-badge" class:collapsed>
<Badge iconSize={80}>{@html chevronDown}</Badge> <Badge iconSize={80}>{@html chevronDown}</Badge>
</div> </div>
@ -20,7 +19,7 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
opacity: 0.4; opacity: 0.4;
transition: opacity var(--transition) ease-in-out, transition: opacity var(--transition) ease-in-out,
transform var(--transition) ease-in; transform var(--transition) ease-in;
&.highlighted { :global(.collapse-label:hover) & {
opacity: 1; opacity: 1;
} }
&.collapsed { &.collapsed {

View file

@ -0,0 +1,29 @@
<!--
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 } from "svelte";
import CollapseBadge from "./CollapseBadge.svelte";
export let collapsed: boolean;
export let tooltip: string;
const dispatch = createEventDispatcher();
function toggle() {
dispatch("toggle");
}
</script>
<span class="collapse-label" title={tooltip} on:click|stopPropagation={toggle}>
<CollapseBadge {collapsed} />
<slot />
</span>
<style lang="scss">
.collapse-label {
cursor: pointer;
}
</style>

View file

@ -132,7 +132,7 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
/* make room for thicker focus border */ /* make room for thicker focus border */
margin: 1px; margin: 1px;
border-radius: 5px; border-radius: var(--border-radius);
border: 1px solid var(--border); border: 1px solid var(--border);
@include elevation(1); @include elevation(1);

View file

@ -2,13 +2,19 @@
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
--> -->
<script lang="ts">
import ScrollArea from "components/ScrollArea.svelte";
</script>
<!-- <!--
@component @component
Contains the fields. This contains the scrollable area. Contains the fields. This contains the scrollable area.
--> -->
<div class="fields"> <ScrollArea>
<div class="fields">
<slot /> <slot />
</div> </div>
</ScrollArea>
<style lang="scss"> <style lang="scss">
.fields { .fields {

View file

@ -4,33 +4,18 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
--> -->
<script lang="ts"> <script lang="ts">
import * as tr from "@tslib/ftl"; import * as tr from "@tslib/ftl";
import { createEventDispatcher } from "svelte";
import CollapseBadge from "./CollapseBadge.svelte"; import CollapseLabel from "./CollapseLabel.svelte";
export let collapsed: boolean; export let collapsed: boolean;
let hovered = false;
$: tooltip = collapsed ? tr.editingExpandField() : tr.editingCollapseField(); $: tooltip = collapsed ? tr.editingExpandField() : tr.editingCollapseField();
const dispatch = createEventDispatcher();
function toggle() {
dispatch("toggle");
}
</script> </script>
<div class="label-container"> <div class="label-container">
<span <CollapseLabel {collapsed} {tooltip} on:toggle>
class="clickable"
title={tooltip}
on:click|stopPropagation={toggle}
on:mouseenter={() => (hovered = true)}
on:mouseleave={() => (hovered = false)}
>
<CollapseBadge {collapsed} highlighted={hovered} />
<slot name="field-name" /> <slot name="field-name" />
</span> </CollapseLabel>
<slot /> <slot />
</div> </div>
@ -46,9 +31,5 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
position: sticky; position: sticky;
top: 0; top: 0;
z-index: 50; z-index: 50;
.clickable {
cursor: pointer;
}
} }
</style> </style>

View file

@ -47,12 +47,7 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
import Absolute from "../components/Absolute.svelte"; import Absolute from "../components/Absolute.svelte";
import Badge from "../components/Badge.svelte"; import Badge from "../components/Badge.svelte";
import HorizontalResizer from "../components/HorizontalResizer.svelte";
import Pane from "../components/Pane.svelte";
import PaneContent from "../components/PaneContent.svelte";
import { ResizablePane } from "../components/types";
import { TagEditor } from "../tag-editor"; import { TagEditor } from "../tag-editor";
import TagAddButton from "../tag-editor/tag-options-button/TagAddButton.svelte";
import { ChangeTimer } from "./change-timer"; import { ChangeTimer } from "./change-timer";
import { clearableArray } from "./destroyable"; import { clearableArray } from "./destroyable";
import DuplicateLink from "./DuplicateLink.svelte"; import DuplicateLink from "./DuplicateLink.svelte";
@ -197,9 +192,11 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
const tagsCollapsed = writable<boolean>(); const tagsCollapsed = writable<boolean>();
export function setTagsCollapsed(collapsed: boolean): void { export function setTagsCollapsed(collapsed: boolean): void {
$tagsCollapsed = collapsed; $tagsCollapsed = collapsed;
if (collapsed) {
lowerResizer.move([tagsPane, fieldsPane], tagsPane.minHeight);
} }
function updateTagsCollapsed(collapsed: boolean) {
$tagsCollapsed = collapsed;
bridgeCommand(`setTagsCollapsed:${$tagsCollapsed}`);
} }
let noteId: number | null = null; let noteId: number | null = null;
@ -312,8 +309,10 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
} }
import { wrapInternal } from "@tslib/wrap"; import { wrapInternal } from "@tslib/wrap";
import Shortcut from "components/Shortcut.svelte";
import { mathjaxConfig } from "../editable/mathjax-element"; import { mathjaxConfig } from "../editable/mathjax-element";
import CollapseLabel from "./CollapseLabel.svelte";
import { refocusInput } from "./helpers"; import { refocusInput } from "./helpers";
import * as oldEditorAdapter from "./old-editor-adapter"; import * as oldEditorAdapter from "./old-editor-adapter";
@ -376,39 +375,7 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
setContextProperty(api); setContextProperty(api);
setupLifecycleHooks(api); setupLifecycleHooks(api);
let clientHeight: number;
const fieldsPane = new ResizablePane();
const tagsPane = new ResizablePane();
let lowerResizer: HorizontalResizer;
let tagEditor: TagEditor;
$: tagAmount = $tags.length; $: tagAmount = $tags.length;
let snapTags = $tagsCollapsed;
function collapseTags(): void {
lowerResizer.move([tagsPane, fieldsPane], tagsPane.minHeight);
$tagsCollapsed = snapTags = true;
}
function expandTags(): void {
lowerResizer.move([tagsPane, fieldsPane], tagsPane.maxHeight);
$tagsCollapsed = snapTags = false;
}
window.addEventListener("resize", () => snapResizer(snapTags));
function snapResizer(collapse: boolean): void {
if (collapse) {
collapseTags();
bridgeCommand("collapseTags");
} else {
expandTags();
bridgeCommand("expandTags");
}
}
</script> </script>
<!-- <!--
@ -419,7 +386,7 @@ components and functionality for general note editing.
Functionality exclusive to specific note-editing views (e.g. in the browser or Functionality exclusive to specific note-editing views (e.g. in the browser or
the AddCards dialog) should be implemented in the user of this component. the AddCards dialog) should be implemented in the user of this component.
--> -->
<div class="note-editor" bind:clientHeight> <div class="note-editor">
<EditorToolbar {size} {wrap} api={toolbar}> <EditorToolbar {size} {wrap} api={toolbar}>
<slot slot="notetypeButtons" name="notetypeButtons" /> <slot slot="notetypeButtons" name="notetypeButtons" />
</EditorToolbar> </EditorToolbar>
@ -435,13 +402,6 @@ the AddCards dialog) should be implemented in the user of this component.
</Absolute> </Absolute>
{/if} {/if}
<Pane
bind:this={fieldsPane.resizable}
on:resize={(e) => {
fieldsPane.height = e.detail.height;
}}
>
<PaneContent>
<Fields> <Fields>
{#each fieldsData as field, index} {#each fieldsData as field, index}
{@const content = fieldStores[index]} {@const content = fieldStores[index]}
@ -514,9 +474,7 @@ the AddCards dialog) should be implemented in the user of this component.
!richTextsHidden[index]; !richTextsHidden[index];
if (!richTextsHidden[index]) { if (!richTextsHidden[index]) {
refocusInput( refocusInput(richTextInputs[index].api);
richTextInputs[index].api,
);
} }
}} }}
/> />
@ -531,9 +489,7 @@ the AddCards dialog) should be implemented in the user of this component.
!plainTextsHidden[index]; !plainTextsHidden[index];
if (!plainTextsHidden[index]) { if (!plainTextsHidden[index]) {
refocusInput( refocusInput(plainTextInputs[index].api);
plainTextInputs[index].api,
);
} }
}} }}
/> />
@ -589,69 +545,23 @@ the AddCards dialog) should be implemented in the user of this component.
<SymbolsOverlay /> <SymbolsOverlay />
{/if} {/if}
</Fields> </Fields>
</PaneContent>
</Pane>
<HorizontalResizer <Shortcut
panes={[fieldsPane, tagsPane]}
showIndicator={$tagsCollapsed || snapTags}
tip={$tagsCollapsed
? tr.editingDoubleClickToExpand()
: tr.editingDoubleClickToCollapse()}
{clientHeight}
bind:this={lowerResizer}
on:dblclick={() => snapResizer(!$tagsCollapsed)}
on:release={() => {
snapResizer(snapTags);
}}
>
<div class="tags-expander">
<TagAddButton
on:tagappend={() => {
tagEditor.appendEmptyTag();
}}
keyCombination="Control+Shift+T" keyCombination="Control+Shift+T"
> on:action={() => {
{@html tagAmount > 0 ? `${tagAmount} ${tr.editingTags()}` : ""} updateTagsCollapsed(false);
</TagAddButton>
</div>
</HorizontalResizer>
<Pane
bind:this={tagsPane.resizable}
on:resize={(e) => {
tagsPane.height = e.detail.height;
if (tagsPane.maxHeight > 0) {
snapTags = tagsPane.height < tagsPane.maxHeight / 2;
}
}}
--opacity={(() => {
if (!$tagsCollapsed) {
return 1;
} else {
return snapTags ? tagsPane.height / tagsPane.maxHeight : 1;
}
})()}
>
<PaneContent scroll={false}>
<TagEditor
{tags}
--button-opacity={snapTags ? 0 : 1}
bind:this={tagEditor}
on:tagsupdate={saveTags}
on:tagsFocused={() => {
expandTags();
$tagsCollapsed = false;
}}
on:heightChange={(e) => {
tagsPane.maxHeight = e.detail.height;
if (!$tagsCollapsed) {
expandTags();
}
}} }}
/> />
</PaneContent> <CollapseLabel
</Pane> collapsed={$tagsCollapsed}
tooltip={$tagsCollapsed ? tr.editingExpand() : tr.editingCollapse()}
on:toggle={() => updateTagsCollapsed(!$tagsCollapsed)}
>
{@html `${tagAmount > 0 ? tagAmount : ""} ${tr.editingTags()}`}
</CollapseLabel>
<Collapsible toggleDisplay collapse={$tagsCollapsed}>
<TagEditor {tags} on:tagsupdate={saveTags} />
</Collapsible>
</div> </div>
<style lang="scss"> <style lang="scss">

View file

@ -391,8 +391,6 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
$: assumedRows = Math.floor(height / badgeHeight); $: assumedRows = Math.floor(height / badgeHeight);
$: shortenTags = shortenTags || assumedRows > 2; $: shortenTags = shortenTags || assumedRows > 2;
$: anyTagsSelected = tagTypes.some((tag) => tag.selected); $: anyTagsSelected = tagTypes.some((tag) => tag.selected);
$: dispatch("heightChange", { height: height + 1 });
</script> </script>
{#if anyTagsSelected} {#if anyTagsSelected}
@ -506,11 +504,19 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
<style lang="scss"> <style lang="scss">
.tag-editor { .tag-editor {
display: flex; display: flex;
flex-grow: 1;
flex-flow: row wrap; flex-flow: row wrap;
align-items: flex-end; align-items: flex-end;
background: var(--canvas-inset); background: var(--canvas-elevated);
border: 1px solid var(--border);
border-radius: var(--border-radius); border-radius: var(--border-radius);
padding: 6px; padding: 6px;
margin: 1px;
&:focus-within {
outline-offset: -1px;
outline: 2px solid var(--border-focus);
}
} }
.tag-relative { .tag-relative {