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",
|
|
|
|
*/
|
|
|
|
|
2021-02-28 14:12:48 +01:00
|
|
|
import type { Editable } from "./editable";
|
2021-06-17 13:46:25 +02:00
|
|
|
import type { Codable } from "./codable";
|
2021-02-28 14:12:48 +01:00
|
|
|
|
2021-04-27 23:08:47 +02:00
|
|
|
import { updateActiveButtons } from "./toolbar";
|
2021-02-28 14:12:48 +01:00
|
|
|
import { bridgeCommand } from "./lib";
|
2021-06-30 19:55:56 +02:00
|
|
|
import { onInput, onKey, onKeyUp } from "./input-handlers";
|
|
|
|
import { onFocus, onBlur } from "./focus-handlers";
|
2021-02-28 14:12:48 +01:00
|
|
|
|
2021-02-28 14:29:41 +01:00
|
|
|
function onCutOrCopy(): void {
|
2021-02-28 14:12:48 +01:00
|
|
|
bridgeCommand("cutOrCopy");
|
|
|
|
}
|
|
|
|
|
|
|
|
export class EditingArea extends HTMLDivElement {
|
|
|
|
editable: Editable;
|
2021-06-17 13:46:25 +02:00
|
|
|
codable: Codable;
|
2021-02-28 14:12:48 +01:00
|
|
|
baseStyle: HTMLStyleElement;
|
|
|
|
|
|
|
|
constructor() {
|
|
|
|
super();
|
|
|
|
this.attachShadow({ mode: "open" });
|
2021-04-15 16:44:07 +02:00
|
|
|
this.className = "field";
|
2021-02-28 14:12:48 +01:00
|
|
|
|
2021-07-26 16:06:50 +02:00
|
|
|
if (document.documentElement.classList.contains("night-mode")) {
|
|
|
|
this.classList.add("night-mode");
|
|
|
|
}
|
|
|
|
|
2021-02-28 14:12:48 +01:00
|
|
|
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);
|
2021-06-17 13:46:25 +02:00
|
|
|
|
2021-06-17 17:18:00 +02:00
|
|
|
this.codable = document.createElement("textarea", {
|
|
|
|
is: "anki-codable",
|
|
|
|
}) as Codable;
|
2021-06-17 13:46:25 +02:00
|
|
|
this.shadowRoot!.appendChild(this.codable);
|
2021-06-17 23:12:15 +02:00
|
|
|
|
|
|
|
this.onPaste = this.onPaste.bind(this);
|
2021-02-28 14:12:48 +01:00
|
|
|
}
|
|
|
|
|
2021-06-17 21:36:56 +02:00
|
|
|
get activeInput(): Editable | Codable {
|
|
|
|
return this.codable.active ? this.codable : this.editable;
|
|
|
|
}
|
|
|
|
|
2021-02-28 14:12:48 +01:00
|
|
|
get ord(): number {
|
|
|
|
return Number(this.getAttribute("ord"));
|
|
|
|
}
|
|
|
|
|
|
|
|
set fieldHTML(content: string) {
|
2021-06-17 21:36:56 +02:00
|
|
|
this.activeInput.fieldHTML = content;
|
2021-02-28 14:12:48 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
get fieldHTML(): string {
|
2021-06-17 21:36:56 +02:00
|
|
|
return this.activeInput.fieldHTML;
|
2021-02-28 14:12:48 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
connectedCallback(): void {
|
|
|
|
this.addEventListener("keydown", onKey);
|
|
|
|
this.addEventListener("keyup", onKeyUp);
|
|
|
|
this.addEventListener("input", onInput);
|
|
|
|
this.addEventListener("focus", onFocus);
|
|
|
|
this.addEventListener("blur", onBlur);
|
2021-06-17 23:12:15 +02:00
|
|
|
this.addEventListener("paste", this.onPaste);
|
2021-02-28 14:12:48 +01:00
|
|
|
this.addEventListener("copy", onCutOrCopy);
|
|
|
|
this.addEventListener("oncut", onCutOrCopy);
|
2021-04-19 23:51:09 +02:00
|
|
|
this.addEventListener("mouseup", updateActiveButtons);
|
2021-02-28 14:12:48 +01:00
|
|
|
|
|
|
|
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);
|
2021-06-17 23:12:15 +02:00
|
|
|
this.removeEventListener("paste", this.onPaste);
|
2021-02-28 14:12:48 +01:00
|
|
|
this.removeEventListener("copy", onCutOrCopy);
|
|
|
|
this.removeEventListener("oncut", onCutOrCopy);
|
2021-04-19 23:51:09 +02:00
|
|
|
this.removeEventListener("mouseup", updateActiveButtons);
|
2021-02-28 14:12:48 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
initialize(color: string, content: string): void {
|
|
|
|
this.setBaseColor(color);
|
2021-06-24 19:15:07 +02:00
|
|
|
this.fieldHTML = content;
|
2021-02-28 14:12:48 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
setBaseColor(color: string): void {
|
|
|
|
const styleSheet = this.baseStyle.sheet as CSSStyleSheet;
|
|
|
|
const firstRule = styleSheet.cssRules[0] as CSSStyleRule;
|
|
|
|
firstRule.style.color = color;
|
|
|
|
}
|
|
|
|
|
2021-08-19 21:31:11 +02:00
|
|
|
quoteFontFamily(fontFamily: string): string {
|
|
|
|
// generic families (e.g. sans-serif) must not be quoted
|
|
|
|
if (!/^[a-z|-]+$/.test(fontFamily)) {
|
|
|
|
fontFamily = `"${fontFamily}"`;
|
|
|
|
}
|
|
|
|
return fontFamily;
|
|
|
|
}
|
|
|
|
|
2021-02-28 14:12:48 +01:00
|
|
|
setBaseStyling(fontFamily: string, fontSize: string, direction: string): void {
|
|
|
|
const styleSheet = this.baseStyle.sheet as CSSStyleSheet;
|
|
|
|
const firstRule = styleSheet.cssRules[0] as CSSStyleRule;
|
2021-08-19 21:31:11 +02:00
|
|
|
firstRule.style.fontFamily = this.quoteFontFamily(fontFamily);
|
2021-02-28 14:12:48 +01:00
|
|
|
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";
|
|
|
|
}
|
|
|
|
|
2021-06-17 21:36:56 +02:00
|
|
|
focus(): void {
|
|
|
|
this.activeInput.focus();
|
|
|
|
}
|
|
|
|
|
|
|
|
blur(): void {
|
|
|
|
this.activeInput.blur();
|
2021-02-28 14:12:48 +01:00
|
|
|
}
|
|
|
|
|
2021-06-17 21:36:56 +02:00
|
|
|
caretToEnd(): void {
|
|
|
|
this.activeInput.caretToEnd();
|
2021-02-28 14:12:48 +01:00
|
|
|
}
|
2021-06-17 17:08:33 +02:00
|
|
|
|
2021-06-17 19:08:14 +02:00
|
|
|
hasFocus(): boolean {
|
|
|
|
return document.activeElement === this;
|
|
|
|
}
|
|
|
|
|
2021-06-18 02:33:56 +02:00
|
|
|
getSelection(): Selection {
|
|
|
|
return this.shadowRoot!.getSelection()!;
|
|
|
|
}
|
|
|
|
|
|
|
|
surroundSelection(before: string, after: string): void {
|
|
|
|
this.activeInput.surroundSelection(before, after);
|
|
|
|
}
|
|
|
|
|
2021-06-17 23:12:15 +02:00
|
|
|
onEnter(event: KeyboardEvent): void {
|
|
|
|
this.activeInput.onEnter(event);
|
|
|
|
}
|
|
|
|
|
|
|
|
onPaste(event: ClipboardEvent): void {
|
|
|
|
this.activeInput.onPaste(event);
|
2021-06-17 22:02:06 +02:00
|
|
|
}
|
|
|
|
|
2021-06-17 17:08:33 +02:00
|
|
|
toggleHtmlEdit(): void {
|
2021-06-17 19:08:14 +02:00
|
|
|
const hadFocus = this.hasFocus();
|
|
|
|
|
|
|
|
if (this.codable.active) {
|
2021-06-17 21:36:56 +02:00
|
|
|
this.fieldHTML = this.codable.teardown();
|
2021-06-17 19:08:14 +02:00
|
|
|
this.editable.hidden = false;
|
|
|
|
} else {
|
|
|
|
this.editable.hidden = true;
|
2021-06-24 19:15:07 +02:00
|
|
|
this.codable.setup(this.editable.fieldHTML);
|
2021-06-17 21:36:56 +02:00
|
|
|
}
|
2021-06-17 19:08:14 +02:00
|
|
|
|
2021-06-17 21:36:56 +02:00
|
|
|
if (hadFocus) {
|
|
|
|
this.focus();
|
|
|
|
this.caretToEnd();
|
2021-06-17 17:49:50 +02:00
|
|
|
}
|
2021-06-17 17:08:33 +02:00
|
|
|
}
|
2021-06-17 22:02:06 +02:00
|
|
|
|
|
|
|
/**
|
|
|
|
* @deprecated Use focus instead
|
|
|
|
*/
|
|
|
|
focusEditable(): void {
|
|
|
|
focus();
|
|
|
|
}
|
|
|
|
/**
|
|
|
|
* @deprecated Use blur instead
|
|
|
|
*/
|
|
|
|
blurEditable(): void {
|
|
|
|
blur();
|
|
|
|
}
|
2021-02-28 14:12:48 +01:00
|
|
|
}
|