From 1db71805d37b6072b3641bea73b197cf3829e9e5 Mon Sep 17 00:00:00 2001 From: Henrik Giesel Date: Thu, 28 Jan 2021 15:42:37 +0100 Subject: [PATCH 01/24] Create a rough draft of the editor web component --- qt/aqt/data/web/js/editor.ts | 108 ++++++++++++++++++++++------------- 1 file changed, 68 insertions(+), 40 deletions(-) diff --git a/qt/aqt/data/web/js/editor.ts b/qt/aqt/data/web/js/editor.ts index 0791fd51b..952347063 100644 --- a/qt/aqt/data/web/js/editor.ts +++ b/qt/aqt/data/web/js/editor.ts @@ -334,7 +334,7 @@ function onBlur(): void { } } -function fieldContainsInlineContent(field: HTMLDivElement): boolean { +function fieldContainsInlineContent(field: Element): boolean { if (field.childNodes.length === 0) { // for now, for all practical purposes, empty fields are in block mode return false; @@ -437,41 +437,73 @@ function onCutOrCopy(): boolean { return true; } -function createField( - index: number, - label: string, - color: string, - content: string -): [HTMLDivElement, HTMLDivElement] { - const name = document.createElement("div"); - name.id = `name${index}`; - name.className = "fname"; +class EditingArea extends HTMLElement { + connectedCallback() { + this.setAttribute("contenteditable", "true"); + } +} - const fieldname = document.createElement("span"); - fieldname.className = "fieldname"; - fieldname.innerText = label; - name.appendChild(fieldname); +customElements.define("editing-area", EditingArea); - const field = document.createElement("div"); - field.id = `f${index}`; - field.className = "field"; - field.setAttribute("contenteditable", "true"); - field.style.color = color; - field.addEventListener("keydown", onKey); - field.addEventListener("keyup", onKeyUp); - field.addEventListener("input", onInput); - field.addEventListener("focus", onFocus); - field.addEventListener("blur", onBlur); - field.addEventListener("paste", onPaste); - field.addEventListener("copy", onCutOrCopy); - field.addEventListener("oncut", onCutOrCopy); - field.innerHTML = content; +class EditorField extends HTMLElement { + labelContainer: HTMLDivElement + label: HTMLSpanElement + editingContainer: HTMLDivElement + editingArea: EditingArea + editingShadow: ShadowRoot - if (fieldContainsInlineContent(field)) { - field.appendChild(document.createElement("br")); + connectedCallback() { + this.labelContainer = this.appendChild(document.createElement("div")); + this.labelContainer.className = "fname"; + + this.label = this.labelContainer.appendChild(document.createElement("span")); + this.label.className = "fieldname"; + + this.editingContainer = this.appendChild(document.createElement("div")); + this.editingContainer.className = "field"; + this.editingContainer.addEventListener("keydown", onKey); + this.editingContainer.addEventListener("keyup", onKeyUp); + this.editingContainer.addEventListener("input", onInput); + this.editingContainer.addEventListener("focus", onFocus); + this.editingContainer.addEventListener("blur", onBlur); + this.editingContainer.addEventListener("paste", onPaste); + this.editingContainer.addEventListener("copy", onCutOrCopy); + this.editingContainer.addEventListener("oncut", onCutOrCopy); + + const editingShadow = this.editingContainer.attachShadow({ mode: "open" }); + this.editingArea = editingShadow.appendChild(document.createElement("editing-area")) as EditingArea; } - return [name, field]; + initialize(index: number, label: string, color: string, content: string): void { + this.labelContainer.id = `name${index}`; + this.label.innerText = label; + + this.editingContainer.id = `f${index}`; + + this.editingArea.style.color = color; + this.editingArea.innerHTML = content; + if (fieldContainsInlineContent(this.editingArea)) { + this.editingArea.appendChild(document.createElement("br")); + } + } + + fieldContent(): string { + return this.editingArea.innerHTML; + } +} + +customElements.define("editor-field", EditorField); + +function adjustFieldAmount(amount: number): void { + const fieldsContainer = document.getElementById("fields"); + + while (fieldsContainer.childElementCount < amount) { + fieldsContainer.appendChild(document.createElement("editor-field")) + } + + while (fieldsContainer.childElementCount > amount) { + fieldsContainer.removeChild(fieldsContainer.lastElementChild); + } } function setFields(fields: [string, string][]): void { @@ -481,17 +513,13 @@ function setFields(fields: [string, string][]): void { .getComputedStyle(document.documentElement) .getPropertyValue("--text-fg"); - const elements = fields.flatMap(([name, fieldcontent], index: number) => - createField(index, name, color, fieldcontent) - ); + adjustFieldAmount(fields.length); const fieldsContainer = document.getElementById("fields"); - // can be replaced with ParentNode.replaceChildren in Chrome 86+ - while (fieldsContainer.firstChild) { - fieldsContainer.removeChild(fieldsContainer.firstChild); - } - for (const element of elements) { - fieldsContainer.appendChild(element); + const children = [...fieldsContainer.children] + for (const [index, child] of children.entries()) { + const [name, fieldContent] = fields[index]; + (child as EditorField).initialize(index, name, color, fieldContent); } maybeDisableButtons(); From 7dc4b8818cb95a0c7043a3b2cd357551bc09bc88 Mon Sep 17 00:00:00 2001 From: Henrik Giesel Date: Thu, 28 Jan 2021 17:16:10 +0100 Subject: [PATCH 02/24] Isolate styling of editing-area into new scss file --- qt/aqt/data/web/css/editing-area.scss | 11 +++ qt/aqt/data/web/css/editor.scss | 8 -- qt/aqt/data/web/js/editor.ts | 123 +++++++++++++++++--------- 3 files changed, 90 insertions(+), 52 deletions(-) create mode 100644 qt/aqt/data/web/css/editing-area.scss diff --git a/qt/aqt/data/web/css/editing-area.scss b/qt/aqt/data/web/css/editing-area.scss new file mode 100644 index 000000000..c171e820c --- /dev/null +++ b/qt/aqt/data/web/css/editing-area.scss @@ -0,0 +1,11 @@ +editing-area { + display: block; + overflow-wrap: break-word; + overflow: auto; + padding: 5px; + + &:empty::after { + content: "\a"; + white-space: pre; + } +} diff --git a/qt/aqt/data/web/css/editor.scss b/qt/aqt/data/web/css/editor.scss index af714c161..d9e90e1d6 100644 --- a/qt/aqt/data/web/css/editor.scss +++ b/qt/aqt/data/web/css/editor.scss @@ -22,14 +22,6 @@ .field { border: 1px solid var(--border); background: var(--frame-bg); - padding: 5px; - overflow-wrap: break-word; - overflow: auto; - - &:empty::after { - content: "\A"; - white-space: pre; - } } .fname { diff --git a/qt/aqt/data/web/js/editor.ts b/qt/aqt/data/web/js/editor.ts index 952347063..2c156f5ad 100644 --- a/qt/aqt/data/web/js/editor.ts +++ b/qt/aqt/data/web/js/editor.ts @@ -356,14 +356,9 @@ function saveField(type: "blur" | "key"): void { return; } - const fieldText = - fieldContainsInlineContent(currentField) && - currentField.innerHTML.endsWith("
") - ? // trim trailing
- currentField.innerHTML.slice(0, -4) - : currentField.innerHTML; - - pycmd(`${type}:${currentFieldOrdinal()}:${currentNoteId}:${fieldText}`); + pycmd( + `${type}:${currentFieldOrdinal()}:${currentNoteId}:${currentField.fieldHTML}` + ); } function currentFieldOrdinal(): string { @@ -441,64 +436,104 @@ class EditingArea extends HTMLElement { connectedCallback() { this.setAttribute("contenteditable", "true"); } + + initialize(color: string): void { + this.style.color = color; + } + + set fieldHTML(content: string) { + this.innerHTML = content; + + if (fieldContainsInlineContent(this)) { + this.appendChild(document.createElement("br")); + } + } + + get fieldHTML(): string { + return fieldContainsInlineContent(this) && this.innerHTML.endsWith("
") + ? this.innerHTML.slice(0, -4) // trim trailing
+ : this.innerHTML; + } } customElements.define("editing-area", EditingArea); -class EditorField extends HTMLElement { - labelContainer: HTMLDivElement - label: HTMLSpanElement - editingContainer: HTMLDivElement - editingArea: EditingArea - editingShadow: ShadowRoot +class EditingContainer extends HTMLDivElement { + editingArea: EditingArea; - connectedCallback() { + connectedCallback(): void { + this.className = "field"; + + this.addEventListener("keydown", onKey); + this.addEventListener("keyup", onKeyUp); + this.addEventListener("input", onInput); + this.addEventListener("focus", onFocus); + this.addEventListener("blur", onBlur); + this.addEventListener("paste", onPaste); + this.addEventListener("copy", onCutOrCopy); + this.addEventListener("oncut", onCutOrCopy); + + const editingShadow = this.attachShadow({ mode: "open" }); + + const style = editingShadow.appendChild(document.createElement("link")); + style.setAttribute("rel", "stylesheet"); + style.setAttribute("href", "./_anki/css/editing-area.css"); + + this.editingArea = editingShadow.appendChild( + document.createElement("editing-area") + ) as EditingArea; + } + + initialize(index: number, color: string, content: string): void { + this.id = `f${index}`; + this.editingArea.initialize(color); + this.editingArea.fieldHTML = content; + } + + set fieldHTML(content: string) { + this.editingArea.fieldHTML = content; + } + + get fieldHTML(): string { + return this.editingArea.fieldHTML; + } +} + +customElements.define("editing-container", EditingContainer, { extends: "div" }); + +class EditorField extends HTMLDivElement { + labelContainer: HTMLDivElement; + label: HTMLSpanElement; + editingContainer: EditingContainer; + + connectedCallback(): void { this.labelContainer = this.appendChild(document.createElement("div")); this.labelContainer.className = "fname"; this.label = this.labelContainer.appendChild(document.createElement("span")); this.label.className = "fieldname"; - this.editingContainer = this.appendChild(document.createElement("div")); - this.editingContainer.className = "field"; - this.editingContainer.addEventListener("keydown", onKey); - this.editingContainer.addEventListener("keyup", onKeyUp); - this.editingContainer.addEventListener("input", onInput); - this.editingContainer.addEventListener("focus", onFocus); - this.editingContainer.addEventListener("blur", onBlur); - this.editingContainer.addEventListener("paste", onPaste); - this.editingContainer.addEventListener("copy", onCutOrCopy); - this.editingContainer.addEventListener("oncut", onCutOrCopy); - - const editingShadow = this.editingContainer.attachShadow({ mode: "open" }); - this.editingArea = editingShadow.appendChild(document.createElement("editing-area")) as EditingArea; + this.editingContainer = this.appendChild( + document.createElement("div", { is: "editing-container" }) + ) as EditingContainer; } initialize(index: number, label: string, color: string, content: string): void { this.labelContainer.id = `name${index}`; this.label.innerText = label; - - this.editingContainer.id = `f${index}`; - - this.editingArea.style.color = color; - this.editingArea.innerHTML = content; - if (fieldContainsInlineContent(this.editingArea)) { - this.editingArea.appendChild(document.createElement("br")); - } - } - - fieldContent(): string { - return this.editingArea.innerHTML; + this.editingContainer.initialize(index, color, content); } } -customElements.define("editor-field", EditorField); +customElements.define("editor-field", EditorField, { extends: "div" }); function adjustFieldAmount(amount: number): void { const fieldsContainer = document.getElementById("fields"); while (fieldsContainer.childElementCount < amount) { - fieldsContainer.appendChild(document.createElement("editor-field")) + fieldsContainer.appendChild( + document.createElement("div", { is: "editor-field" }) + ); } while (fieldsContainer.childElementCount > amount) { @@ -516,10 +551,10 @@ function setFields(fields: [string, string][]): void { adjustFieldAmount(fields.length); const fieldsContainer = document.getElementById("fields"); - const children = [...fieldsContainer.children] + const children = [...fieldsContainer.children] as EditorField[]; for (const [index, child] of children.entries()) { const [name, fieldContent] = fields[index]; - (child as EditorField).initialize(index, name, color, fieldContent); + child.initialize(index, name, color, fieldContent); } maybeDisableButtons(); From e0d1450ce07527148a9a666590ec64be63bb538a Mon Sep 17 00:00:00 2001 From: Henrik Giesel Date: Thu, 28 Jan 2021 17:24:07 +0100 Subject: [PATCH 03/24] Rename fieldContainsInlineContent to containsInlineContent to reflect new usage --- qt/aqt/data/web/js/editor.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/qt/aqt/data/web/js/editor.ts b/qt/aqt/data/web/js/editor.ts index 2c156f5ad..125b9ed5f 100644 --- a/qt/aqt/data/web/js/editor.ts +++ b/qt/aqt/data/web/js/editor.ts @@ -334,7 +334,7 @@ function onBlur(): void { } } -function fieldContainsInlineContent(field: Element): boolean { +function containsInlineContent(field: Element): boolean { if (field.childNodes.length === 0) { // for now, for all practical purposes, empty fields are in block mode return false; @@ -444,13 +444,13 @@ class EditingArea extends HTMLElement { set fieldHTML(content: string) { this.innerHTML = content; - if (fieldContainsInlineContent(this)) { + if (containsInlineContent(this)) { this.appendChild(document.createElement("br")); } } get fieldHTML(): string { - return fieldContainsInlineContent(this) && this.innerHTML.endsWith("
") + return containsInlineContent(this) && this.innerHTML.endsWith("
") ? this.innerHTML.slice(0, -4) // trim trailing
: this.innerHTML; } From db0c776210ae8134e89a6289ef8e735ca48cb749 Mon Sep 17 00:00:00 2001 From: Henrik Giesel Date: Thu, 28 Jan 2021 17:43:56 +0100 Subject: [PATCH 04/24] Fix inListItem for shadow roots --- qt/aqt/data/web/js/editor.ts | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/qt/aqt/data/web/js/editor.ts b/qt/aqt/data/web/js/editor.ts index 125b9ed5f..f9072fe44 100644 --- a/qt/aqt/data/web/js/editor.ts +++ b/qt/aqt/data/web/js/editor.ts @@ -1,7 +1,7 @@ /* Copyright: Ankitects Pty Ltd and contributors * License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html */ -let currentField = null; +let currentField: EditingContainer | null = null; let changeTimer = null; let currentNoteId = null; @@ -172,7 +172,7 @@ function nodeIsInline(node: Node): boolean { } function inListItem(): boolean { - const anchor = window.getSelection().anchorNode; + const anchor = currentField.getSelection().anchorNode; let inList = false; let n = nodeIsElement(anchor) ? anchor : anchor.parentElement; @@ -254,7 +254,7 @@ function clearChangeTimer(): void { } function onFocus(evt: FocusEvent): void { - const elem = evt.currentTarget as HTMLElement; + const elem = evt.currentTarget as EditingContainer; if (currentField === elem) { // anki window refocused; current element unchanged return; @@ -293,7 +293,7 @@ function focusField(n: number): void { function focusIfField(x: number, y: number): boolean { const elements = document.elementsFromPoint(x, y); for (let i = 0; i < elements.length; i++) { - let elem = elements[i] as HTMLElement; + let elem = elements[i] as EditingContainer; if (elem.classList.contains("field")) { elem.focus(); // the focus event may not fire if the window is not active, so make sure @@ -459,6 +459,7 @@ class EditingArea extends HTMLElement { customElements.define("editing-area", EditingArea); class EditingContainer extends HTMLDivElement { + editingShadow: ShadowRoot; editingArea: EditingArea; connectedCallback(): void { @@ -473,13 +474,13 @@ class EditingContainer extends HTMLDivElement { this.addEventListener("copy", onCutOrCopy); this.addEventListener("oncut", onCutOrCopy); - const editingShadow = this.attachShadow({ mode: "open" }); + this.editingShadow = this.attachShadow({ mode: "open" }); - const style = editingShadow.appendChild(document.createElement("link")); + const style = this.editingShadow.appendChild(document.createElement("link")); style.setAttribute("rel", "stylesheet"); style.setAttribute("href", "./_anki/css/editing-area.css"); - this.editingArea = editingShadow.appendChild( + this.editingArea = this.editingShadow.appendChild( document.createElement("editing-area") ) as EditingArea; } @@ -490,6 +491,10 @@ class EditingContainer extends HTMLDivElement { this.editingArea.fieldHTML = content; } + getSelection(): Selection { + return this.editingShadow.getSelection(); + } + set fieldHTML(content: string) { this.editingArea.fieldHTML = content; } From bec709c7a9d0ea3a1dfa719e519d4a07b239b080 Mon Sep 17 00:00:00 2001 From: Henrik Giesel Date: Thu, 28 Jan 2021 18:37:38 +0100 Subject: [PATCH 05/24] Update setFonts and setBackgrounds --- qt/aqt/data/web/js/editor.ts | 83 ++++++++++++++++++++---------------- 1 file changed, 47 insertions(+), 36 deletions(-) diff --git a/qt/aqt/data/web/js/editor.ts b/qt/aqt/data/web/js/editor.ts index f9072fe44..47ac209a2 100644 --- a/qt/aqt/data/web/js/editor.ts +++ b/qt/aqt/data/web/js/editor.ts @@ -60,28 +60,23 @@ function onKey(evt: KeyboardEvent): void { if (evt.code === "Enter" && !inListItem()) { evt.preventDefault(); document.execCommand("insertLineBreak"); - return; } - // fix Ctrl+right/left handling in RTL fields - if (currentField.dir === "rtl") { - const selection = window.getSelection(); - let granularity = "character"; - let alter = "move"; - if (evt.ctrlKey) { - granularity = "word"; - } - if (evt.shiftKey) { - alter = "extend"; - } - if (evt.code === "ArrowRight") { - selection.modify(alter, "right", granularity); - evt.preventDefault(); - return; - } else if (evt.code === "ArrowLeft") { - selection.modify(alter, "left", granularity); - evt.preventDefault(); - return; + // // fix Ctrl+right/left handling in RTL fields + if (currentField.isRightToLeft()) { + const selection = currentField.getSelection(); + const granularity = evt.ctrlKey ? "word" : "character"; + const alter = evt.shiftKey ? "extend" : "move"; + + switch (evt.code) { + case "ArrowRight": + selection.modify(alter, "right", granularity); + evt.preventDefault(); + return; + case "ArrowLeft": + selection.modify(alter, "left", granularity); + evt.preventDefault(); + return; } } @@ -441,6 +436,12 @@ class EditingArea extends HTMLElement { this.style.color = color; } + setBaseStyling(fontFamily: string, fontSize: string, direction: string): void { + this.style.fontFamily = fontFamily; + this.style.fontSize = fontSize; + this.style.direction = direction; + } + set fieldHTML(content: string) { this.innerHTML = content; @@ -491,6 +492,14 @@ class EditingContainer extends HTMLDivElement { this.editingArea.fieldHTML = content; } + setBaseStyling(fontFamily: string, fontSize: string, direction: string): void { + this.editingArea.setBaseStyling(fontFamily, fontSize, direction); + } + + isRightToLeft(): boolean { + return this.editingArea.style.direction === "rtl"; + } + getSelection(): Selection { return this.editingShadow.getSelection(); } @@ -528,6 +537,10 @@ class EditorField extends HTMLDivElement { this.label.innerText = label; this.editingContainer.initialize(index, color, content); } + + setBaseStyling(fontFamily: string, fontSize: string, direction: string): void { + this.editingContainer.setBaseStyling(fontFamily, fontSize, direction); + } } customElements.define("editor-field", EditorField, { extends: "div" }); @@ -546,6 +559,15 @@ function adjustFieldAmount(amount: number): void { } } +function forField(values: T[], func: (value: T, field: EditorField, index: number) => void): void { + const fieldContainer = document.getElementById("fields"); + const fields = [...fieldContainer.children] as EditorField[]; + + for (const [index, field] of fields.entries()) { + func(values[index], field, index) + } +} + function setFields(fields: [string, string][]): void { // webengine will include the variable after enter+backspace // if we don't convert it to a literal colour @@ -554,30 +576,19 @@ function setFields(fields: [string, string][]): void { .getPropertyValue("--text-fg"); adjustFieldAmount(fields.length); - - const fieldsContainer = document.getElementById("fields"); - const children = [...fieldsContainer.children] as EditorField[]; - for (const [index, child] of children.entries()) { - const [name, fieldContent] = fields[index]; - child.initialize(index, name, color, fieldContent); - } + forField(fields, ([name, fieldContent], field, index) => field.initialize(index, name, color, fieldContent)); maybeDisableButtons(); } function setBackgrounds(cols: "dupe"[]) { - for (let i = 0; i < cols.length; i++) { - const element = document.querySelector(`#f${i}`); - element.classList.toggle("dupe", cols[i] === "dupe"); - } + forField(cols, (value, field) => field.classList.toggle("dupe", value === "dupe")); } function setFonts(fonts: [string, number, boolean][]): void { - for (let i = 0; i < fonts.length; i++) { - const n = $(`#f${i}`); - n.css("font-family", fonts[i][0]).css("font-size", fonts[i][1]); - n[0].dir = fonts[i][2] ? "rtl" : "ltr"; - } + forField(fonts, ([fontFamily, fontSize, isRtl], field) => { + field.setBaseStyling(fontFamily, `${fontSize}px`, isRtl ? "rtl" : "ltr"); + }); } function setNoteId(id: number): void { From aed38de228f4a115a997fd343ceaebdf87c47979 Mon Sep 17 00:00:00 2001 From: Henrik Giesel Date: Thu, 28 Jan 2021 19:13:39 +0100 Subject: [PATCH 06/24] Fix dupes, but also make sticky, and centered to draw more attention --- qt/aqt/data/web/css/editor.scss | 28 +++++++++++++++++++++++----- qt/aqt/data/web/js/editor.ts | 28 +++++++++++++++------------- qt/aqt/editor.py | 19 ++++++++----------- 3 files changed, 46 insertions(+), 29 deletions(-) diff --git a/qt/aqt/data/web/css/editor.scss b/qt/aqt/data/web/css/editor.scss index d9e90e1d6..96cb770e6 100644 --- a/qt/aqt/data/web/css/editor.scss +++ b/qt/aqt/data/web/css/editor.scss @@ -1,6 +1,10 @@ /* Copyright: Ankitects Pty Ltd and contributors * License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html */ +html { + background: var(--bg-color); +} + #fields { display: flex; flex-direction: column; @@ -22,6 +26,10 @@ .field { border: 1px solid var(--border); background: var(--frame-bg); + + &.dupe { + background: var(--flag1-bg); + } } .fname { @@ -46,6 +54,8 @@ body { top: 0; left: 0; padding: 2px; + + background: var(--bg-color); } .topbuts > * { @@ -113,12 +123,20 @@ button.highlighted { } } -.dupe { - background: var(--flag1-bg); -} +#dupes { + position: sticky; + bottom: 0; -#dupes a { - color: var(--link); + text-align: center; + background-color: var(--bg-color); + + &.is-inactive { + display: none; + } + + a { + color: var(--link); + } } .drawing { diff --git a/qt/aqt/data/web/js/editor.ts b/qt/aqt/data/web/js/editor.ts index 47ac209a2..8955c21d9 100644 --- a/qt/aqt/data/web/js/editor.ts +++ b/qt/aqt/data/web/js/editor.ts @@ -559,12 +559,15 @@ function adjustFieldAmount(amount: number): void { } } -function forField(values: T[], func: (value: T, field: EditorField, index: number) => void): void { +function forField( + values: T[], + func: (value: T, field: EditorField, index: number) => void +): void { const fieldContainer = document.getElementById("fields"); const fields = [...fieldContainer.children] as EditorField[]; for (const [index, field] of fields.entries()) { - func(values[index], field, index) + func(values[index], field, index); } } @@ -576,13 +579,20 @@ function setFields(fields: [string, string][]): void { .getPropertyValue("--text-fg"); adjustFieldAmount(fields.length); - forField(fields, ([name, fieldContent], field, index) => field.initialize(index, name, color, fieldContent)); + forField(fields, ([name, fieldContent], field, index) => + field.initialize(index, name, color, fieldContent) + ); maybeDisableButtons(); } -function setBackgrounds(cols: "dupe"[]) { - forField(cols, (value, field) => field.classList.toggle("dupe", value === "dupe")); +function setBackgrounds(cols: ("dupe" | "")[]) { + forField(cols, (value, field) => + field.editingContainer.classList.toggle("dupe", value === "dupe") + ); + document + .querySelector("#dupes") + .classList.toggle("is-inactive", !cols.includes("dupe")); } function setFonts(fonts: [string, number, boolean][]): void { @@ -595,14 +605,6 @@ function setNoteId(id: number): void { currentNoteId = id; } -function showDupes(): void { - $("#dupes").show(); -} - -function hideDupes(): void { - $("#dupes").hide(); -} - let pasteHTML = function ( html: string, internal: boolean, diff --git a/qt/aqt/editor.py b/qt/aqt/editor.py index 4335b97bb..f99296143 100644 --- a/qt/aqt/editor.py +++ b/qt/aqt/editor.py @@ -73,8 +73,9 @@ audio = ( _html = """
@@ -82,11 +83,9 @@ html { background: %s; }
-
- """ @@ -219,7 +218,7 @@ class Editor: bgcol = self.mw.app.palette().window().color().name() # type: ignore # then load page self.web.stdHtml( - _html % (bgcol, bgcol, topbuts, tr(TR.EDITING_SHOW_DUPLICATES)), + _html % (bgcol, topbuts, tr(TR.EDITING_SHOW_DUPLICATES)), css=["css/editor.css"], js=["js/vendor/jquery.min.js", "js/editor.js"], context=self, @@ -534,9 +533,7 @@ class Editor: err = self.note.dupeOrEmpty() if err == 2: cols[0] = "dupe" - self.web.eval("showDupes();") - else: - self.web.eval("hideDupes();") + self.web.eval("setBackgrounds(%s);" % json.dumps(cols)) def showDupes(self): From 08a6f8f02f437c0040842748a51787971fcb9715 Mon Sep 17 00:00:00 2001 From: Henrik Giesel Date: Thu, 28 Jan 2021 19:52:49 +0100 Subject: [PATCH 07/24] Use new focusEditingArea and blurEditingArea to delegate to editing area --- qt/aqt/data/web/js/editor.ts | 23 ++++++++++++++++------- 1 file changed, 16 insertions(+), 7 deletions(-) diff --git a/qt/aqt/data/web/js/editor.ts b/qt/aqt/data/web/js/editor.ts index 8955c21d9..86b70f261 100644 --- a/qt/aqt/data/web/js/editor.ts +++ b/qt/aqt/data/web/js/editor.ts @@ -19,7 +19,7 @@ String.prototype.format = function (...args: string[]): string { }; function setFGButton(col: string): void { - $("#forecolor")[0].style.backgroundColor = col; + document.getElementById("forecolor").style.backgroundColor = col; } function saveNow(keepFocus: boolean): void { @@ -33,7 +33,7 @@ function saveNow(keepFocus: boolean): void { saveField("key"); } else { // triggers onBlur, which saves - currentField.blur(); + currentField.blurEditingArea(); } } @@ -52,7 +52,7 @@ interface Selection { function onKey(evt: KeyboardEvent): void { // esc clears focus, allowing dialog to close if (evt.code === "Escape") { - currentField.blur(); + currentField.blurEditingArea(); return; } @@ -279,10 +279,11 @@ function onFocus(evt: FocusEvent): void { } function focusField(n: number): void { - if (n === null) { - return; + const field = document.getElementById(`f${n}`) as EditingContainer; + + if (field) { + field.focusEditingArea(); } - $(`#f${n}`).focus(); } function focusIfField(x: number, y: number): boolean { @@ -290,7 +291,7 @@ function focusIfField(x: number, y: number): boolean { for (let i = 0; i < elements.length; i++) { let elem = elements[i] as EditingContainer; if (elem.classList.contains("field")) { - elem.focus(); + elem.focusEditingArea(); // the focus event may not fire if the window is not active, so make sure // the current field is set currentField = elem; @@ -504,6 +505,14 @@ class EditingContainer extends HTMLDivElement { return this.editingShadow.getSelection(); } + focusEditingArea(): void { + this.editingArea.focus(); + } + + blurEditingArea(): void { + this.editingArea.blur(); + } + set fieldHTML(content: string) { this.editingArea.fieldHTML = content; } From 8a525d3643bf10e14e6fc7ddde1d64d94461fd51 Mon Sep 17 00:00:00 2001 From: Henrik Giesel Date: Thu, 28 Jan 2021 20:19:04 +0100 Subject: [PATCH 08/24] Use currentField.getSelection instead of window.getSelection --- qt/aqt/data/web/js/editor.ts | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/qt/aqt/data/web/js/editor.ts b/qt/aqt/data/web/js/editor.ts index 86b70f261..4bb6cb9e4 100644 --- a/qt/aqt/data/web/js/editor.ts +++ b/qt/aqt/data/web/js/editor.ts @@ -86,7 +86,7 @@ function onKey(evt: KeyboardEvent): void { function onKeyUp(evt: KeyboardEvent): void { // Avoid div element on remove if (evt.code === "Enter" || evt.code === "Backspace") { - const anchor = window.getSelection().anchorNode; + const anchor = currentField.getSelection().anchorNode; if ( nodeIsElement(anchor) && @@ -190,7 +190,7 @@ function insertNewline(): void { // differently. so in such cases we note the height has not // changed and insert an extra newline. - const r = window.getSelection().getRangeAt(0); + const r = currentField.getSelection().getRangeAt(0); if (!r.collapsed) { // delete any currently selected text first, making // sure the delete is undoable @@ -206,7 +206,7 @@ function insertNewline(): void { // is the cursor in an environment that respects whitespace? function inPreEnvironment(): boolean { - const anchor = window.getSelection().anchorNode; + const anchor = currentField.getSelection().anchorNode; const n = nodeIsElement(anchor) ? anchor : anchor.parentElement; return window.getComputedStyle(n).whiteSpace.startsWith("pre"); @@ -310,7 +310,7 @@ function caretToEnd(): void { const r = document.createRange(); r.selectNodeContents(currentField); r.collapse(false); - const s = document.getSelection(); + const s = currentField.getSelection(); s.removeAllRanges(); s.addRange(r); } @@ -401,7 +401,7 @@ function wrapIntoText(front: string, back: string): void { } function wrapInternal(front: string, back: string, plainText: boolean): void { - const s = window.getSelection(); + const s = currentField.getSelection(); let r = s.getRangeAt(0); const content = r.cloneContents(); const span = document.createElement("span"); From af3753948a6bcbb83eb295833a8a7b19afe5200b Mon Sep 17 00:00:00 2001 From: Henrik Giesel Date: Thu, 28 Jan 2021 21:38:55 +0100 Subject: [PATCH 09/24] Allow stylesheet of note type take effect on editor fields --- qt/aqt/data/web/js/editor.ts | 60 ++++++++++++++++++++++++++++++------ qt/aqt/editor.py | 3 +- 2 files changed, 52 insertions(+), 11 deletions(-) diff --git a/qt/aqt/data/web/js/editor.ts b/qt/aqt/data/web/js/editor.ts index 4bb6cb9e4..50c69e622 100644 --- a/qt/aqt/data/web/js/editor.ts +++ b/qt/aqt/data/web/js/editor.ts @@ -437,12 +437,6 @@ class EditingArea extends HTMLElement { this.style.color = color; } - setBaseStyling(fontFamily: string, fontSize: string, direction: string): void { - this.style.fontFamily = fontFamily; - this.style.fontSize = fontSize; - this.style.direction = direction; - } - set fieldHTML(content: string) { this.innerHTML = content; @@ -464,6 +458,9 @@ class EditingContainer extends HTMLDivElement { editingShadow: ShadowRoot; editingArea: EditingArea; + baseStylesheet: CSSStyleSheet; + userStyle: HTMLStyleElement; + connectedCallback(): void { this.className = "field"; @@ -478,9 +475,30 @@ class EditingContainer extends HTMLDivElement { this.editingShadow = this.attachShadow({ mode: "open" }); - const style = this.editingShadow.appendChild(document.createElement("link")); - style.setAttribute("rel", "stylesheet"); - style.setAttribute("href", "./_anki/css/editing-area.css"); + const rootStyle = this.editingShadow.appendChild( + document.createElement("link") + ); + rootStyle.setAttribute("rel", "stylesheet"); + rootStyle.setAttribute("href", "./_anki/css/editing-area.css"); + + const baseStyle = this.editingShadow.appendChild( + document.createElement("style") + ); + baseStyle.setAttribute("rel", "stylesheet"); + this.baseStylesheet = baseStyle.sheet as CSSStyleSheet; + this.baseStylesheet.insertRule( + `editing-area { + font-family: initial; + font-size: initial; + direction: initial; + }`, + 0 + ); + + this.userStyle = this.editingShadow.appendChild( + document.createElement("style") + ); + this.userStyle.setAttribute("rel", "stylesheet"); this.editingArea = this.editingShadow.appendChild( document.createElement("editing-area") @@ -494,7 +512,15 @@ class EditingContainer extends HTMLDivElement { } setBaseStyling(fontFamily: string, fontSize: string, direction: string): void { - this.editingArea.setBaseStyling(fontFamily, fontSize, direction); + const firstRule = this.baseStylesheet.cssRules[0] as CSSStyleRule; + firstRule.style.fontFamily = fontFamily; + firstRule.style.fontSize = fontSize; + firstRule.style.direction = direction; + } + + setUserStyling(css: HTMLStyleElement): void { + this.userStyle.parentNode.replaceChild(css, this.userStyle); + this.userStyle = css; } isRightToLeft(): boolean { @@ -550,6 +576,10 @@ class EditorField extends HTMLDivElement { setBaseStyling(fontFamily: string, fontSize: string, direction: string): void { this.editingContainer.setBaseStyling(fontFamily, fontSize, direction); } + + setUserStyling(css: HTMLStyleElement): void { + this.editingContainer.setUserStyling(css); + } } customElements.define("editor-field", EditorField, { extends: "div" }); @@ -610,6 +640,16 @@ function setFonts(fonts: [string, number, boolean][]): void { }); } +function setUserStyling(css: string): void { + const userStyle = document.createElement("style"); + userStyle.setAttribute("rel", "stylesheet"); + userStyle.innerHTML = css; + + forField([], (_, field) => { + field.setUserStyling(userStyle.cloneNode(true) as HTMLStyleElement); + }); +} + function setNoteId(id: number): void { currentNoteId = id; } diff --git a/qt/aqt/editor.py b/qt/aqt/editor.py index f99296143..cad272020 100644 --- a/qt/aqt/editor.py +++ b/qt/aqt/editor.py @@ -504,9 +504,10 @@ class Editor: self.web.setFocus() gui_hooks.editor_did_load_note(self) - js = "setFields(%s); setFonts(%s); focusField(%s); setNoteId(%s)" % ( + js = "setFields(%s); setFonts(%s); setUserStyling(%s); focusField(%s); setNoteId(%s)" % ( json.dumps(data), json.dumps(self.fonts()), + json.dumps(self.note.model()["css"]), json.dumps(focusTo), json.dumps(self.note.id), ) From 3eade1c64d18308e3108d43396846ab56d9d84fb Mon Sep 17 00:00:00 2001 From: Henrik Giesel Date: Thu, 28 Jan 2021 22:01:34 +0100 Subject: [PATCH 10/24] Make the text color part of the base style in editor --- qt/aqt/data/web/js/editor.ts | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/qt/aqt/data/web/js/editor.ts b/qt/aqt/data/web/js/editor.ts index 50c69e622..b7ab7306d 100644 --- a/qt/aqt/data/web/js/editor.ts +++ b/qt/aqt/data/web/js/editor.ts @@ -433,8 +433,8 @@ class EditingArea extends HTMLElement { this.setAttribute("contenteditable", "true"); } - initialize(color: string): void { - this.style.color = color; + initialize(index: number): void { + this.className = `editor-field-${index}`; } set fieldHTML(content: string) { @@ -491,6 +491,7 @@ class EditingContainer extends HTMLDivElement { font-family: initial; font-size: initial; direction: initial; + color: initial; }`, 0 ); @@ -507,10 +508,16 @@ class EditingContainer extends HTMLDivElement { initialize(index: number, color: string, content: string): void { this.id = `f${index}`; - this.editingArea.initialize(color); + this.editingArea.initialize(index); + this.setBaseColor(color); this.editingArea.fieldHTML = content; } + setBaseColor(color: string): void { + const firstRule = this.baseStylesheet.cssRules[0] as CSSStyleRule; + firstRule.style.color = color; + } + setBaseStyling(fontFamily: string, fontSize: string, direction: string): void { const firstRule = this.baseStylesheet.cssRules[0] as CSSStyleRule; firstRule.style.fontFamily = fontFamily; From a620bf91a8d8ec5c365431147e3c35cd098f45de Mon Sep 17 00:00:00 2001 From: Henrik Giesel Date: Thu, 28 Jan 2021 22:23:06 +0100 Subject: [PATCH 11/24] Make button highlight white in nightMode --- qt/aqt/data/web/css/editor.scss | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/qt/aqt/data/web/css/editor.scss b/qt/aqt/data/web/css/editor.scss index 96cb770e6..6a78d8ea1 100644 --- a/qt/aqt/data/web/css/editor.scss +++ b/qt/aqt/data/web/css/editor.scss @@ -119,7 +119,11 @@ button.highlighted { } #topbutsright & { - border-bottom: 3px solid #000; + border-bottom: 3px solid black; + } + + .nightMode #topbutsright & { + border-bottom: 3px solid white; } } From 6e907fd8b6b551a23bd997eea70f60cb9fd716dd Mon Sep 17 00:00:00 2001 From: Henrik Giesel Date: Thu, 28 Jan 2021 22:34:18 +0100 Subject: [PATCH 12/24] Move styling attributes from editor to editing-area that are for editing area --- qt/aqt/data/web/css/editing-area.scss | 8 ++++++++ qt/aqt/data/web/css/editor.scss | 13 +------------ 2 files changed, 9 insertions(+), 12 deletions(-) diff --git a/qt/aqt/data/web/css/editing-area.scss b/qt/aqt/data/web/css/editing-area.scss index c171e820c..b8c6c4b9f 100644 --- a/qt/aqt/data/web/css/editing-area.scss +++ b/qt/aqt/data/web/css/editing-area.scss @@ -9,3 +9,11 @@ editing-area { white-space: pre; } } + +img.drawing { + zoom: 50%; + + .nightMode & { + filter: unquote("invert() hue-rotate(180deg)"); + } +} diff --git a/qt/aqt/data/web/css/editor.scss b/qt/aqt/data/web/css/editor.scss index 6a78d8ea1..75f50b000 100644 --- a/qt/aqt/data/web/css/editor.scss +++ b/qt/aqt/data/web/css/editor.scss @@ -10,7 +10,7 @@ html { flex-direction: column; margin: 5px; - & > * { + & > *, & > * > * { margin: 1px 0; &:first-child { @@ -37,10 +37,6 @@ html { padding: 0; } -img { - max-width: 90%; -} - body { margin: 0; } @@ -142,10 +138,3 @@ button.highlighted { color: var(--link); } } - -.drawing { - zoom: 50%; -} -.nightMode img.drawing { - filter: unquote("invert() hue-rotate(180deg)"); -} From 594fc9bebdee7bc40930aad0e0d4b570df2d1453 Mon Sep 17 00:00:00 2001 From: Henrik Giesel Date: Fri, 29 Jan 2021 00:43:45 +0100 Subject: [PATCH 13/24] Fix focus change on tab --- qt/aqt/data/web/js/editor.ts | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/qt/aqt/data/web/js/editor.ts b/qt/aqt/data/web/js/editor.ts index b7ab7306d..a1d2ab0f9 100644 --- a/qt/aqt/data/web/js/editor.ts +++ b/qt/aqt/data/web/js/editor.ts @@ -2,8 +2,8 @@ * License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html */ let currentField: EditingContainer | null = null; -let changeTimer = null; -let currentNoteId = null; +let changeTimer: number | null = null; +let currentNoteId: number | null = null; declare interface String { format(...args: string[]): string; @@ -254,6 +254,7 @@ function onFocus(evt: FocusEvent): void { // anki window refocused; current element unchanged return; } + elem.focusEditingArea(); currentField = elem; pycmd(`focus:${currentFieldOrdinal()}`); enableButtons(); @@ -307,12 +308,12 @@ function onPaste(): void { } function caretToEnd(): void { - const r = document.createRange(); - r.selectNodeContents(currentField); - r.collapse(false); - const s = currentField.getSelection(); - s.removeAllRanges(); - s.addRange(r); + const range = document.createRange(); + range.selectNodeContents(currentField.editingArea); + range.collapse(false); + const selection = currentField.getSelection(); + selection.removeAllRanges(); + selection.addRange(range); } function onBlur(): void { From fe87f986d349baa1dffa6a3c09fc9ad71e974983 Mon Sep 17 00:00:00 2001 From: Henrik Giesel Date: Fri, 29 Jan 2021 13:36:48 +0100 Subject: [PATCH 14/24] Remove user styling in editor again for now --- qt/aqt/data/web/js/editor.ts | 25 ------------------------- qt/aqt/editor.py | 3 +-- 2 files changed, 1 insertion(+), 27 deletions(-) diff --git a/qt/aqt/data/web/js/editor.ts b/qt/aqt/data/web/js/editor.ts index a1d2ab0f9..c62f1b898 100644 --- a/qt/aqt/data/web/js/editor.ts +++ b/qt/aqt/data/web/js/editor.ts @@ -460,7 +460,6 @@ class EditingContainer extends HTMLDivElement { editingArea: EditingArea; baseStylesheet: CSSStyleSheet; - userStyle: HTMLStyleElement; connectedCallback(): void { this.className = "field"; @@ -497,11 +496,6 @@ class EditingContainer extends HTMLDivElement { 0 ); - this.userStyle = this.editingShadow.appendChild( - document.createElement("style") - ); - this.userStyle.setAttribute("rel", "stylesheet"); - this.editingArea = this.editingShadow.appendChild( document.createElement("editing-area") ) as EditingArea; @@ -526,11 +520,6 @@ class EditingContainer extends HTMLDivElement { firstRule.style.direction = direction; } - setUserStyling(css: HTMLStyleElement): void { - this.userStyle.parentNode.replaceChild(css, this.userStyle); - this.userStyle = css; - } - isRightToLeft(): boolean { return this.editingArea.style.direction === "rtl"; } @@ -584,10 +573,6 @@ class EditorField extends HTMLDivElement { setBaseStyling(fontFamily: string, fontSize: string, direction: string): void { this.editingContainer.setBaseStyling(fontFamily, fontSize, direction); } - - setUserStyling(css: HTMLStyleElement): void { - this.editingContainer.setUserStyling(css); - } } customElements.define("editor-field", EditorField, { extends: "div" }); @@ -648,16 +633,6 @@ function setFonts(fonts: [string, number, boolean][]): void { }); } -function setUserStyling(css: string): void { - const userStyle = document.createElement("style"); - userStyle.setAttribute("rel", "stylesheet"); - userStyle.innerHTML = css; - - forField([], (_, field) => { - field.setUserStyling(userStyle.cloneNode(true) as HTMLStyleElement); - }); -} - function setNoteId(id: number): void { currentNoteId = id; } diff --git a/qt/aqt/editor.py b/qt/aqt/editor.py index cad272020..f99296143 100644 --- a/qt/aqt/editor.py +++ b/qt/aqt/editor.py @@ -504,10 +504,9 @@ class Editor: self.web.setFocus() gui_hooks.editor_did_load_note(self) - js = "setFields(%s); setFonts(%s); setUserStyling(%s); focusField(%s); setNoteId(%s)" % ( + js = "setFields(%s); setFonts(%s); focusField(%s); setNoteId(%s)" % ( json.dumps(data), json.dumps(self.fonts()), - json.dumps(self.note.model()["css"]), json.dumps(focusTo), json.dumps(self.note.id), ) From 0beefc06997e04af4a6c2a965de4f7cfb8a643de Mon Sep 17 00:00:00 2001 From: Henrik Giesel Date: Fri, 29 Jan 2021 14:00:33 +0100 Subject: [PATCH 15/24] Move setting of index to connectedCallback --- qt/aqt/data/web/js/editor.ts | 34 ++++++++++++++++------------------ 1 file changed, 16 insertions(+), 18 deletions(-) diff --git a/qt/aqt/data/web/js/editor.ts b/qt/aqt/data/web/js/editor.ts index c62f1b898..618fb862e 100644 --- a/qt/aqt/data/web/js/editor.ts +++ b/qt/aqt/data/web/js/editor.ts @@ -434,10 +434,6 @@ class EditingArea extends HTMLElement { this.setAttribute("contenteditable", "true"); } - initialize(index: number): void { - this.className = `editor-field-${index}`; - } - set fieldHTML(content: string) { this.innerHTML = content; @@ -496,14 +492,12 @@ class EditingContainer extends HTMLDivElement { 0 ); - this.editingArea = this.editingShadow.appendChild( - document.createElement("editing-area") - ) as EditingArea; + this.editingArea = document.createElement("editing-area") as EditingArea; + this.editingArea.id = `editor-field-${this.id.match(/\d+$/)[0]}`; + this.editingShadow.appendChild(this.editingArea); } - initialize(index: number, color: string, content: string): void { - this.id = `f${index}`; - this.editingArea.initialize(index); + initialize(color: string, content: string): void { this.setBaseColor(color); this.editingArea.fieldHTML = content; } @@ -553,21 +547,25 @@ class EditorField extends HTMLDivElement { editingContainer: EditingContainer; connectedCallback(): void { + const index = Array.prototype.indexOf.call(this.parentNode.children, this); + this.labelContainer = this.appendChild(document.createElement("div")); this.labelContainer.className = "fname"; + this.labelContainer.id = `name${index}`; this.label = this.labelContainer.appendChild(document.createElement("span")); this.label.className = "fieldname"; - this.editingContainer = this.appendChild( - document.createElement("div", { is: "editing-container" }) - ) as EditingContainer; + this.editingContainer = document.createElement("div", { + is: "editing-container", + }) as EditingContainer; + this.editingContainer.id = `f${index}`; + this.appendChild(this.editingContainer); } - initialize(index: number, label: string, color: string, content: string): void { - this.labelContainer.id = `name${index}`; + initialize(label: string, color: string, content: string): void { this.label.innerText = label; - this.editingContainer.initialize(index, color, content); + this.editingContainer.initialize(color, content); } setBaseStyling(fontFamily: string, fontSize: string, direction: string): void { @@ -611,8 +609,8 @@ function setFields(fields: [string, string][]): void { .getPropertyValue("--text-fg"); adjustFieldAmount(fields.length); - forField(fields, ([name, fieldContent], field, index) => - field.initialize(index, name, color, fieldContent) + forField(fields, ([name, fieldContent], field) => + field.initialize(name, color, fieldContent) ); maybeDisableButtons(); From 982372beae72b8c0a64e76d15ed2edd75f32fed8 Mon Sep 17 00:00:00 2001 From: Henrik Giesel Date: Fri, 29 Jan 2021 15:50:34 +0100 Subject: [PATCH 16/24] Put HTML initialization into web component constructor * disconnectedCallback should remove event listeners and free other resources * attributes belong to connectedCallback --- qt/aqt/data/web/js/editor.ts | 80 ++++++++++++++++++++++-------------- 1 file changed, 49 insertions(+), 31 deletions(-) diff --git a/qt/aqt/data/web/js/editor.ts b/qt/aqt/data/web/js/editor.ts index 618fb862e..0b5986856 100644 --- a/qt/aqt/data/web/js/editor.ts +++ b/qt/aqt/data/web/js/editor.ts @@ -431,7 +431,7 @@ function onCutOrCopy(): boolean { class EditingArea extends HTMLElement { connectedCallback() { - this.setAttribute("contenteditable", "true"); + this.setAttribute("contenteditable", ""); } set fieldHTML(content: string) { @@ -452,14 +452,28 @@ class EditingArea extends HTMLElement { customElements.define("editing-area", EditingArea); class EditingContainer extends HTMLDivElement { - editingShadow: ShadowRoot; editingArea: EditingArea; + baseStyle: HTMLStyleElement; - baseStylesheet: CSSStyleSheet; - - connectedCallback(): void { + constructor() { + super(); + this.attachShadow({ mode: "open" }); this.className = "field"; + const rootStyle = document.createElement("link"); + rootStyle.setAttribute("rel", "stylesheet"); + rootStyle.setAttribute("href", "./_anki/css/editing-area.css"); + this.shadowRoot.appendChild(rootStyle) + + this.baseStyle = document.createElement("style"); + this.baseStyle.setAttribute("rel", "stylesheet"); + this.shadowRoot.appendChild(this.baseStyle) + + this.editingArea = document.createElement("editing-area") as EditingArea; + this.shadowRoot.appendChild(this.editingArea); + } + + connectedCallback(): void { this.addEventListener("keydown", onKey); this.addEventListener("keyup", onKeyUp); this.addEventListener("input", onInput); @@ -469,20 +483,8 @@ class EditingContainer extends HTMLDivElement { this.addEventListener("copy", onCutOrCopy); this.addEventListener("oncut", onCutOrCopy); - this.editingShadow = this.attachShadow({ mode: "open" }); - - const rootStyle = this.editingShadow.appendChild( - document.createElement("link") - ); - rootStyle.setAttribute("rel", "stylesheet"); - rootStyle.setAttribute("href", "./_anki/css/editing-area.css"); - - const baseStyle = this.editingShadow.appendChild( - document.createElement("style") - ); - baseStyle.setAttribute("rel", "stylesheet"); - this.baseStylesheet = baseStyle.sheet as CSSStyleSheet; - this.baseStylesheet.insertRule( + const baseStyleSheet = this.baseStyle.sheet as CSSStyleSheet; + baseStyleSheet.insertRule( `editing-area { font-family: initial; font-size: initial; @@ -492,9 +494,18 @@ class EditingContainer extends HTMLDivElement { 0 ); - this.editingArea = document.createElement("editing-area") as EditingArea; this.editingArea.id = `editor-field-${this.id.match(/\d+$/)[0]}`; - this.editingShadow.appendChild(this.editingArea); + } + + 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", onPaste); + this.removeEventListener("copy", onCutOrCopy); + this.removeEventListener("oncut", onCutOrCopy); } initialize(color: string, content: string): void { @@ -503,12 +514,14 @@ class EditingContainer extends HTMLDivElement { } setBaseColor(color: string): void { - const firstRule = this.baseStylesheet.cssRules[0] as CSSStyleRule; + const styleSheet = this.baseStyle.sheet as CSSStyleSheet; + const firstRule = styleSheet.cssRules[0] as CSSStyleRule; firstRule.style.color = color; } setBaseStyling(fontFamily: string, fontSize: string, direction: string): void { - const firstRule = this.baseStylesheet.cssRules[0] as CSSStyleRule; + const styleSheet = this.baseStyle.sheet as CSSStyleSheet; + const firstRule = styleSheet.cssRules[0] as CSSStyleRule; firstRule.style.fontFamily = fontFamily; firstRule.style.fontSize = fontSize; firstRule.style.direction = direction; @@ -519,7 +532,7 @@ class EditingContainer extends HTMLDivElement { } getSelection(): Selection { - return this.editingShadow.getSelection(); + return this.shadowRoot.getSelection(); } focusEditingArea(): void { @@ -546,23 +559,28 @@ class EditorField extends HTMLDivElement { label: HTMLSpanElement; editingContainer: EditingContainer; - connectedCallback(): void { - const index = Array.prototype.indexOf.call(this.parentNode.children, this); - - this.labelContainer = this.appendChild(document.createElement("div")); + constructor() { + super(); + this.labelContainer = document.createElement("div"); this.labelContainer.className = "fname"; - this.labelContainer.id = `name${index}`; + this.appendChild(this.labelContainer) - this.label = this.labelContainer.appendChild(document.createElement("span")); + this.label = document.createElement("span"); this.label.className = "fieldname"; + this.labelContainer.appendChild(this.label); this.editingContainer = document.createElement("div", { is: "editing-container", }) as EditingContainer; - this.editingContainer.id = `f${index}`; this.appendChild(this.editingContainer); } + connectedCallback(): void { + const index = Array.prototype.indexOf.call(this.parentNode.children, this); + this.labelContainer.id = `name${index}`; + this.editingContainer.id = `f${index}`; + } + initialize(label: string, color: string, content: string): void { this.label.innerText = label; this.editingContainer.initialize(color, content); From 32ee8635775dcfb767a1026998e67b87904b67ce Mon Sep 17 00:00:00 2001 From: Henrik Giesel Date: Fri, 29 Jan 2021 17:41:27 +0100 Subject: [PATCH 17/24] Remove checking for class names for instance checks --- qt/aqt/data/web/js/editor.ts | 68 +++++++++++++++++++++++++----------- 1 file changed, 48 insertions(+), 20 deletions(-) diff --git a/qt/aqt/data/web/js/editor.ts b/qt/aqt/data/web/js/editor.ts index 0b5986856..f606773b0 100644 --- a/qt/aqt/data/web/js/editor.ts +++ b/qt/aqt/data/web/js/editor.ts @@ -91,7 +91,7 @@ function onKeyUp(evt: KeyboardEvent): void { if ( nodeIsElement(anchor) && anchor.tagName === "DIV" && - !anchor.classList.contains("field") && + !(anchor instanceof EditingContainer) && anchor.childElementCount === 1 && anchor.children[0].tagName === "BR" ) { @@ -256,7 +256,7 @@ function onFocus(evt: FocusEvent): void { } elem.focusEditingArea(); currentField = elem; - pycmd(`focus:${currentFieldOrdinal()}`); + pycmd(`focus:${currentField.ord}`); enableButtons(); // do this twice so that there's no flicker on newer versions caretToEnd(); @@ -291,7 +291,7 @@ function focusIfField(x: number, y: number): boolean { const elements = document.elementsFromPoint(x, y); for (let i = 0; i < elements.length; i++) { let elem = elements[i] as EditingContainer; - if (elem.classList.contains("field")) { + if (elem instanceof EditingContainer) { elem.focusEditingArea(); // the focus event may not fire if the window is not active, so make sure // the current field is set @@ -354,14 +354,10 @@ function saveField(type: "blur" | "key"): void { } pycmd( - `${type}:${currentFieldOrdinal()}:${currentNoteId}:${currentField.fieldHTML}` + `${type}:${currentField.ord}:${currentNoteId}:${currentField.fieldHTML}` ); } -function currentFieldOrdinal(): string { - return currentField.id.substring(1); -} - function wrappedExceptForWhitespace(text: string, front: string, back: string): string { const match = text.match(/^(\s*)([^]*?)(\s*)$/); return match[1] + front + match[2] + back + match[3]; @@ -385,10 +381,10 @@ function enableButtons(): void { // disable the buttons if a field is not currently focused function maybeDisableButtons(): void { - if (!document.activeElement || document.activeElement.className !== "field") { - disableButtons(); - } else { + if (document.activeElement instanceof EditingContainer) { enableButtons(); + } else { + disableButtons(); } } @@ -434,6 +430,16 @@ class EditingArea extends HTMLElement { this.setAttribute("contenteditable", ""); } + static get observedAttributes(): string[] { + return ['ord']; + } + + attributeChangedCallback(name: string, _oldValue: string, newValue: string): void { + switch (name) { + case "ord": this.id = `editor-field-${newValue}`; + } + } + set fieldHTML(content: string) { this.innerHTML = content; @@ -473,6 +479,21 @@ class EditingContainer extends HTMLDivElement { this.shadowRoot.appendChild(this.editingArea); } + static get observedAttributes(): string[] { + return ['ord']; + } + + attributeChangedCallback(name: string, _oldValue: string, newValue: string): void { + switch (name) { + case "ord": + this.id = `f${newValue}`; + } + } + + get ord(): number { + return Number(this.getAttribute("ord")); + } + connectedCallback(): void { this.addEventListener("keydown", onKey); this.addEventListener("keyup", onKeyUp); @@ -493,8 +514,6 @@ class EditingContainer extends HTMLDivElement { }`, 0 ); - - this.editingArea.id = `editor-field-${this.id.match(/\d+$/)[0]}`; } disconnectedCallback(): void { @@ -575,10 +594,19 @@ class EditorField extends HTMLDivElement { this.appendChild(this.editingContainer); } - connectedCallback(): void { - const index = Array.prototype.indexOf.call(this.parentNode.children, this); - this.labelContainer.id = `name${index}`; - this.editingContainer.id = `f${index}`; + static get observedAttributes(): string[] { + return ['ord']; + } + + attributeChangedCallback(name: string, _oldValue: string, newValue: string): void { + switch (name) { + case "ord": + this.editingContainer.setAttribute("ord", newValue); + } + } + + set ord(n: number) { + this.setAttribute("ord", String(n)); } initialize(label: string, color: string, content: string): void { @@ -597,9 +625,9 @@ function adjustFieldAmount(amount: number): void { const fieldsContainer = document.getElementById("fields"); while (fieldsContainer.childElementCount < amount) { - fieldsContainer.appendChild( - document.createElement("div", { is: "editor-field" }) - ); + const newField = document.createElement("div", { is: "editor-field" }) as EditorField; + newField.ord = fieldsContainer.childElementCount; + fieldsContainer.appendChild(newField); } while (fieldsContainer.childElementCount > amount) { From 5e67e706fbdc9c441f2616296af959e3975af55c Mon Sep 17 00:00:00 2001 From: Henrik Giesel Date: Fri, 29 Jan 2021 17:51:26 +0100 Subject: [PATCH 18/24] No need to set initial values for editing area base CSS --- qt/aqt/data/web/js/editor.ts | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) diff --git a/qt/aqt/data/web/js/editor.ts b/qt/aqt/data/web/js/editor.ts index f606773b0..24ffb13dd 100644 --- a/qt/aqt/data/web/js/editor.ts +++ b/qt/aqt/data/web/js/editor.ts @@ -505,15 +505,7 @@ class EditingContainer extends HTMLDivElement { this.addEventListener("oncut", onCutOrCopy); const baseStyleSheet = this.baseStyle.sheet as CSSStyleSheet; - baseStyleSheet.insertRule( - `editing-area { - font-family: initial; - font-size: initial; - direction: initial; - color: initial; - }`, - 0 - ); + baseStyleSheet.insertRule("editing-area {}", 0); } disconnectedCallback(): void { From 79dc0ecf86f9ec6a679c1f976262e0633e4148f7 Mon Sep 17 00:00:00 2001 From: Henrik Giesel Date: Fri, 29 Jan 2021 18:07:31 +0100 Subject: [PATCH 19/24] Remove explicit ids, as they are not necessary anymore --- qt/aqt/data/web/js/editor.ts | 17 ----------------- 1 file changed, 17 deletions(-) diff --git a/qt/aqt/data/web/js/editor.ts b/qt/aqt/data/web/js/editor.ts index 24ffb13dd..1b172ac05 100644 --- a/qt/aqt/data/web/js/editor.ts +++ b/qt/aqt/data/web/js/editor.ts @@ -430,16 +430,6 @@ class EditingArea extends HTMLElement { this.setAttribute("contenteditable", ""); } - static get observedAttributes(): string[] { - return ['ord']; - } - - attributeChangedCallback(name: string, _oldValue: string, newValue: string): void { - switch (name) { - case "ord": this.id = `editor-field-${newValue}`; - } - } - set fieldHTML(content: string) { this.innerHTML = content; @@ -483,13 +473,6 @@ class EditingContainer extends HTMLDivElement { return ['ord']; } - attributeChangedCallback(name: string, _oldValue: string, newValue: string): void { - switch (name) { - case "ord": - this.id = `f${newValue}`; - } - } - get ord(): number { return Number(this.getAttribute("ord")); } From 3559834bc979eb32ff043b1b9b0506784739690c Mon Sep 17 00:00:00 2001 From: Henrik Giesel Date: Fri, 29 Jan 2021 19:34:44 +0100 Subject: [PATCH 20/24] Make forEditorField more cheaper to execute by avoiding casting to Array --- qt/aqt/data/web/js/editor.ts | 37 ++++++++++++++++++------------------ 1 file changed, 18 insertions(+), 19 deletions(-) diff --git a/qt/aqt/data/web/js/editor.ts b/qt/aqt/data/web/js/editor.ts index 1b172ac05..abd27b5f7 100644 --- a/qt/aqt/data/web/js/editor.ts +++ b/qt/aqt/data/web/js/editor.ts @@ -353,9 +353,7 @@ function saveField(type: "blur" | "key"): void { return; } - pycmd( - `${type}:${currentField.ord}:${currentNoteId}:${currentField.fieldHTML}` - ); + pycmd(`${type}:${currentField.ord}:${currentNoteId}:${currentField.fieldHTML}`); } function wrappedExceptForWhitespace(text: string, front: string, back: string): string { @@ -459,18 +457,18 @@ class EditingContainer extends HTMLDivElement { const rootStyle = document.createElement("link"); rootStyle.setAttribute("rel", "stylesheet"); rootStyle.setAttribute("href", "./_anki/css/editing-area.css"); - this.shadowRoot.appendChild(rootStyle) + this.shadowRoot.appendChild(rootStyle); this.baseStyle = document.createElement("style"); this.baseStyle.setAttribute("rel", "stylesheet"); - this.shadowRoot.appendChild(this.baseStyle) + this.shadowRoot.appendChild(this.baseStyle); this.editingArea = document.createElement("editing-area") as EditingArea; this.shadowRoot.appendChild(this.editingArea); } static get observedAttributes(): string[] { - return ['ord']; + return ["ord"]; } get ord(): number { @@ -557,7 +555,7 @@ class EditorField extends HTMLDivElement { super(); this.labelContainer = document.createElement("div"); this.labelContainer.className = "fname"; - this.appendChild(this.labelContainer) + this.appendChild(this.labelContainer); this.label = document.createElement("span"); this.label.className = "fieldname"; @@ -570,7 +568,7 @@ class EditorField extends HTMLDivElement { } static get observedAttributes(): string[] { - return ['ord']; + return ["ord"]; } attributeChangedCallback(name: string, _oldValue: string, newValue: string): void { @@ -600,7 +598,9 @@ function adjustFieldAmount(amount: number): void { const fieldsContainer = document.getElementById("fields"); while (fieldsContainer.childElementCount < amount) { - const newField = document.createElement("div", { is: "editor-field" }) as EditorField; + const newField = document.createElement("div", { + is: "editor-field", + }) as EditorField; newField.ord = fieldsContainer.childElementCount; fieldsContainer.appendChild(newField); } @@ -610,15 +610,14 @@ function adjustFieldAmount(amount: number): void { } } -function forField( +function forEditorField( values: T[], - func: (value: T, field: EditorField, index: number) => void + func: (field: EditorField, value: T) => void ): void { - const fieldContainer = document.getElementById("fields"); - const fields = [...fieldContainer.children] as EditorField[]; - - for (const [index, field] of fields.entries()) { - func(values[index], field, index); + const fields = document.getElementById("fields").children; + for (let i = 0; i < fields.length; i++) { + const field = fields[i] as EditorField; + func(field, values[i]); } } @@ -630,7 +629,7 @@ function setFields(fields: [string, string][]): void { .getPropertyValue("--text-fg"); adjustFieldAmount(fields.length); - forField(fields, ([name, fieldContent], field) => + forEditorField(fields, (field, [name, fieldContent]) => field.initialize(name, color, fieldContent) ); @@ -638,7 +637,7 @@ function setFields(fields: [string, string][]): void { } function setBackgrounds(cols: ("dupe" | "")[]) { - forField(cols, (value, field) => + forEditorField(cols, (field, value) => field.editingContainer.classList.toggle("dupe", value === "dupe") ); document @@ -647,7 +646,7 @@ function setBackgrounds(cols: ("dupe" | "")[]) { } function setFonts(fonts: [string, number, boolean][]): void { - forField(fonts, ([fontFamily, fontSize, isRtl], field) => { + forEditorField(fonts, (field, [fontFamily, fontSize, isRtl]) => { field.setBaseStyling(fontFamily, `${fontSize}px`, isRtl ? "rtl" : "ltr"); }); } From aa924ac82157620884564d1b5b0b488d6c26e97f Mon Sep 17 00:00:00 2001 From: Henrik Giesel Date: Fri, 29 Jan 2021 19:48:17 +0100 Subject: [PATCH 21/24] Add semicolon in js message --- qt/aqt/editor.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/qt/aqt/editor.py b/qt/aqt/editor.py index f99296143..4f7d980ad 100644 --- a/qt/aqt/editor.py +++ b/qt/aqt/editor.py @@ -504,7 +504,7 @@ class Editor: self.web.setFocus() gui_hooks.editor_did_load_note(self) - js = "setFields(%s); setFonts(%s); focusField(%s); setNoteId(%s)" % ( + js = "setFields(%s); setFonts(%s); focusField(%s); setNoteId(%s);" % ( json.dumps(data), json.dumps(self.fonts()), json.dumps(focusTo), From e37fd2e091041cf642c522590f7ffccdd2b786ce Mon Sep 17 00:00:00 2001 From: Henrik Giesel Date: Fri, 29 Jan 2021 20:11:00 +0100 Subject: [PATCH 22/24] Remove unnecessarily observed attribute --- qt/aqt/data/web/js/editor.ts | 4 ---- 1 file changed, 4 deletions(-) diff --git a/qt/aqt/data/web/js/editor.ts b/qt/aqt/data/web/js/editor.ts index abd27b5f7..7b49b174f 100644 --- a/qt/aqt/data/web/js/editor.ts +++ b/qt/aqt/data/web/js/editor.ts @@ -467,10 +467,6 @@ class EditingContainer extends HTMLDivElement { this.shadowRoot.appendChild(this.editingArea); } - static get observedAttributes(): string[] { - return ["ord"]; - } - get ord(): number { return Number(this.getAttribute("ord")); } From 2e72de4af012c90bae3e10636b747fc5132537cc Mon Sep 17 00:00:00 2001 From: Henrik Giesel Date: Fri, 29 Jan 2021 20:13:02 +0100 Subject: [PATCH 23/24] Reorder methods / properties --- qt/aqt/data/web/js/editor.ts | 32 ++++++++++++++++---------------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/qt/aqt/data/web/js/editor.ts b/qt/aqt/data/web/js/editor.ts index 7b49b174f..97c24d37c 100644 --- a/qt/aqt/data/web/js/editor.ts +++ b/qt/aqt/data/web/js/editor.ts @@ -424,10 +424,6 @@ function onCutOrCopy(): boolean { } class EditingArea extends HTMLElement { - connectedCallback() { - this.setAttribute("contenteditable", ""); - } - set fieldHTML(content: string) { this.innerHTML = content; @@ -441,6 +437,10 @@ class EditingArea extends HTMLElement { ? this.innerHTML.slice(0, -4) // trim trailing
: this.innerHTML; } + + connectedCallback() { + this.setAttribute("contenteditable", ""); + } } customElements.define("editing-area", EditingArea); @@ -471,6 +471,14 @@ class EditingContainer extends HTMLDivElement { return Number(this.getAttribute("ord")); } + set fieldHTML(content: string) { + this.editingArea.fieldHTML = content; + } + + get fieldHTML(): string { + return this.editingArea.fieldHTML; + } + connectedCallback(): void { this.addEventListener("keydown", onKey); this.addEventListener("keyup", onKeyUp); @@ -530,14 +538,6 @@ class EditingContainer extends HTMLDivElement { blurEditingArea(): void { this.editingArea.blur(); } - - set fieldHTML(content: string) { - this.editingArea.fieldHTML = content; - } - - get fieldHTML(): string { - return this.editingArea.fieldHTML; - } } customElements.define("editing-container", EditingContainer, { extends: "div" }); @@ -567,6 +567,10 @@ class EditorField extends HTMLDivElement { return ["ord"]; } + set ord(n: number) { + this.setAttribute("ord", String(n)); + } + attributeChangedCallback(name: string, _oldValue: string, newValue: string): void { switch (name) { case "ord": @@ -574,10 +578,6 @@ class EditorField extends HTMLDivElement { } } - set ord(n: number) { - this.setAttribute("ord", String(n)); - } - initialize(label: string, color: string, content: string): void { this.label.innerText = label; this.editingContainer.initialize(color, content); From 61346cf1f70ececf7dafe0e8aa7f73571524b989 Mon Sep 17 00:00:00 2001 From: Henrik Giesel Date: Fri, 29 Jan 2021 20:32:21 +0100 Subject: [PATCH 24/24] Rename editingContainer -> editingArea; editingArea -> editable * Custom elements are now namespaces with `anki-` * The element names are inspired by summernote, which have the same naming scheme of "editingArea > editable" --- .../css/{editing-area.scss => editable.scss} | 2 +- qt/aqt/data/web/js/editor.ts | 82 +++++++++---------- 2 files changed, 42 insertions(+), 42 deletions(-) rename qt/aqt/data/web/css/{editing-area.scss => editable.scss} (94%) diff --git a/qt/aqt/data/web/css/editing-area.scss b/qt/aqt/data/web/css/editable.scss similarity index 94% rename from qt/aqt/data/web/css/editing-area.scss rename to qt/aqt/data/web/css/editable.scss index b8c6c4b9f..caedb7a21 100644 --- a/qt/aqt/data/web/css/editing-area.scss +++ b/qt/aqt/data/web/css/editable.scss @@ -1,4 +1,4 @@ -editing-area { +anki-editable { display: block; overflow-wrap: break-word; overflow: auto; diff --git a/qt/aqt/data/web/js/editor.ts b/qt/aqt/data/web/js/editor.ts index 97c24d37c..feae3fcc8 100644 --- a/qt/aqt/data/web/js/editor.ts +++ b/qt/aqt/data/web/js/editor.ts @@ -1,7 +1,7 @@ /* Copyright: Ankitects Pty Ltd and contributors * License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html */ -let currentField: EditingContainer | null = null; +let currentField: EditingArea | null = null; let changeTimer: number | null = null; let currentNoteId: number | null = null; @@ -33,7 +33,7 @@ function saveNow(keepFocus: boolean): void { saveField("key"); } else { // triggers onBlur, which saves - currentField.blurEditingArea(); + currentField.blurEditable(); } } @@ -52,7 +52,7 @@ interface Selection { function onKey(evt: KeyboardEvent): void { // esc clears focus, allowing dialog to close if (evt.code === "Escape") { - currentField.blurEditingArea(); + currentField.blurEditable(); return; } @@ -91,7 +91,7 @@ function onKeyUp(evt: KeyboardEvent): void { if ( nodeIsElement(anchor) && anchor.tagName === "DIV" && - !(anchor instanceof EditingContainer) && + !(anchor instanceof EditingArea) && anchor.childElementCount === 1 && anchor.children[0].tagName === "BR" ) { @@ -249,12 +249,12 @@ function clearChangeTimer(): void { } function onFocus(evt: FocusEvent): void { - const elem = evt.currentTarget as EditingContainer; + const elem = evt.currentTarget as EditingArea; if (currentField === elem) { // anki window refocused; current element unchanged return; } - elem.focusEditingArea(); + elem.focusEditable(); currentField = elem; pycmd(`focus:${currentField.ord}`); enableButtons(); @@ -280,19 +280,19 @@ function onFocus(evt: FocusEvent): void { } function focusField(n: number): void { - const field = document.getElementById(`f${n}`) as EditingContainer; + const field = document.getElementById(`f${n}`) as EditingArea; if (field) { - field.focusEditingArea(); + field.focusEditable(); } } function focusIfField(x: number, y: number): boolean { const elements = document.elementsFromPoint(x, y); for (let i = 0; i < elements.length; i++) { - let elem = elements[i] as EditingContainer; - if (elem instanceof EditingContainer) { - elem.focusEditingArea(); + let elem = elements[i] as EditingArea; + if (elem instanceof EditingArea) { + elem.focusEditable(); // the focus event may not fire if the window is not active, so make sure // the current field is set currentField = elem; @@ -309,7 +309,7 @@ function onPaste(): void { function caretToEnd(): void { const range = document.createRange(); - range.selectNodeContents(currentField.editingArea); + range.selectNodeContents(currentField.editable); range.collapse(false); const selection = currentField.getSelection(); selection.removeAllRanges(); @@ -379,7 +379,7 @@ function enableButtons(): void { // disable the buttons if a field is not currently focused function maybeDisableButtons(): void { - if (document.activeElement instanceof EditingContainer) { + if (document.activeElement instanceof EditingArea) { enableButtons(); } else { disableButtons(); @@ -423,7 +423,7 @@ function onCutOrCopy(): boolean { return true; } -class EditingArea extends HTMLElement { +class Editable extends HTMLElement { set fieldHTML(content: string) { this.innerHTML = content; @@ -443,10 +443,10 @@ class EditingArea extends HTMLElement { } } -customElements.define("editing-area", EditingArea); +customElements.define("anki-editable", Editable); -class EditingContainer extends HTMLDivElement { - editingArea: EditingArea; +class EditingArea extends HTMLDivElement { + editable: Editable; baseStyle: HTMLStyleElement; constructor() { @@ -456,15 +456,15 @@ class EditingContainer extends HTMLDivElement { const rootStyle = document.createElement("link"); rootStyle.setAttribute("rel", "stylesheet"); - rootStyle.setAttribute("href", "./_anki/css/editing-area.css"); + 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.editingArea = document.createElement("editing-area") as EditingArea; - this.shadowRoot.appendChild(this.editingArea); + this.editable = document.createElement("anki-editable") as Editable; + this.shadowRoot.appendChild(this.editable); } get ord(): number { @@ -472,11 +472,11 @@ class EditingContainer extends HTMLDivElement { } set fieldHTML(content: string) { - this.editingArea.fieldHTML = content; + this.editable.fieldHTML = content; } get fieldHTML(): string { - return this.editingArea.fieldHTML; + return this.editable.fieldHTML; } connectedCallback(): void { @@ -490,7 +490,7 @@ class EditingContainer extends HTMLDivElement { this.addEventListener("oncut", onCutOrCopy); const baseStyleSheet = this.baseStyle.sheet as CSSStyleSheet; - baseStyleSheet.insertRule("editing-area {}", 0); + baseStyleSheet.insertRule("anki-editable {}", 0); } disconnectedCallback(): void { @@ -506,7 +506,7 @@ class EditingContainer extends HTMLDivElement { initialize(color: string, content: string): void { this.setBaseColor(color); - this.editingArea.fieldHTML = content; + this.editable.fieldHTML = content; } setBaseColor(color: string): void { @@ -524,28 +524,28 @@ class EditingContainer extends HTMLDivElement { } isRightToLeft(): boolean { - return this.editingArea.style.direction === "rtl"; + return this.editable.style.direction === "rtl"; } getSelection(): Selection { return this.shadowRoot.getSelection(); } - focusEditingArea(): void { - this.editingArea.focus(); + focusEditable(): void { + this.editable.focus(); } - blurEditingArea(): void { - this.editingArea.blur(); + blurEditable(): void { + this.editable.blur(); } } -customElements.define("editing-container", EditingContainer, { extends: "div" }); +customElements.define("anki-editing-area", EditingArea, { extends: "div" }); class EditorField extends HTMLDivElement { labelContainer: HTMLDivElement; label: HTMLSpanElement; - editingContainer: EditingContainer; + editingArea: EditingArea; constructor() { super(); @@ -557,10 +557,10 @@ class EditorField extends HTMLDivElement { this.label.className = "fieldname"; this.labelContainer.appendChild(this.label); - this.editingContainer = document.createElement("div", { - is: "editing-container", - }) as EditingContainer; - this.appendChild(this.editingContainer); + this.editingArea = document.createElement("div", { + is: "anki-editing-area", + }) as EditingArea; + this.appendChild(this.editingArea); } static get observedAttributes(): string[] { @@ -574,28 +574,28 @@ class EditorField extends HTMLDivElement { attributeChangedCallback(name: string, _oldValue: string, newValue: string): void { switch (name) { case "ord": - this.editingContainer.setAttribute("ord", newValue); + this.editingArea.setAttribute("ord", newValue); } } initialize(label: string, color: string, content: string): void { this.label.innerText = label; - this.editingContainer.initialize(color, content); + this.editingArea.initialize(color, content); } setBaseStyling(fontFamily: string, fontSize: string, direction: string): void { - this.editingContainer.setBaseStyling(fontFamily, fontSize, direction); + this.editingArea.setBaseStyling(fontFamily, fontSize, direction); } } -customElements.define("editor-field", EditorField, { extends: "div" }); +customElements.define("anki-editor-field", EditorField, { extends: "div" }); function adjustFieldAmount(amount: number): void { const fieldsContainer = document.getElementById("fields"); while (fieldsContainer.childElementCount < amount) { const newField = document.createElement("div", { - is: "editor-field", + is: "anki-editor-field", }) as EditorField; newField.ord = fieldsContainer.childElementCount; fieldsContainer.appendChild(newField); @@ -634,7 +634,7 @@ function setFields(fields: [string, string][]): void { function setBackgrounds(cols: ("dupe" | "")[]) { forEditorField(cols, (field, value) => - field.editingContainer.classList.toggle("dupe", value === "dupe") + field.editingArea.classList.toggle("dupe", value === "dupe") ); document .querySelector("#dupes")