diff --git a/ftl/core/editing.ftl b/ftl/core/editing.ftl
index 89e876b98..e6fcc47cc 100644
--- a/ftl/core/editing.ftl
+++ b/ftl/core/editing.ftl
@@ -1,3 +1,4 @@
+editing-actual-size = Toggle actual size
editing-add-media = Add Media
editing-align-left = Align left
editing-align-right = Align right
@@ -15,6 +16,9 @@ editing-cut = Cut
editing-edit-current = Edit Current
editing-edit-html = Edit HTML
editing-fields = Fields
+editing-float-left = Float left
+editing-float-right = Float right
+editing-float-none = No float
editing-html-editor = HTML Editor
editing-indent = Increase indent
editing-italic-text = Italic text
diff --git a/ts/components/ButtonGroup.svelte b/ts/components/ButtonGroup.svelte
index 6e2ef5989..6a5be8357 100644
--- a/ts/components/ButtonGroup.svelte
+++ b/ts/components/ButtonGroup.svelte
@@ -19,6 +19,7 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
export { className as class };
export let size: number | undefined = undefined;
+
export let wrap: boolean | undefined = undefined;
$: buttonSize = size ? `--buttons-size: ${size}rem; ` : "";
@@ -45,9 +46,9 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
if ($items.length === 1) {
return ButtonPosition.Standalone;
} else if (index === 0) {
- return ButtonPosition.Leftmost;
+ return ButtonPosition.InlineStart;
} else if (index === $items.length - 1) {
- return ButtonPosition.Rightmost;
+ return ButtonPosition.InlineEnd;
} else {
return ButtonPosition.Center;
}
@@ -95,7 +96,7 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
div {
+ flex-direction: row;
flex-wrap: var(--buttons-wrap);
padding: calc(var(--buttons-size) / 10);
margin: 0;
diff --git a/ts/components/ButtonGroupItem.svelte b/ts/components/ButtonGroupItem.svelte
index 50a9938bf..79d214469 100644
--- a/ts/components/ButtonGroupItem.svelte
+++ b/ts/components/ButtonGroupItem.svelte
@@ -20,21 +20,24 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
let position_: ButtonPosition;
let style: string;
- const radius = "calc(var(--buttons-size) / 7.5)";
+ const radius = "5px";
+
+ const leftStyle = `--border-left-radius: ${radius}; --border-right-radius: 0; `;
+ const rightStyle = `--border-left-radius: 0; --border-right-radius: ${radius}; `;
$: {
switch (position_) {
case ButtonPosition.Standalone:
style = `--border-left-radius: ${radius}; --border-right-radius: ${radius}; `;
break;
- case ButtonPosition.Leftmost:
- style = `--border-left-radius: ${radius}; --border-right-radius: 0; `;
+ case ButtonPosition.InlineStart:
+ style = leftStyle;
break;
case ButtonPosition.Center:
style = "--border-left-radius: 0; --border-right-radius: 0; ";
break;
- case ButtonPosition.Rightmost:
- style = `--border-left-radius: 0; --border-right-radius: ${radius}; `;
+ case ButtonPosition.InlineEnd:
+ style = rightStyle;
break;
}
}
diff --git a/ts/components/IconButton.svelte b/ts/components/IconButton.svelte
index e23a4d5d3..2f9d7b61a 100644
--- a/ts/components/IconButton.svelte
+++ b/ts/components/IconButton.svelte
@@ -18,6 +18,7 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
export let iconSize: number = 75;
export let widthMultiplier: number = 1;
+ export let flipX: boolean = false;
let buttonRef: HTMLButtonElement;
@@ -44,7 +45,9 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
on:click
on:mousedown|preventDefault
>
-
+
+
+
diff --git a/ts/editor/HandleControl.svelte b/ts/editor/HandleControl.svelte
new file mode 100644
index 000000000..6e8f0e429
--- /dev/null
+++ b/ts/editor/HandleControl.svelte
@@ -0,0 +1,139 @@
+
+
+
+
+
+
diff --git a/ts/editor/HandleLabel.svelte b/ts/editor/HandleLabel.svelte
new file mode 100644
index 000000000..d4e36b16f
--- /dev/null
+++ b/ts/editor/HandleLabel.svelte
@@ -0,0 +1,69 @@
+
+
+
+
+
+
+
+
diff --git a/ts/editor/HandleSelection.svelte b/ts/editor/HandleSelection.svelte
new file mode 100644
index 000000000..e0ef0f46e
--- /dev/null
+++ b/ts/editor/HandleSelection.svelte
@@ -0,0 +1,56 @@
+
+
+
+
+ /* prevent triggering Bootstrap dropdown */ event.stopImmediatePropagation()}
+ style="--left: {left}px; --top: {top}px; --width: {width}px; --height: {height}px; --offsetX: {offsetX}px; --offsetY: {offsetY}px;"
+>
+
+
+
+
diff --git a/ts/editor/ImageHandle.svelte b/ts/editor/ImageHandle.svelte
new file mode 100644
index 000000000..1fdee5374
--- /dev/null
+++ b/ts/editor/ImageHandle.svelte
@@ -0,0 +1,210 @@
+
+
+
+{#if sheet}
+
+ {
+ updateSizesWithDimensions();
+ dropdownObject.update();
+ }}
+ let:toggleActualSize
+ let:active
+ >
+ {#if activeImage}
+ createDropdown(event.detail.selection)}
+ >
+
+
+
+ {actualWidth}×{actualHeight}
+ {#if customDimensions}
+ (Original: {naturalWidth}×{naturalHeight})
+ {/if}
+
+
+ {
+ if (active) {
+ setPointerCapture(event);
+ }
+ }}
+ on:pointermove={(event) => {
+ resize(event);
+ updateSizesWithDimensions();
+ dropdownObject.update();
+ }}
+ />
+
+
+
+ -
+
+
+ -
+
+
+
+
+ {/if}
+
+
+{/if}
diff --git a/ts/editor/ImageHandleFloatButtons.svelte b/ts/editor/ImageHandleFloatButtons.svelte
new file mode 100644
index 000000000..2d221f0a6
--- /dev/null
+++ b/ts/editor/ImageHandleFloatButtons.svelte
@@ -0,0 +1,61 @@
+
+
+
+
+
+ {
+ image.style.float = "left";
+ setTimeout(() => dispatch("update"));
+ }}>{@html inlineStartIcon}
+
+
+
+ {
+ image.style.float = "";
+ setTimeout(() => dispatch("update"));
+ }}>{@html floatNoneIcon}
+
+
+
+ {
+ image.style.float = "right";
+ setTimeout(() => dispatch("update"));
+ }}>{@html inlineEndIcon}
+
+
diff --git a/ts/editor/ImageHandleSizeSelect.svelte b/ts/editor/ImageHandleSizeSelect.svelte
new file mode 100644
index 000000000..bed0d71b2
--- /dev/null
+++ b/ts/editor/ImageHandleSizeSelect.svelte
@@ -0,0 +1,26 @@
+
+
+
+
+
+ {@html icon}
+
+
diff --git a/ts/editor/TemplateButtons.svelte b/ts/editor/TemplateButtons.svelte
index cb224de5b..b9766e0c9 100644
--- a/ts/editor/TemplateButtons.svelte
+++ b/ts/editor/TemplateButtons.svelte
@@ -18,8 +18,7 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
import OnlyEditable from "./OnlyEditable.svelte";
import ClozeButton from "./ClozeButton.svelte";
- import { getCurrentField } from ".";
- import { appendInParentheses } from "./helpers";
+ import { getCurrentField, appendInParentheses } from "./helpers";
import { wrapCurrent } from "./wrap";
import { paperclipIcon, micIcon, functionIcon, xmlIcon } from "./icons";
diff --git a/ts/editor/WithImageConstrained.svelte b/ts/editor/WithImageConstrained.svelte
new file mode 100644
index 000000000..a9e303b2d
--- /dev/null
+++ b/ts/editor/WithImageConstrained.svelte
@@ -0,0 +1,199 @@
+
+
+
+{#if activeImage}
+
+{/if}
diff --git a/ts/editor/change-timer.ts b/ts/editor/change-timer.ts
index fb580fb84..033e9f9ed 100644
--- a/ts/editor/change-timer.ts
+++ b/ts/editor/change-timer.ts
@@ -3,7 +3,7 @@
import type { EditingArea } from "./editing-area";
-import { getCurrentField } from ".";
+import { getCurrentField } from "./helpers";
import { bridgeCommand } from "./lib";
import { getNoteId } from "./note-id";
@@ -23,9 +23,10 @@ function clearChangeTimer(): void {
export function saveField(currentField: EditingArea, type: "blur" | "key"): void {
clearChangeTimer();
- bridgeCommand(
- `${type}:${currentField.ord}:${getNoteId()}:${currentField.fieldHTML}`
- );
+ const command = `${type}:${currentField.ord}:${getNoteId()}:${
+ currentField.fieldHTML
+ }`;
+ bridgeCommand(command);
}
export function saveNow(keepFocus: boolean): void {
diff --git a/ts/editor/editable-container.ts b/ts/editor/editable-container.ts
new file mode 100644
index 000000000..3448e662e
--- /dev/null
+++ b/ts/editor/editable-container.ts
@@ -0,0 +1,59 @@
+// Copyright: Ankitects Pty Ltd and contributors
+// License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
+
+/* eslint
+@typescript-eslint/no-non-null-assertion: "off",
+ */
+
+export class EditableContainer extends HTMLDivElement {
+ baseStyle: HTMLStyleElement;
+ baseRule?: CSSStyleRule;
+ imageStyle?: HTMLStyleElement;
+
+ constructor() {
+ super();
+ const shadow = this.attachShadow({ mode: "open" });
+
+ if (document.documentElement.classList.contains("night-mode")) {
+ this.classList.add("night-mode");
+ }
+
+ const rootStyle = document.createElement("link");
+ rootStyle.setAttribute("rel", "stylesheet");
+ rootStyle.setAttribute("href", "./_anki/css/editable.css");
+ shadow.appendChild(rootStyle);
+
+ this.baseStyle = document.createElement("style");
+ this.baseStyle.setAttribute("rel", "stylesheet");
+ this.baseStyle.id = "baseStyle";
+ shadow.appendChild(this.baseStyle);
+ }
+
+ connectedCallback(): void {
+ const sheet = this.baseStyle.sheet as CSSStyleSheet;
+ const baseIndex = sheet.insertRule("anki-editable {}");
+ this.baseRule = sheet.cssRules[baseIndex] as CSSStyleRule;
+ }
+
+ initialize(color: string): void {
+ this.setBaseColor(color);
+ }
+
+ setBaseColor(color: string): void {
+ if (this.baseRule) {
+ this.baseRule.style.color = color;
+ }
+ }
+
+ setBaseStyling(fontFamily: string, fontSize: string, direction: string): void {
+ if (this.baseRule) {
+ this.baseRule.style.fontFamily = fontFamily;
+ this.baseRule.style.fontSize = fontSize;
+ this.baseRule.style.direction = direction;
+ }
+ }
+
+ isRightToLeft(): boolean {
+ return this.baseRule!.style.direction === "rtl";
+ }
+}
diff --git a/ts/editor/editable.scss b/ts/editor/editable.scss
index a13e719e6..360c9d83c 100644
--- a/ts/editor/editable.scss
+++ b/ts/editor/editable.scss
@@ -6,6 +6,10 @@ anki-editable {
overflow: auto;
padding: 6px;
+ &:focus {
+ outline: none;
+ }
+
* {
max-width: 100%;
}
diff --git a/ts/editor/editing-area.ts b/ts/editor/editing-area.ts
index 2309a2e1e..c1578d76f 100644
--- a/ts/editor/editing-area.ts
+++ b/ts/editor/editing-area.ts
@@ -3,8 +3,12 @@
/* eslint
@typescript-eslint/no-non-null-assertion: "off",
+@typescript-eslint/no-explicit-any: "off",
*/
+import ImageHandle from "./ImageHandle.svelte";
+
+import type { EditableContainer } from "./editable-container";
import type { Editable } from "./editable";
import type { Codable } from "./codable";
@@ -12,43 +16,71 @@ import { updateActiveButtons } from "./toolbar";
import { bridgeCommand } from "./lib";
import { onInput, onKey, onKeyUp } from "./input-handlers";
import { onFocus, onBlur } from "./focus-handlers";
+import { nightModeKey } from "components/context-keys";
function onCutOrCopy(): void {
bridgeCommand("cutOrCopy");
}
export class EditingArea extends HTMLDivElement {
+ imageHandle: Promise
;
+ editableContainer: EditableContainer;
editable: Editable;
codable: Codable;
- baseStyle: HTMLStyleElement;
constructor() {
super();
- this.attachShadow({ mode: "open" });
this.className = "field";
- if (document.documentElement.classList.contains("night-mode")) {
- this.classList.add("night-mode");
- }
+ this.editableContainer = document.createElement("div", {
+ is: "anki-editable-container",
+ }) as EditableContainer;
- const rootStyle = document.createElement("link");
- rootStyle.setAttribute("rel", "stylesheet");
- rootStyle.setAttribute("href", "./_anki/css/editable.css");
- this.shadowRoot!.appendChild(rootStyle);
-
- this.baseStyle = document.createElement("style");
- this.baseStyle.setAttribute("rel", "stylesheet");
- this.shadowRoot!.appendChild(this.baseStyle);
+ const imageStyle = document.createElement("style");
+ imageStyle.setAttribute("rel", "stylesheet");
+ imageStyle.id = "imageHandleStyle";
this.editable = document.createElement("anki-editable") as Editable;
- this.shadowRoot!.appendChild(this.editable);
+
+ const context = new Map();
+ context.set(
+ nightModeKey,
+ document.documentElement.classList.contains("night-mode")
+ );
+
+ let imageHandleResolve: (value: ImageHandle) => void;
+ this.imageHandle = new Promise((resolve) => {
+ imageHandleResolve = resolve;
+ });
+
+ imageStyle.addEventListener("load", () =>
+ imageHandleResolve(
+ new ImageHandle({
+ target: this,
+ anchor: this.editableContainer,
+ props: {
+ container: this.editable,
+ sheet: imageStyle.sheet,
+ },
+ context,
+ } as any)
+ )
+ );
+
+ this.editableContainer.shadowRoot!.appendChild(imageStyle);
+ this.editableContainer.shadowRoot!.appendChild(this.editable);
+ this.appendChild(this.editableContainer);
this.codable = document.createElement("textarea", {
is: "anki-codable",
}) as Codable;
- this.shadowRoot!.appendChild(this.codable);
+ this.appendChild(this.codable);
+ this.onFocus = this.onFocus.bind(this);
+ this.onBlur = this.onBlur.bind(this);
+ this.onKey = this.onKey.bind(this);
this.onPaste = this.onPaste.bind(this);
+ this.showImageHandle = this.showImageHandle.bind(this);
}
get activeInput(): Editable | Codable {
@@ -60,7 +92,7 @@ export class EditingArea extends HTMLDivElement {
}
set fieldHTML(content: string) {
- this.activeInput.fieldHTML = content;
+ this.imageHandle.then(() => (this.activeInput.fieldHTML = content));
}
get fieldHTML(): string {
@@ -68,30 +100,29 @@ export class EditingArea extends HTMLDivElement {
}
connectedCallback(): void {
- this.addEventListener("keydown", onKey);
+ this.addEventListener("keydown", this.onKey);
this.addEventListener("keyup", onKeyUp);
this.addEventListener("input", onInput);
- this.addEventListener("focus", onFocus);
- this.addEventListener("blur", onBlur);
+ this.addEventListener("focusin", this.onFocus);
+ this.addEventListener("focusout", this.onBlur);
this.addEventListener("paste", this.onPaste);
this.addEventListener("copy", onCutOrCopy);
this.addEventListener("oncut", onCutOrCopy);
this.addEventListener("mouseup", updateActiveButtons);
-
- const baseStyleSheet = this.baseStyle.sheet as CSSStyleSheet;
- baseStyleSheet.insertRule("anki-editable {}", 0);
+ this.editable.addEventListener("click", this.showImageHandle);
}
disconnectedCallback(): void {
- this.removeEventListener("keydown", onKey);
+ this.removeEventListener("keydown", this.onKey);
this.removeEventListener("keyup", onKeyUp);
this.removeEventListener("input", onInput);
- this.removeEventListener("focus", onFocus);
- this.removeEventListener("blur", onBlur);
+ this.removeEventListener("focusin", this.onFocus);
+ this.removeEventListener("focusout", this.onBlur);
this.removeEventListener("paste", this.onPaste);
this.removeEventListener("copy", onCutOrCopy);
this.removeEventListener("oncut", onCutOrCopy);
this.removeEventListener("mouseup", updateActiveButtons);
+ this.editable.removeEventListener("click", this.showImageHandle);
}
initialize(color: string, content: string): void {
@@ -100,9 +131,7 @@ export class EditingArea extends HTMLDivElement {
}
setBaseColor(color: string): void {
- const styleSheet = this.baseStyle.sheet as CSSStyleSheet;
- const firstRule = styleSheet.cssRules[0] as CSSStyleRule;
- firstRule.style.color = color;
+ this.editableContainer.setBaseColor(color);
}
quoteFontFamily(fontFamily: string): string {
@@ -114,17 +143,15 @@ export class EditingArea extends HTMLDivElement {
}
setBaseStyling(fontFamily: string, fontSize: string, direction: string): void {
- const styleSheet = this.baseStyle.sheet as CSSStyleSheet;
- const firstRule = styleSheet.cssRules[0] as CSSStyleRule;
- firstRule.style.fontFamily = this.quoteFontFamily(fontFamily);
- firstRule.style.fontSize = fontSize;
- firstRule.style.direction = direction;
+ this.editableContainer.setBaseStyling(
+ this.quoteFontFamily(fontFamily),
+ fontSize,
+ direction
+ );
}
isRightToLeft(): boolean {
- const styleSheet = this.baseStyle.sheet as CSSStyleSheet;
- const firstRule = styleSheet.cssRules[0] as CSSStyleRule;
- return firstRule.style.direction === "rtl";
+ return this.editableContainer.isRightToLeft();
}
focus(): void {
@@ -140,25 +167,62 @@ export class EditingArea extends HTMLDivElement {
}
hasFocus(): boolean {
- return document.activeElement === this;
+ return document.activeElement?.closest(".field") === this;
}
getSelection(): Selection {
- return this.shadowRoot!.getSelection()!;
+ const root = this.activeInput.getRootNode() as Document | ShadowRoot;
+ return root.getSelection()!;
}
surroundSelection(before: string, after: string): void {
this.activeInput.surroundSelection(before, after);
}
+ onFocus(event: FocusEvent): void {
+ onFocus(event);
+ }
+
+ onBlur(event: FocusEvent): void {
+ this.resetImageHandle();
+ onBlur(event);
+ }
+
onEnter(event: KeyboardEvent): void {
this.activeInput.onEnter(event);
}
+ onKey(event: KeyboardEvent): void {
+ this.resetImageHandle();
+ onKey(event);
+ }
+
onPaste(event: ClipboardEvent): void {
+ this.resetImageHandle();
this.activeInput.onPaste(event);
}
+ resetImageHandle(): void {
+ this.imageHandle.then((imageHandle) =>
+ (imageHandle as any).$set({
+ activeImage: null,
+ })
+ );
+ }
+
+ showImageHandle(event: MouseEvent): void {
+ if (event.target instanceof HTMLImageElement) {
+ this.imageHandle.then((imageHandle) =>
+ (imageHandle as any).$set({
+ activeImage: event.target,
+ isRtl: this.isRightToLeft(),
+ })
+ );
+ } else {
+ this.resetImageHandle();
+ }
+ }
+
toggleHtmlEdit(): void {
const hadFocus = this.hasFocus();
@@ -166,6 +230,7 @@ export class EditingArea extends HTMLDivElement {
this.fieldHTML = this.codable.teardown();
this.editable.hidden = false;
} else {
+ this.resetImageHandle();
this.editable.hidden = true;
this.codable.setup(this.editable.fieldHTML);
}
diff --git a/ts/editor/editor-field.ts b/ts/editor/editor-field.ts
index 0c1cca362..08b85df71 100644
--- a/ts/editor/editor-field.ts
+++ b/ts/editor/editor-field.ts
@@ -10,6 +10,8 @@ export class EditorField extends HTMLDivElement {
constructor() {
super();
+ this.classList.add("editor-field");
+
this.labelContainer = document.createElement("div", {
is: "anki-label-container",
}) as LabelContainer;
@@ -19,6 +21,8 @@ export class EditorField extends HTMLDivElement {
is: "anki-editing-area",
}) as EditingArea;
this.appendChild(this.editingArea);
+
+ this.focusIfNotFocused = this.focusIfNotFocused.bind(this);
}
static get observedAttributes(): string[] {
@@ -29,6 +33,21 @@ export class EditorField extends HTMLDivElement {
this.setAttribute("ord", String(n));
}
+ focusIfNotFocused(): void {
+ if (!this.editingArea.hasFocus()) {
+ this.editingArea.focus();
+ this.editingArea.caretToEnd();
+ }
+ }
+
+ connectedCallback(): void {
+ this.labelContainer.addEventListener("mousedown", this.focusIfNotFocused);
+ }
+
+ disconnectedCallback(): void {
+ this.labelContainer.removeEventListener("mousedown", this.focusIfNotFocused);
+ }
+
attributeChangedCallback(name: string, _oldValue: string, newValue: string): void {
switch (name) {
case "ord":
diff --git a/ts/editor/fields.scss b/ts/editor/fields.scss
index 1d0e34920..727959aa8 100644
--- a/ts/editor/fields.scss
+++ b/ts/editor/fields.scss
@@ -10,13 +10,30 @@
#fields {
display: flex;
+ overflow-x: hidden;
flex-direction: column;
- margin: 5px;
+ margin: 3px 0;
+}
+
+.editor-field {
+ margin: 3px;
+ border-radius: 5px;
+ border: 1px solid var(--border-color);
+
+ --border-color: var(--border);
+
+ &:focus-within {
+ box-shadow: 0 0 0 3px var(--focus-shadow);
+
+ --border-color: var(--focus-border);
+ }
}
.field {
- border: 1px solid var(--border);
+ position: relative;
+
background: var(--frame-bg);
+ border-radius: 0 0 5px 5px;
&.dupe {
// this works around the background colour persisting in copy+paste
@@ -26,8 +43,16 @@
}
.fname {
- vertical-align: middle;
- padding: 0;
+ border-width: 0 0 1px;
+ border-style: dashed;
+ border-color: var(--border-color);
+ border-radius: 5px 5px 0 0;
+
+ padding: 0px 6px;
+}
+
+.fieldname {
+ user-select: none;
}
#dupes,
@@ -62,3 +87,13 @@
opacity: 0.5;
}
}
+
+@import "ts/sass/codemirror/lib/codemirror";
+@import "ts/sass/codemirror/theme/monokai";
+@import "ts/sass/codemirror/addon/fold/foldgutter";
+
+.CodeMirror {
+ height: auto;
+ border-radius: 0 0 5px 5px;
+ padding: 6px 0;
+}
diff --git a/ts/editor/focus-handlers.ts b/ts/editor/focus-handlers.ts
index f15f9df50..11c317ead 100644
--- a/ts/editor/focus-handlers.ts
+++ b/ts/editor/focus-handlers.ts
@@ -10,15 +10,12 @@ import type { EditingArea } from "./editing-area";
import { saveField } from "./change-timer";
import { bridgeCommand } from "./lib";
+import { getCurrentField } from "./helpers";
export function onFocus(evt: FocusEvent): void {
const currentField = evt.currentTarget as EditingArea;
currentField.focus();
-
- if (currentField.shadowRoot!.getSelection()!.anchorNode === null) {
- // selection is not inside editable after focusing
- currentField.caretToEnd();
- }
+ currentField.caretToEnd();
bridgeCommand(`focus:${currentField.ord}`);
fieldFocused.set(true);
@@ -26,7 +23,7 @@ export function onFocus(evt: FocusEvent): void {
export function onBlur(evt: FocusEvent): void {
const previousFocus = evt.currentTarget as EditingArea;
- const currentFieldUnchanged = previousFocus === document.activeElement;
+ const currentFieldUnchanged = previousFocus === getCurrentField();
saveField(previousFocus, currentFieldUnchanged ? "key" : "blur");
fieldFocused.set(false);
diff --git a/ts/editor/helpers.ts b/ts/editor/helpers.ts
index 12a13657e..174260152 100644
--- a/ts/editor/helpers.ts
+++ b/ts/editor/helpers.ts
@@ -5,6 +5,12 @@
@typescript-eslint/no-non-null-assertion: "off",
*/
+import type { EditingArea } from "./editing-area";
+
+export function getCurrentField(): EditingArea | null {
+ return document.activeElement?.closest(".field") ?? null;
+}
+
export function nodeIsElement(node: Node): node is Element {
return node.nodeType === Node.ELEMENT_NODE;
}
diff --git a/ts/editor/icons.ts b/ts/editor/icons.ts
index 003cdcd01..00ae6776c 100644
--- a/ts/editor/icons.ts
+++ b/ts/editor/icons.ts
@@ -33,3 +33,11 @@ export { default as xmlIcon } from "./xml.svg";
export const arrowIcon =
'';
+
+// image handle
+export { default as floatNoneIcon } from "./format-float-none.svg";
+export { default as floatLeftIcon } from "./format-float-left.svg";
+export { default as floatRightIcon } from "./format-float-right.svg";
+
+export { default as sizeActual } from "./image-size-select-actual.svg";
+export { default as sizeMinimized } from "./image-size-select-large.svg";
diff --git a/ts/editor/index.ts b/ts/editor/index.ts
index 09ec6d01f..bdb0c7f03 100644
--- a/ts/editor/index.ts
+++ b/ts/editor/index.ts
@@ -23,15 +23,18 @@ import { saveField } from "./change-timer";
import { EditorField } from "./editor-field";
import { LabelContainer } from "./label-container";
import { EditingArea } from "./editing-area";
+import { EditableContainer } from "./editable-container";
import { Editable } from "./editable";
import { Codable } from "./codable";
import { initToolbar, fieldFocused } from "./toolbar";
+import { getCurrentField } from "./helpers";
export { setNoteId, getNoteId } from "./note-id";
export { saveNow } from "./change-timer";
export { wrap, wrapIntoText } from "./wrap";
export { editorToolbar } from "./toolbar";
export { activateStickyShortcuts } from "./label-container";
+export { getCurrentField } from "./helpers";
export { components } from "./Components.svelte";
declare global {
@@ -44,6 +47,7 @@ declare global {
}
customElements.define("anki-editable", Editable);
+customElements.define("anki-editable-container", EditableContainer, { extends: "div" });
customElements.define("anki-codable", Codable, { extends: "textarea" });
customElements.define("anki-editing-area", EditingArea, { extends: "div" });
customElements.define("anki-label-container", LabelContainer, { extends: "div" });
@@ -53,12 +57,6 @@ if (isApplePlatform()) {
registerShortcut(() => bridgeCommand("paste"), "Control+Shift+V");
}
-export function getCurrentField(): EditingArea | null {
- return document.activeElement instanceof EditingArea
- ? document.activeElement
- : null;
-}
-
export function focusField(n: number): void {
const field = getEditorField(n);
diff --git a/ts/editor/label-container.ts b/ts/editor/label-container.ts
index bc6816e54..2bbdeffdf 100644
--- a/ts/editor/label-container.ts
+++ b/ts/editor/label-container.ts
@@ -32,12 +32,14 @@ export function activateStickyShortcuts(): void {
}
export class LabelContainer extends HTMLDivElement {
- sticky: HTMLSpanElement;
label: HTMLSpanElement;
+ fieldState: HTMLSpanElement;
+
+ sticky: HTMLSpanElement;
constructor() {
super();
- this.className = "d-flex justify-content-between";
+ this.className = "fname d-flex justify-content-between";
i18n.then(() => {
this.title = appendInParentheses(tr.editingToggleSticky(), "F9");
@@ -47,25 +49,36 @@ export class LabelContainer extends HTMLDivElement {
this.label.className = "fieldname";
this.appendChild(this.label);
+ this.fieldState = document.createElement("span");
+ this.fieldState.className = "field-state d-flex justify-content-between";
+ this.appendChild(this.fieldState);
+
this.sticky = document.createElement("span");
- this.sticky.className = "icon pin-icon me-1";
+ this.sticky.className = "icon pin-icon";
this.sticky.innerHTML = pinIcon;
this.sticky.hidden = true;
- this.appendChild(this.sticky);
+ this.fieldState.appendChild(this.sticky);
this.setSticky = this.setSticky.bind(this);
this.hoverIcon = this.hoverIcon.bind(this);
this.removeHoverIcon = this.removeHoverIcon.bind(this);
this.toggleSticky = this.toggleSticky.bind(this);
+ this.keepFocus = this.keepFocus.bind(this);
+ }
+
+ keepFocus(event: Event): void {
+ event.preventDefault();
}
connectedCallback(): void {
+ this.addEventListener("mousedown", this.keepFocus);
this.sticky.addEventListener("click", this.toggleSticky);
this.sticky.addEventListener("mouseenter", this.hoverIcon);
this.sticky.addEventListener("mouseleave", this.removeHoverIcon);
}
disconnectedCallback(): void {
+ this.removeEventListener("mousedown", this.keepFocus);
this.sticky.removeEventListener("click", this.toggleSticky);
this.sticky.removeEventListener("mouseenter", this.hoverIcon);
this.sticky.removeEventListener("mouseleave", this.removeHoverIcon);
diff --git a/ts/editor/wrap.ts b/ts/editor/wrap.ts
index 8b67d776b..f7c2114a6 100644
--- a/ts/editor/wrap.ts
+++ b/ts/editor/wrap.ts
@@ -5,7 +5,8 @@
@typescript-eslint/no-non-null-assertion: "off",
*/
-import { getCurrentField, setFormat } from ".";
+import { getCurrentField } from "./helpers";
+import { setFormat } from ".";
function wrappedExceptForWhitespace(text: string, front: string, back: string): string {
const match = text.match(/^(\s*)([^]*?)(\s*)$/)!;
diff --git a/ts/reviewer/reviewer.scss b/ts/reviewer/reviewer.scss
index a39fca0fa..24f24a3cf 100644
--- a/ts/reviewer/reviewer.scss
+++ b/ts/reviewer/reviewer.scss
@@ -19,7 +19,7 @@ body.nightMode {
}
img {
- max-width: 95%;
+ max-width: 100%;
max-height: 95vh;
}
diff --git a/ts/sass/_vars.scss b/ts/sass/_vars.scss
index ec8b2d1bb..bac3d4816 100644
--- a/ts/sass/_vars.scss
+++ b/ts/sass/_vars.scss
@@ -36,6 +36,8 @@
--suspended-bg: #ffffb2;
--marked-bg: #cce;
--tooltip-bg: #fcfcfc;
+ --focus-border: #0969da;
+ --focus-shadow: rgba(9 105 218 / 0.3);
}
:root[class*="night-mode"] {
@@ -73,4 +75,6 @@
--suspended-bg: #aaaa33;
--marked-bg: #77c;
--tooltip-bg: #272727;
+ --focus-border: #316dca;
+ --focus-shadow: #143d79;
}
diff --git a/ts/sass/buttons.scss b/ts/sass/buttons.scss
index c7b29f544..2246e9f6a 100644
--- a/ts/sass/buttons.scss
+++ b/ts/sass/buttons.scss
@@ -17,7 +17,7 @@
font-size: 14px;
-webkit-appearance: none;
- border-radius: 3px;
+ border-radius: 5px;
padding: 5px;
border: 1px solid var(--border);
}
@@ -37,7 +37,7 @@
box-shadow: 0 0 3px fusion-vars.$button-outline;
border: 1px solid fusion-vars.$button-border;
- border-radius: 2px;
+ border-radius: 5px;
padding: 10px;
padding-top: 3px;
padding-bottom: 3px;