mirror of
https://github.com/ankitects/anki.git
synced 2025-09-23 08:22:24 -04:00

Contains the shadow root, and references to the styles. Is ignorant of Editable. Is necessary, so our we editable.scss does not need to contain information about Codable, ImageHandle or all those other things which have nothing to do with Editable
176 lines
4.9 KiB
TypeScript
176 lines
4.9 KiB
TypeScript
// 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",
|
|
*/
|
|
|
|
import type { EditableContainer } from "./editable-container";
|
|
import type { Editable } from "./editable";
|
|
import type { Codable } from "./codable";
|
|
|
|
import { updateActiveButtons } from "./toolbar";
|
|
import { bridgeCommand } from "./lib";
|
|
import { onInput, onKey, onKeyUp } from "./input-handlers";
|
|
import { onFocus, onBlur } from "./focus-handlers";
|
|
|
|
function onCutOrCopy(): void {
|
|
bridgeCommand("cutOrCopy");
|
|
}
|
|
|
|
export class EditingArea extends HTMLDivElement {
|
|
editableContainer: EditableContainer;
|
|
editable: Editable;
|
|
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);
|
|
|
|
this.codable = document.createElement("textarea", {
|
|
is: "anki-codable",
|
|
}) as Codable;
|
|
this.appendChild(this.codable);
|
|
|
|
this.onPaste = this.onPaste.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 {
|
|
this.addEventListener("keydown", onKey);
|
|
this.addEventListener("keyup", onKeyUp);
|
|
this.addEventListener("input", onInput);
|
|
this.addEventListener("focusin", onFocus);
|
|
this.addEventListener("focusout", onBlur);
|
|
this.addEventListener("paste", this.onPaste);
|
|
this.addEventListener("copy", onCutOrCopy);
|
|
this.addEventListener("oncut", onCutOrCopy);
|
|
this.addEventListener("mouseup", updateActiveButtons);
|
|
}
|
|
|
|
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);
|
|
}
|
|
|
|
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
|
|
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();
|
|
}
|
|
|
|
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);
|
|
}
|
|
|
|
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();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @deprecated Use focus instead
|
|
*/
|
|
focusEditable(): void {
|
|
focus();
|
|
}
|
|
/**
|
|
* @deprecated Use blur instead
|
|
*/
|
|
blurEditable(): void {
|
|
blur();
|
|
}
|
|
}
|