// 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 { 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 { 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"); } 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); this.editable = document.createElement("anki-editable") as Editable; this.shadowRoot!.appendChild(this.editable); this.codable = document.createElement("textarea", { is: "anki-codable", }) as Codable; this.shadowRoot!.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("focus", onFocus); this.addEventListener("blur", 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); } disconnectedCallback(): void { this.removeEventListener("keydown", onKey); this.removeEventListener("keyup", onKeyUp); this.removeEventListener("input", onInput); this.removeEventListener("focus", onFocus); this.removeEventListener("blur", 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 { const styleSheet = this.baseStyle.sheet as CSSStyleSheet; const firstRule = styleSheet.cssRules[0] as CSSStyleRule; firstRule.style.color = 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 { 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; } isRightToLeft(): boolean { const styleSheet = this.baseStyle.sheet as CSSStyleSheet; const firstRule = styleSheet.cssRules[0] as CSSStyleRule; return firstRule.style.direction === "rtl"; } focus(): void { this.activeInput.focus(); } blur(): void { this.activeInput.blur(); } caretToEnd(): void { this.activeInput.caretToEnd(); } hasFocus(): boolean { return document.activeElement === this; } getSelection(): Selection { return this.shadowRoot!.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(); } }