anki/ts/editor/editing-area.ts

224 lines
6.3 KiB
TypeScript
Raw Normal View History

2021-04-13 10:57:08 +02:00
// Copyright: Ankitects Pty Ltd and contributors
// License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
2021-05-06 23:33:28 +02:00
/* eslint
@typescript-eslint/no-non-null-assertion: "off",
*/
import ImageHandle from "./ImageHandle.svelte";
2021-07-21 01:32:09 +02:00
import type { EditableContainer } from "./editable-container";
import type { Editable } from "./editable";
2021-06-17 13:46:25 +02:00
import type { Codable } from "./codable";
2021-04-27 23:08:47 +02:00
import { updateActiveButtons } from "./toolbar";
import { bridgeCommand } from "./lib";
import { onInput, onKey, onKeyUp } from "./input-handlers";
import { onFocus, onBlur } from "./focus-handlers";
2021-07-21 01:32:09 +02:00
import { nightModeKey } from "components/context-keys";
function onCutOrCopy(): void {
bridgeCommand("cutOrCopy");
}
export class EditingArea extends HTMLDivElement {
imageHandle: ImageHandle;
editableContainer: EditableContainer;
editable: Editable;
2021-06-17 13:46:25 +02:00
codable: Codable;
constructor() {
super();
this.className = "field";
this.editableContainer = document.createElement("div", {
is: "anki-editable-container",
}) as EditableContainer;
this.editable = document.createElement("anki-editable") as Editable;
this.editableContainer.shadowRoot!.appendChild(this.editable);
this.appendChild(this.editableContainer);
2021-06-17 13:46:25 +02:00
2021-07-21 01:32:09 +02:00
const context = new Map();
context.set(
nightModeKey,
document.documentElement.classList.contains("night-mode")
);
this.imageHandle = new ImageHandle({
2021-07-21 01:32:09 +02:00
target: this,
anchor: this.editableContainer,
2021-07-21 14:13:56 +02:00
props: {
container: this.editable,
},
2021-07-21 01:32:09 +02:00
context,
} as any);
2021-06-17 17:18:00 +02:00
this.codable = document.createElement("textarea", {
is: "anki-codable",
}) as Codable;
this.appendChild(this.codable);
this.onPaste = this.onPaste.bind(this);
2021-07-21 01:32:09 +02:00
this.showImageHandle = this.showImageHandle.bind(this);
}
get activeInput(): Editable | Codable {
return this.codable.active ? this.codable : this.editable;
}
get ord(): number {
return Number(this.getAttribute("ord"));
}
set fieldHTML(content: string) {
this.activeInput.fieldHTML = content;
}
get fieldHTML(): string {
return this.activeInput.fieldHTML;
}
connectedCallback(): void {
2021-07-21 17:02:59 +02:00
this.addEventListener("keydown", (event) => {
this.resetImageHandle();
onKey(event);
});
this.addEventListener("keyup", onKeyUp);
this.addEventListener("input", onInput);
this.addEventListener("focusin", onFocus);
2021-07-21 17:02:59 +02:00
this.addEventListener("focusout", (event) => {
this.resetImageHandle();
onBlur(event);
});
this.addEventListener("paste", this.onPaste);
this.addEventListener("copy", onCutOrCopy);
this.addEventListener("oncut", onCutOrCopy);
this.addEventListener("mouseup", updateActiveButtons);
2021-07-21 01:32:09 +02:00
this.editable.addEventListener("click", this.showImageHandle);
}
disconnectedCallback(): void {
this.removeEventListener("keydown", onKey);
this.removeEventListener("keyup", onKeyUp);
this.removeEventListener("input", onInput);
this.removeEventListener("focusin", onFocus);
this.removeEventListener("focusout", onBlur);
this.removeEventListener("paste", this.onPaste);
this.removeEventListener("copy", onCutOrCopy);
this.removeEventListener("oncut", onCutOrCopy);
this.removeEventListener("mouseup", updateActiveButtons);
2021-07-21 01:32:09 +02:00
this.editable.removeEventListener("click", this.showImageHandle);
}
initialize(color: string, content: string): void {
this.setBaseColor(color);
this.fieldHTML = content;
}
setBaseColor(color: string): void {
this.editableContainer.setBaseColor(color);
}
quoteFontFamily(fontFamily: string): string {
// generic families (e.g. sans-serif) must not be quoted
2021-09-02 13:04:39 +02:00
if (!/^[-a-z]+$/.test(fontFamily)) {
fontFamily = `"${fontFamily}"`;
}
return fontFamily;
}
setBaseStyling(fontFamily: string, fontSize: string, direction: string): void {
this.editableContainer.setBaseStyling(
this.quoteFontFamily(fontFamily),
fontSize,
direction,
);
}
isRightToLeft(): boolean {
return this.editableContainer.isRightToLeft();
}
focus(): void {
this.activeInput.focus();
}
blur(): void {
this.activeInput.blur();
}
caretToEnd(): void {
this.activeInput.caretToEnd();
}
2021-06-17 17:08:33 +02:00
hasFocus(): boolean {
return document.activeElement?.closest(".field") === this;
}
getSelection(): Selection {
const root = this.activeInput.getRootNode() as Document | ShadowRoot;
return root.getSelection()!;
}
surroundSelection(before: string, after: string): void {
this.activeInput.surroundSelection(before, after);
}
onEnter(event: KeyboardEvent): void {
this.activeInput.onEnter(event);
}
onPaste(event: ClipboardEvent): void {
this.activeInput.onPaste(event);
}
2021-07-21 17:02:59 +02:00
resetImageHandle(): void {
(this.imageHandle as any).$set({
image: null,
imageRule: null,
});
}
2021-07-21 01:32:09 +02:00
showImageHandle(event: MouseEvent): void {
if (event.target instanceof HTMLImageElement) {
(this.imageHandle as any).$set({
image: event.target,
2021-07-21 14:13:56 +02:00
imageRule: this.editableContainer.imageRule,
2021-07-21 16:48:02 +02:00
isRtl: this.isRightToLeft(),
2021-07-21 01:32:09 +02:00
});
} else {
2021-07-21 17:02:59 +02:00
this.resetImageHandle();
2021-07-21 01:32:09 +02:00
}
}
2021-06-17 17:08:33 +02:00
toggleHtmlEdit(): void {
const hadFocus = this.hasFocus();
if (this.codable.active) {
this.fieldHTML = this.codable.teardown();
this.editable.hidden = false;
} else {
this.editable.hidden = true;
this.codable.setup(this.editable.fieldHTML);
}
if (hadFocus) {
this.focus();
this.caretToEnd();
}
2021-06-17 17:08:33 +02:00
}
/**
* @deprecated Use focus instead
*/
focusEditable(): void {
focus();
}
/**
* @deprecated Use blur instead
*/
blurEditable(): void {
blur();
}
}