From e481452114a02a9d257ff1af87512c71519b9827 Mon Sep 17 00:00:00 2001 From: Henrik Giesel Date: Tue, 26 Jan 2021 23:00:55 +0100 Subject: [PATCH] Make fields div instead of table + implement fieldIsInInlineMode logic --- qt/aqt/data/web/css/editor.scss | 6 -- qt/aqt/data/web/js/editor.ts | 117 +++++++++++++++++++------------ qt/aqt/data/web/js/tsconfig.json | 2 +- 3 files changed, 72 insertions(+), 53 deletions(-) diff --git a/qt/aqt/data/web/css/editor.scss b/qt/aqt/data/web/css/editor.scss index c24c15275..df6d5731a 100644 --- a/qt/aqt/data/web/css/editor.scss +++ b/qt/aqt/data/web/css/editor.scss @@ -14,12 +14,6 @@ } } -.clearfix::after { - content: ""; - display: table; - clear: both; -} - .fname { vertical-align: middle; padding: 0; diff --git a/qt/aqt/data/web/js/editor.ts b/qt/aqt/data/web/js/editor.ts index 6c505e715..6eb94779a 100644 --- a/qt/aqt/data/web/js/editor.ts +++ b/qt/aqt/data/web/js/editor.ts @@ -105,6 +105,10 @@ function onKeyUp(evt: KeyboardEvent): void { } } +function nodeIsBRElement(node: Node): node is HTMLBRElement { + return nodeIsElement(node) && node.tagName === "BR"; +} + function nodeIsElement(node: Node): node is Element { return node.nodeType === Node.ELEMENT_NODE; } @@ -113,6 +117,10 @@ function nodeIsText(node: Node): node is Text { return node.nodeType === Node.TEXT_NODE; } +function nodeIsInline(node: Node): boolean { + return nodeIsText(node) || nodeIsBRElement(node) || window.getComputedStyle(node as Element).getPropertyValue("display").startsWith("inline"); +} + function inListItem(): boolean { const anchor = window.getSelection().anchorNode; @@ -197,7 +205,8 @@ function clearChangeTimer(): void { } } -function onFocus(elem: HTMLElement): void { +function onFocus(evt: FocusEvent): void { + const elem = evt.currentTarget as HTMLElement; if (currentField === elem) { // anki window refocused; current element unchanged return; @@ -277,19 +286,19 @@ function onBlur(): void { } } -function stripTrailingLinebreakIfInlineElements(field: HTMLDivElement) { - if ( - nodeIsElement(field.lastChild) && - field.lastChild.tagName === "BR" && ( - nodeIsText(field.lastChild.previousSibling) || - window.getComputedStyle(field.lastChild.previousSibling as Element).getPropertyValue("display").startsWith("inline") - ) - ) { - console.log('trimmd!', field.lastChild, field.lastChild.previousSibling) - return field.innerHTML.slice(0, -4); +function fieldIsInInlineMode(field: HTMLDivElement): boolean { + if (field.childNodes.length === 0) { + // for now, for all practical purposes, empty fields are in block mode + return false; } - return field.innerHTML; + for (const child of field.children) { + if (!nodeIsInline(child)) { + return false; + } + } + + return true; } function saveField(type: "blur" | "key"): void { @@ -299,7 +308,11 @@ function saveField(type: "blur" | "key"): void { return; } - const fieldText = stripTrailingLinebreakIfInlineElements(currentField); + const fieldText = fieldIsInInlineMode(currentField) && currentField.innerHTML.endsWith("
") + // trim trailing
+ ? currentField.innerHTML.slice(0, -4) + : currentField.innerHTML + pycmd(`${type}:${currentFieldOrdinal()}:${currentNoteId}:${fieldText}`); } @@ -374,44 +387,58 @@ 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"; + + const fieldname = document.createElement("span"); + fieldname.className = "fieldname"; + fieldname.innerText = label; + name.appendChild(fieldname); + + 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; + + if (fieldIsInInlineMode(field)) { + field.appendChild(document.createElement("br")); + } + + return [name, field]; +} + function setFields(fields: [string, string][]): void { - let txt = ""; // webengine will include the variable after enter+backspace // if we don't convert it to a literal colour const color = window .getComputedStyle(document.documentElement) .getPropertyValue("--text-fg"); - for (let i = 0; i < fields.length; i++) { - const n = fields[i][0]; - let f = fields[i][1]; - txt += ` - - - ${n} - - - - -
${f}
- - `; + + const elements = fields.flatMap( + ([name, fieldcontent], index: number) => createField(index, name, color, fieldcontent) + ) + + const fieldsContainer = document.getElementById("fields") + // can be replaced with ParentNode.replaceChildren in Chrome 86+ + while (fieldsContainer.firstChild) { + fieldsContainer.removeChild(fieldsContainer.firstChild); } - $("#fields").html( - `${txt}
` - ); + for (const element of elements) { + fieldsContainer.appendChild(element); + } + maybeDisableButtons(); } @@ -474,8 +501,6 @@ let filterHTML = function ( outHtml = outHtml.replace(/[\n\t ]+/g, " "); } outHtml = outHtml.trim(); - //console.log(`input html: ${html}`); - //console.log(`outpt html: ${outHtml}`); return outHtml; }; diff --git a/qt/aqt/data/web/js/tsconfig.json b/qt/aqt/data/web/js/tsconfig.json index e15ecfe4f..9deb06f21 100644 --- a/qt/aqt/data/web/js/tsconfig.json +++ b/qt/aqt/data/web/js/tsconfig.json @@ -2,7 +2,7 @@ "compilerOptions": { "target": "es6", "module": "commonjs", - "lib": ["es6", "dom", "dom.iterable"], + "lib": ["es2019", "dom", "dom.iterable"], "strict": true, "noImplicitAny": false, "strictNullChecks": false,