From 33160dcb00373caa5f5aace69a529c556e8e4032 Mon Sep 17 00:00:00 2001 From: Henrik Giesel Date: Sat, 30 Jan 2021 17:54:07 +0100 Subject: [PATCH 1/6] Make editor a rollup package --- qt/aqt/data/web/js/BUILD.bazel | 28 +- qt/aqt/data/web/js/editor/BUILD.bazel | 16 + qt/aqt/data/web/js/editor/filterHtml.ts | 170 +++++++++++ qt/aqt/data/web/js/editor/helpers.ts | 65 +++++ .../web/js/{editor.ts => editor/index.ts} | 274 ++---------------- qt/aqt/data/web/js/rollup.config.js | 15 + 6 files changed, 312 insertions(+), 256 deletions(-) create mode 100644 qt/aqt/data/web/js/editor/BUILD.bazel create mode 100644 qt/aqt/data/web/js/editor/filterHtml.ts create mode 100644 qt/aqt/data/web/js/editor/helpers.ts rename qt/aqt/data/web/js/{editor.ts => editor/index.ts} (72%) create mode 100644 qt/aqt/data/web/js/rollup.config.js diff --git a/qt/aqt/data/web/js/BUILD.bazel b/qt/aqt/data/web/js/BUILD.bazel index b47d90193..24800474f 100644 --- a/qt/aqt/data/web/js/BUILD.bazel +++ b/qt/aqt/data/web/js/BUILD.bazel @@ -1,9 +1,12 @@ load("@npm//@bazel/typescript:index.bzl", "ts_library") +load("@npm//@bazel/rollup:index.bzl", "rollup_bundle") load("//ts:prettier.bzl", "prettier_test") +load("//ts:eslint.bzl", "eslint_test") ts_library( name = "pycmd", srcs = ["pycmd.d.ts"], + visibility = ["//qt/aqt/data/web/js:__subpackages__"], ) ts_library( @@ -26,10 +29,30 @@ filegroup( output_group = "es5_sources", ) +###### aqt bundles + +rollup_bundle( + name = "editor", + config_file = "rollup.config.js", + entry_point = "//qt/aqt/data/web/js/editor:index.ts", + format = "iife", + link_workspace_root = True, + silent = True, + sourcemap = "false", + deps = [ + "//qt/aqt/data/web/js/editor", + "@npm//@rollup/plugin-commonjs", + "@npm//@rollup/plugin-node-resolve", + "@npm//rollup-plugin-terser", + ], +) + + filegroup( name = "js", srcs = [ "aqt_es5", + "editor", "mathjax.js", "//qt/aqt/data/web/js/vendor", ], @@ -47,4 +70,7 @@ prettier_test( # srcs = glob(["*.ts"]), # ) -exports_files(["mathjax.js"]) +exports_files([ + "mathjax.js", + "tsconfig.json", +]) diff --git a/qt/aqt/data/web/js/editor/BUILD.bazel b/qt/aqt/data/web/js/editor/BUILD.bazel new file mode 100644 index 000000000..853ea8471 --- /dev/null +++ b/qt/aqt/data/web/js/editor/BUILD.bazel @@ -0,0 +1,16 @@ +load("@npm//@bazel/typescript:index.bzl", "ts_library") + +ts_library( + name = "editor", + srcs = glob(["*.ts"]), + tsconfig = "//qt/aqt/data/web/js:tsconfig.json", + deps = [ + "//qt/aqt/data/web/js:pycmd", + "@npm//@types/jquery", + ], + visibility = ["//qt:__subpackages__"], +) + +exports_files([ + "index.ts", +]) diff --git a/qt/aqt/data/web/js/editor/filterHtml.ts b/qt/aqt/data/web/js/editor/filterHtml.ts new file mode 100644 index 000000000..0b142932d --- /dev/null +++ b/qt/aqt/data/web/js/editor/filterHtml.ts @@ -0,0 +1,170 @@ +import { nodeIsElement } from "./helpers"; + +export let filterHTML = function ( + html: string, + internal: boolean, + extendedMode: boolean +): string { + // wrap it in as we aren't allowed to change top level elements + const top = document.createElement("ankitop"); + top.innerHTML = html; + + if (internal) { + filterInternalNode(top); + } else { + filterNode(top, extendedMode); + } + let outHtml = top.innerHTML; + if (!extendedMode && !internal) { + // collapse whitespace + outHtml = outHtml.replace(/[\n\t ]+/g, " "); + } + outHtml = outHtml.trim(); + return outHtml; +}; + +let allowedTagsBasic = {}; +let allowedTagsExtended = {}; + +let TAGS_WITHOUT_ATTRS = ["P", "DIV", "BR", "SUB", "SUP"]; +for (const tag of TAGS_WITHOUT_ATTRS) { + allowedTagsBasic[tag] = { attrs: [] }; +} + +TAGS_WITHOUT_ATTRS = [ + "B", + "BLOCKQUOTE", + "CODE", + "DD", + "DL", + "DT", + "EM", + "H1", + "H2", + "H3", + "I", + "LI", + "OL", + "PRE", + "RP", + "RT", + "RUBY", + "STRONG", + "TABLE", + "U", + "UL", +]; +for (const tag of TAGS_WITHOUT_ATTRS) { + allowedTagsExtended[tag] = { attrs: [] }; +} + +allowedTagsBasic["IMG"] = { attrs: ["SRC"] }; + +allowedTagsExtended["A"] = { attrs: ["HREF"] }; +allowedTagsExtended["TR"] = { attrs: ["ROWSPAN"] }; +allowedTagsExtended["TD"] = { attrs: ["COLSPAN", "ROWSPAN"] }; +allowedTagsExtended["TH"] = { attrs: ["COLSPAN", "ROWSPAN"] }; +allowedTagsExtended["FONT"] = { attrs: ["COLOR"] }; + +const allowedStyling = { + color: true, + "background-color": true, + "font-weight": true, + "font-style": true, + "text-decoration-line": true, +}; + +let isNightMode = function (): boolean { + return document.body.classList.contains("nightMode"); +}; + +let filterExternalSpan = function (elem: HTMLElement) { + // filter out attributes + for (const attr of [...elem.attributes]) { + const attrName = attr.name.toUpperCase(); + + if (attrName !== "STYLE") { + elem.removeAttributeNode(attr); + } + } + + // filter styling + for (const name of [...elem.style]) { + const value = elem.style.getPropertyValue(name); + + if ( + !allowedStyling.hasOwnProperty(name) || + // google docs adds this unnecessarily + (name === "background-color" && value === "transparent") || + // ignore coloured text in night mode for now + (isNightMode() && (name === "background-color" || name === "color")) + ) { + elem.style.removeProperty(name); + } + } +}; + +allowedTagsExtended["SPAN"] = filterExternalSpan; + +// add basic tags to extended +Object.assign(allowedTagsExtended, allowedTagsBasic); + +function isHTMLElement(elem: Element): elem is HTMLElement { + return elem instanceof HTMLElement; +} + +// filtering from another field +let filterInternalNode = function (elem: Element) { + if (isHTMLElement(elem)) { + elem.style.removeProperty("background-color"); + elem.style.removeProperty("font-size"); + elem.style.removeProperty("font-family"); + } + // recurse + for (let i = 0; i < elem.children.length; i++) { + const child = elem.children[i]; + filterInternalNode(child); + } +}; + +// filtering from external sources +let filterNode = function (node: Node, extendedMode: boolean): void { + if (!nodeIsElement(node)) { + return; + } + + // descend first, and take a copy of the child nodes as the loop will skip + // elements due to node modifications otherwise + for (const child of [...node.children]) { + filterNode(child, extendedMode); + } + + if (node.tagName === "ANKITOP") { + return; + } + + const tag = extendedMode + ? allowedTagsExtended[node.tagName] + : allowedTagsBasic[node.tagName]; + + if (!tag) { + if (!node.innerHTML || node.tagName === "TITLE") { + node.parentNode.removeChild(node); + } else { + node.outerHTML = node.innerHTML; + } + } else { + if (typeof tag === "function") { + // filtering function provided + tag(node); + } else { + // allowed, filter out attributes + for (const attr of [...node.attributes]) { + const attrName = attr.name.toUpperCase(); + if (tag.attrs.indexOf(attrName) === -1) { + node.removeAttributeNode(attr); + } + } + } + } +}; diff --git a/qt/aqt/data/web/js/editor/helpers.ts b/qt/aqt/data/web/js/editor/helpers.ts new file mode 100644 index 000000000..c2ce1efc1 --- /dev/null +++ b/qt/aqt/data/web/js/editor/helpers.ts @@ -0,0 +1,65 @@ +export function nodeIsElement(node: Node): node is Element { + return node.nodeType === Node.ELEMENT_NODE; +} + +const INLINE_TAGS = [ + "A", + "ABBR", + "ACRONYM", + "AUDIO", + "B", + "BDI", + "BDO", + "BIG", + "BR", + "BUTTON", + "CANVAS", + "CITE", + "CODE", + "DATA", + "DATALIST", + "DEL", + "DFN", + "EM", + "EMBED", + "I", + "IFRAME", + "IMG", + "INPUT", + "INS", + "KBD", + "LABEL", + "MAP", + "MARK", + "METER", + "NOSCRIPT", + "OBJECT", + "OUTPUT", + "PICTURE", + "PROGRESS", + "Q", + "RUBY", + "S", + "SAMP", + "SCRIPT", + "SELECT", + "SLOT", + "SMALL", + "SPAN", + "STRONG", + "SUB", + "SUP", + "SVG", + "TEMPLATE", + "TEXTAREA", + "TIME", + "U", + "TT", + "VAR", + "VIDEO", + "WBR", +]; + +export function nodeIsInline(node: Node): boolean { + return !nodeIsElement(node) || INLINE_TAGS.includes(node.tagName); +} diff --git a/qt/aqt/data/web/js/editor.ts b/qt/aqt/data/web/js/editor/index.ts similarity index 72% rename from qt/aqt/data/web/js/editor.ts rename to qt/aqt/data/web/js/editor/index.ts index 20a1d7dea..8f15811be 100644 --- a/qt/aqt/data/web/js/editor.ts +++ b/qt/aqt/data/web/js/editor/index.ts @@ -1,16 +1,15 @@ /* Copyright: Ankitects Pty Ltd and contributors * License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html */ +import { filterHTML } from "./filterHtml"; +import { nodeIsElement, nodeIsInline } from "./helpers"; + let currentField: EditingArea | null = null; let changeTimer: number | null = null; let currentNoteId: number | null = null; -declare interface String { - format(...args: string[]): string; -} - /* kept for compatibility with add-ons */ -String.prototype.format = function (...args: string[]): string { +(String.prototype as any).format = function (...args: string[]): string { return this.replace(/\{\d+\}/g, (m: string): void => { const match = m.match(/\d+/); @@ -18,11 +17,11 @@ String.prototype.format = function (...args: string[]): string { }); }; -function setFGButton(col: string): void { +export function setFGButton(col: string): void { document.getElementById("forecolor").style.backgroundColor = col; } -function saveNow(keepFocus: boolean): void { +export function saveNow(keepFocus: boolean): void { if (!currentField) { return; } @@ -100,72 +99,6 @@ function onKeyUp(evt: KeyboardEvent): void { } } -function nodeIsElement(node: Node): node is Element { - return node.nodeType === Node.ELEMENT_NODE; -} - -const INLINE_TAGS = [ - "A", - "ABBR", - "ACRONYM", - "AUDIO", - "B", - "BDI", - "BDO", - "BIG", - "BR", - "BUTTON", - "CANVAS", - "CITE", - "CODE", - "DATA", - "DATALIST", - "DEL", - "DFN", - "EM", - "EMBED", - "I", - "IFRAME", - "IMG", - "INPUT", - "INS", - "KBD", - "LABEL", - "MAP", - "MARK", - "METER", - "NOSCRIPT", - "OBJECT", - "OUTPUT", - "PICTURE", - "PROGRESS", - "Q", - "RUBY", - "S", - "SAMP", - "SCRIPT", - "SELECT", - "SLOT", - "SMALL", - "SPAN", - "STRONG", - "SUB", - "SUP", - "SVG", - "TEMPLATE", - "TEXTAREA", - "TIME", - "U", - "TT", - "VAR", - "VIDEO", - "WBR", -]; - -function nodeIsInline(node: Node): boolean { - return !nodeIsElement(node) || INLINE_TAGS.includes(node.tagName); -} - function inListItem(): boolean { const anchor = currentField.getSelection().anchorNode; @@ -179,7 +112,7 @@ function inListItem(): boolean { return inList; } -function insertNewline(): void { +export function insertNewline(): void { if (!inPreEnvironment()) { setFormat("insertText", "\n"); return; @@ -228,12 +161,12 @@ function updateButtonState(): void { // 'col': document.queryCommandValue("forecolor") } -function toggleEditorButton(buttonid: string): void { +export function toggleEditorButton(buttonid: string): void { const button = $(buttonid)[0]; button.classList.toggle("highlighted"); } -function setFormat(cmd: string, arg?: any, nosave: boolean = false): void { +export function setFormat(cmd: string, arg?: any, nosave: boolean = false): void { document.execCommand(cmd, false, arg); if (!nosave) { saveField("key"); @@ -279,7 +212,7 @@ function onFocus(evt: FocusEvent): void { } } -function focusField(n: number): void { +export function focusField(n: number): void { const field = getEditorField(n); if (field) { @@ -287,7 +220,7 @@ function focusField(n: number): void { } } -function focusIfField(x: number, y: number): boolean { +export 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 EditingArea; @@ -361,7 +294,7 @@ function wrappedExceptForWhitespace(text: string, front: string, back: string): return match[1] + front + match[2] + back + match[3]; } -function preventButtonFocus(): void { +export function preventButtonFocus(): void { for (const element of document.querySelectorAll("button.linkb")) { element.addEventListener("mousedown", (evt: Event) => { evt.preventDefault(); @@ -386,7 +319,7 @@ function maybeDisableButtons(): void { } } -function wrap(front: string, back: string): void { +export function wrap(front: string, back: string): void { wrapInternal(front, back, false); } @@ -527,7 +460,7 @@ class EditingArea extends HTMLDivElement { return this.editable.style.direction === "rtl"; } - getSelection(): Selection { + getSelection(): any { return this.shadowRoot.getSelection(); } @@ -622,7 +555,7 @@ function forEditorField( } } -function setFields(fields: [string, string][]): void { +export function setFields(fields: [string, string][]): void { // webengine will include the variable after enter+backspace // if we don't convert it to a literal colour const color = window @@ -637,7 +570,7 @@ function setFields(fields: [string, string][]): void { maybeDisableButtons(); } -function setBackgrounds(cols: ("dupe" | "")[]) { +export function setBackgrounds(cols: ("dupe" | "")[]) { forEditorField(cols, (field, value) => field.editingArea.classList.toggle("dupe", value === "dupe") ); @@ -646,17 +579,17 @@ function setBackgrounds(cols: ("dupe" | "")[]) { .classList.toggle("is-inactive", !cols.includes("dupe")); } -function setFonts(fonts: [string, number, boolean][]): void { +export function setFonts(fonts: [string, number, boolean][]): void { forEditorField(fonts, (field, [fontFamily, fontSize, isRtl]) => { field.setBaseStyling(fontFamily, `${fontSize}px`, isRtl ? "rtl" : "ltr"); }); } -function setNoteId(id: number): void { +export function setNoteId(id: number): void { currentNoteId = id; } -let pasteHTML = function ( +export let pasteHTML = function ( html: string, internal: boolean, extendedMode: boolean @@ -667,172 +600,3 @@ let pasteHTML = function ( setFormat("inserthtml", html); } }; - -let filterHTML = function ( - html: string, - internal: boolean, - extendedMode: boolean -): string { - // wrap it in as we aren't allowed to change top level elements - const top = document.createElement("ankitop"); - top.innerHTML = html; - - if (internal) { - filterInternalNode(top); - } else { - filterNode(top, extendedMode); - } - let outHtml = top.innerHTML; - if (!extendedMode && !internal) { - // collapse whitespace - outHtml = outHtml.replace(/[\n\t ]+/g, " "); - } - outHtml = outHtml.trim(); - return outHtml; -}; - -let allowedTagsBasic = {}; -let allowedTagsExtended = {}; - -let TAGS_WITHOUT_ATTRS = ["P", "DIV", "BR", "SUB", "SUP"]; -for (const tag of TAGS_WITHOUT_ATTRS) { - allowedTagsBasic[tag] = { attrs: [] }; -} - -TAGS_WITHOUT_ATTRS = [ - "B", - "BLOCKQUOTE", - "CODE", - "DD", - "DL", - "DT", - "EM", - "H1", - "H2", - "H3", - "I", - "LI", - "OL", - "PRE", - "RP", - "RT", - "RUBY", - "STRONG", - "TABLE", - "U", - "UL", -]; -for (const tag of TAGS_WITHOUT_ATTRS) { - allowedTagsExtended[tag] = { attrs: [] }; -} - -allowedTagsBasic["IMG"] = { attrs: ["SRC"] }; - -allowedTagsExtended["A"] = { attrs: ["HREF"] }; -allowedTagsExtended["TR"] = { attrs: ["ROWSPAN"] }; -allowedTagsExtended["TD"] = { attrs: ["COLSPAN", "ROWSPAN"] }; -allowedTagsExtended["TH"] = { attrs: ["COLSPAN", "ROWSPAN"] }; -allowedTagsExtended["FONT"] = { attrs: ["COLOR"] }; - -const allowedStyling = { - color: true, - "background-color": true, - "font-weight": true, - "font-style": true, - "text-decoration-line": true, -}; - -let isNightMode = function (): boolean { - return document.body.classList.contains("nightMode"); -}; - -let filterExternalSpan = function (elem: HTMLElement) { - // filter out attributes - for (const attr of [...elem.attributes]) { - const attrName = attr.name.toUpperCase(); - - if (attrName !== "STYLE") { - elem.removeAttributeNode(attr); - } - } - - // filter styling - for (const name of [...elem.style]) { - const value = elem.style.getPropertyValue(name); - - if ( - !allowedStyling.hasOwnProperty(name) || - // google docs adds this unnecessarily - (name === "background-color" && value === "transparent") || - // ignore coloured text in night mode for now - (isNightMode() && (name === "background-color" || name === "color")) - ) { - elem.style.removeProperty(name); - } - } -}; - -allowedTagsExtended["SPAN"] = filterExternalSpan; - -// add basic tags to extended -Object.assign(allowedTagsExtended, allowedTagsBasic); - -function isHTMLElement(elem: Element): elem is HTMLElement { - return elem instanceof HTMLElement; -} - -// filtering from another field -let filterInternalNode = function (elem: Element) { - if (isHTMLElement(elem)) { - elem.style.removeProperty("background-color"); - elem.style.removeProperty("font-size"); - elem.style.removeProperty("font-family"); - } - // recurse - for (let i = 0; i < elem.children.length; i++) { - const child = elem.children[i]; - filterInternalNode(child); - } -}; - -// filtering from external sources -let filterNode = function (node: Node, extendedMode: boolean): void { - if (!nodeIsElement(node)) { - return; - } - - // descend first, and take a copy of the child nodes as the loop will skip - // elements due to node modifications otherwise - for (const child of [...node.children]) { - filterNode(child, extendedMode); - } - - if (node.tagName === "ANKITOP") { - return; - } - - const tag = extendedMode - ? allowedTagsExtended[node.tagName] - : allowedTagsBasic[node.tagName]; - - if (!tag) { - if (!node.innerHTML || node.tagName === "TITLE") { - node.parentNode.removeChild(node); - } else { - node.outerHTML = node.innerHTML; - } - } else { - if (typeof tag === "function") { - // filtering function provided - tag(node); - } else { - // allowed, filter out attributes - for (const attr of [...node.attributes]) { - const attrName = attr.name.toUpperCase(); - if (tag.attrs.indexOf(attrName) === -1) { - node.removeAttributeNode(attr); - } - } - } - } -}; diff --git a/qt/aqt/data/web/js/rollup.config.js b/qt/aqt/data/web/js/rollup.config.js new file mode 100644 index 000000000..363cc5ae1 --- /dev/null +++ b/qt/aqt/data/web/js/rollup.config.js @@ -0,0 +1,15 @@ +import resolve from "@rollup/plugin-node-resolve"; +import commonjs from "@rollup/plugin-commonjs"; +import { terser } from "rollup-plugin-terser"; + +import process from "process"; +const production = process.env["COMPILATION_MODE"] === "opt"; + +export default { + output: { + name: "globalThis", + extend: true, + format: "iife", + }, + plugins: [resolve({ browser: true }), commonjs(), production && terser()], +}; From 859a52ab1501c58d702a3c9ce80ea21a12e1faf4 Mon Sep 17 00:00:00 2001 From: Henrik Giesel Date: Sat, 30 Jan 2021 18:32:36 +0100 Subject: [PATCH 2/6] Fix type issues --- qt/aqt/data/web/js/editor/index.ts | 21 +++++++++++++++------ 1 file changed, 15 insertions(+), 6 deletions(-) diff --git a/qt/aqt/data/web/js/editor/index.ts b/qt/aqt/data/web/js/editor/index.ts index 8f15811be..d38215d43 100644 --- a/qt/aqt/data/web/js/editor/index.ts +++ b/qt/aqt/data/web/js/editor/index.ts @@ -8,8 +8,21 @@ let currentField: EditingArea | null = null; let changeTimer: number | null = null; let currentNoteId: number | null = null; +declare global { + interface String { + format(...args: string[]): string; + } + + interface Selection { + modify(s: string, t: string, u: string): void; + addRange(r: Range): void; + removeAllRanges(): void; + getRangeAt(n: number): Range; + } +} + /* kept for compatibility with add-ons */ -(String.prototype as any).format = function (...args: string[]): string { +String.prototype.format = function (...args: string[]): string { return this.replace(/\{\d+\}/g, (m: string): void => { const match = m.match(/\d+/); @@ -44,10 +57,6 @@ function triggerKeyTimer(): void { }, 600); } -interface Selection { - modify(s: string, t: string, u: string): void; -} - function onKey(evt: KeyboardEvent): void { // esc clears focus, allowing dialog to close if (evt.code === "Escape") { @@ -460,7 +469,7 @@ class EditingArea extends HTMLDivElement { return this.editable.style.direction === "rtl"; } - getSelection(): any { + getSelection(): Selection { return this.shadowRoot.getSelection(); } From 2ab06a654048214a90926c1e6eec2707b80adbf3 Mon Sep 17 00:00:00 2001 From: Henrik Giesel Date: Sun, 31 Jan 2021 14:15:03 +0100 Subject: [PATCH 3/6] Move editor to /ts/editor --- qt/aqt/data/web/js/BUILD.bazel | 20 ++------ qt/aqt/data/web/js/editor/BUILD.bazel | 16 ------ ts/BUILD.bazel | 2 +- ts/editor/BUILD.bazel | 49 +++++++++++++++++++ .../data/web/js => ts}/editor/filterHtml.ts | 0 {qt/aqt/data/web/js => ts}/editor/helpers.ts | 0 {qt/aqt/data/web/js => ts}/editor/index.ts | 11 +++-- ts/editor/lib.ts | 9 ++++ .../rollup.aqt.config.js | 0 9 files changed, 71 insertions(+), 36 deletions(-) delete mode 100644 qt/aqt/data/web/js/editor/BUILD.bazel create mode 100644 ts/editor/BUILD.bazel rename {qt/aqt/data/web/js => ts}/editor/filterHtml.ts (100%) rename {qt/aqt/data/web/js => ts}/editor/helpers.ts (100%) rename {qt/aqt/data/web/js => ts}/editor/index.ts (98%) create mode 100644 ts/editor/lib.ts rename qt/aqt/data/web/js/rollup.config.js => ts/rollup.aqt.config.js (100%) diff --git a/qt/aqt/data/web/js/BUILD.bazel b/qt/aqt/data/web/js/BUILD.bazel index 24800474f..417c1a9bb 100644 --- a/qt/aqt/data/web/js/BUILD.bazel +++ b/qt/aqt/data/web/js/BUILD.bazel @@ -1,5 +1,6 @@ load("@npm//@bazel/typescript:index.bzl", "ts_library") load("@npm//@bazel/rollup:index.bzl", "rollup_bundle") +load("//qt/aqt/data/web/pages:defs.bzl", "copy_page") load("//ts:prettier.bzl", "prettier_test") load("//ts:eslint.bzl", "eslint_test") @@ -29,25 +30,14 @@ filegroup( output_group = "es5_sources", ) -###### aqt bundles - -rollup_bundle( +copy_page( name = "editor", - config_file = "rollup.config.js", - entry_point = "//qt/aqt/data/web/js/editor:index.ts", - format = "iife", - link_workspace_root = True, - silent = True, - sourcemap = "false", - deps = [ - "//qt/aqt/data/web/js/editor", - "@npm//@rollup/plugin-commonjs", - "@npm//@rollup/plugin-node-resolve", - "@npm//rollup-plugin-terser", + srcs = [ + "editor.js", ], + package = "//ts/editor", ) - filegroup( name = "js", srcs = [ diff --git a/qt/aqt/data/web/js/editor/BUILD.bazel b/qt/aqt/data/web/js/editor/BUILD.bazel deleted file mode 100644 index 853ea8471..000000000 --- a/qt/aqt/data/web/js/editor/BUILD.bazel +++ /dev/null @@ -1,16 +0,0 @@ -load("@npm//@bazel/typescript:index.bzl", "ts_library") - -ts_library( - name = "editor", - srcs = glob(["*.ts"]), - tsconfig = "//qt/aqt/data/web/js:tsconfig.json", - deps = [ - "//qt/aqt/data/web/js:pycmd", - "@npm//@types/jquery", - ], - visibility = ["//qt:__subpackages__"], -) - -exports_files([ - "index.ts", -]) diff --git a/ts/BUILD.bazel b/ts/BUILD.bazel index 53b97d3cd..a64f51265 100644 --- a/ts/BUILD.bazel +++ b/ts/BUILD.bazel @@ -18,9 +18,9 @@ sql_format_setup() exports_files([ "tsconfig.json", - "d3_missing.d.ts", ".prettierrc", "rollup.config.js", + "rollup.aqt.config.js", ".eslintrc.js", "licenses.json", "sql_format.ts", diff --git a/ts/editor/BUILD.bazel b/ts/editor/BUILD.bazel new file mode 100644 index 000000000..30be758e3 --- /dev/null +++ b/ts/editor/BUILD.bazel @@ -0,0 +1,49 @@ +load("@npm//@bazel/typescript:index.bzl", "ts_library") +load("@npm//@bazel/rollup:index.bzl", "rollup_bundle") +load("//ts:prettier.bzl", "prettier_test") +load("//ts:eslint.bzl", "eslint_test") + +ts_library( + name = "editor_ts", + srcs = glob(["*.ts"]), + tsconfig = "//qt/aqt/data/web/js:tsconfig.json", + deps = [ + "@npm//@types/jquery", + ], +) + +rollup_bundle( + name = "editor", + config_file = "//ts:rollup.aqt.config.js", + entry_point = "index.ts", + format = "iife", + link_workspace_root = True, + silent = True, + sourcemap = "false", + visibility = ["//visibility:public"], + deps = [ + "editor_ts", + "@npm//@rollup/plugin-commonjs", + "@npm//@rollup/plugin-node-resolve", + "@npm//rollup-plugin-terser", + ], +) + +# Tests +################ + +prettier_test( + name = "format_check", + srcs = glob([ + "*.ts", + ]), +) + +eslint_test( + name = "eslint", + srcs = glob( + [ + "*.ts", + ], + ), +) diff --git a/qt/aqt/data/web/js/editor/filterHtml.ts b/ts/editor/filterHtml.ts similarity index 100% rename from qt/aqt/data/web/js/editor/filterHtml.ts rename to ts/editor/filterHtml.ts diff --git a/qt/aqt/data/web/js/editor/helpers.ts b/ts/editor/helpers.ts similarity index 100% rename from qt/aqt/data/web/js/editor/helpers.ts rename to ts/editor/helpers.ts diff --git a/qt/aqt/data/web/js/editor/index.ts b/ts/editor/index.ts similarity index 98% rename from qt/aqt/data/web/js/editor/index.ts rename to ts/editor/index.ts index d38215d43..4a42373b0 100644 --- a/qt/aqt/data/web/js/editor/index.ts +++ b/ts/editor/index.ts @@ -3,6 +3,7 @@ import { filterHTML } from "./filterHtml"; import { nodeIsElement, nodeIsInline } from "./helpers"; +import { bridgeCommand } from "./lib"; let currentField: EditingArea | null = null; let changeTimer: number | null = null; @@ -198,7 +199,7 @@ function onFocus(evt: FocusEvent): void { } elem.focusEditable(); currentField = elem; - pycmd(`focus:${currentField.ord}`); + bridgeCommand(`focus:${currentField.ord}`); enableButtons(); // do this twice so that there's no flicker on newer versions caretToEnd(); @@ -245,7 +246,7 @@ export function focusIfField(x: number, y: number): boolean { } function onPaste(): void { - pycmd("paste"); + bridgeCommand("paste"); window.event.preventDefault(); } @@ -295,7 +296,9 @@ function saveField(type: "blur" | "key"): void { return; } - pycmd(`${type}:${currentField.ord}:${currentNoteId}:${currentField.fieldHTML}`); + bridgeCommand( + `${type}:${currentField.ord}:${currentNoteId}:${currentField.fieldHTML}` + ); } function wrappedExceptForWhitespace(text: string, front: string, back: string): string { @@ -361,7 +364,7 @@ function wrapInternal(front: string, back: string, plainText: boolean): void { } function onCutOrCopy(): boolean { - pycmd("cutOrCopy"); + bridgeCommand("cutOrCopy"); return true; } diff --git a/ts/editor/lib.ts b/ts/editor/lib.ts new file mode 100644 index 000000000..3797974c8 --- /dev/null +++ b/ts/editor/lib.ts @@ -0,0 +1,9 @@ +declare global { + interface Window { + bridgeCommand(command: string, callback?: (value: T) => void): void; + } +} + +export function bridgeCommand(command: string, callback?: (value: T) => void): void { + window.bridgeCommand(command, callback); +} diff --git a/qt/aqt/data/web/js/rollup.config.js b/ts/rollup.aqt.config.js similarity index 100% rename from qt/aqt/data/web/js/rollup.config.js rename to ts/rollup.aqt.config.js From 70b7cbcd4a2517c9ac8ab8ad8c707f9a850f2eb8 Mon Sep 17 00:00:00 2001 From: Henrik Giesel Date: Sun, 31 Jan 2021 19:03:40 +0100 Subject: [PATCH 4/6] Move editor css to editor directory --- qt/aqt/data/web/css/BUILD.bazel | 11 +++++++++++ ts/editor/BUILD.bazel | 13 +++++++++++++ {qt/aqt/data/web/css => ts/editor}/editable.scss | 0 {qt/aqt/data/web/css => ts/editor}/editor.scss | 0 4 files changed, 24 insertions(+) rename {qt/aqt/data/web/css => ts/editor}/editable.scss (100%) rename {qt/aqt/data/web/css => ts/editor}/editor.scss (100%) diff --git a/qt/aqt/data/web/css/BUILD.bazel b/qt/aqt/data/web/css/BUILD.bazel index 107de0643..33d821773 100644 --- a/qt/aqt/data/web/css/BUILD.bazel +++ b/qt/aqt/data/web/css/BUILD.bazel @@ -1,4 +1,5 @@ load("@bazel_skylib//rules:copy_file.bzl", "copy_file") +load("//qt/aqt/data/web/pages:defs.bzl", "copy_page") load("compile_sass.bzl", "compile_sass") compile_sass( @@ -16,11 +17,21 @@ copy_file( out = "core.css", ) +copy_page( + name = "editor", + srcs = [ + "editor.css", + "editable.css", + ], + package = "//ts/editor", +) + filegroup( name = "css", srcs = [ "core.css", "css_local", + "editor", ], visibility = ["//qt:__subpackages__"], ) diff --git a/ts/editor/BUILD.bazel b/ts/editor/BUILD.bazel index 30be758e3..8cdcc21c5 100644 --- a/ts/editor/BUILD.bazel +++ b/ts/editor/BUILD.bazel @@ -2,6 +2,19 @@ load("@npm//@bazel/typescript:index.bzl", "ts_library") load("@npm//@bazel/rollup:index.bzl", "rollup_bundle") load("//ts:prettier.bzl", "prettier_test") load("//ts:eslint.bzl", "eslint_test") +load("@io_bazel_rules_sass//:defs.bzl", "sass_binary") + +sass_binary( + name = "editor_css", + src = "editor.scss", + visibility = ["//visibility:public"], +) + +sass_binary( + name = "editable_css", + src = "editable.scss", + visibility = ["//visibility:public"], +) ts_library( name = "editor_ts", diff --git a/qt/aqt/data/web/css/editable.scss b/ts/editor/editable.scss similarity index 100% rename from qt/aqt/data/web/css/editable.scss rename to ts/editor/editable.scss diff --git a/qt/aqt/data/web/css/editor.scss b/ts/editor/editor.scss similarity index 100% rename from qt/aqt/data/web/css/editor.scss rename to ts/editor/editor.scss From df1b6976eb5841af835256405fc3710a55100b08 Mon Sep 17 00:00:00 2001 From: Henrik Giesel Date: Sun, 31 Jan 2021 20:55:36 +0100 Subject: [PATCH 5/6] Turn off eslint check for now --- ts/editor/BUILD.bazel | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/ts/editor/BUILD.bazel b/ts/editor/BUILD.bazel index 8cdcc21c5..59658204a 100644 --- a/ts/editor/BUILD.bazel +++ b/ts/editor/BUILD.bazel @@ -52,11 +52,11 @@ prettier_test( ]), ) -eslint_test( - name = "eslint", - srcs = glob( - [ - "*.ts", - ], - ), -) +# eslint_test( +# name = "eslint", +# srcs = glob( +# [ +# "*.ts", +# ], +# ), +# ) From 48b276cacc19daf283848727d80455e8c62175d9 Mon Sep 17 00:00:00 2001 From: Henrik Giesel Date: Sun, 31 Jan 2021 20:56:28 +0100 Subject: [PATCH 6/6] Export getEditorField and forEditorField --- ts/editor/index.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ts/editor/index.ts b/ts/editor/index.ts index 4a42373b0..2e726d751 100644 --- a/ts/editor/index.ts +++ b/ts/editor/index.ts @@ -551,12 +551,12 @@ function adjustFieldAmount(amount: number): void { } } -function getEditorField(n: number): EditorField | null { +export function getEditorField(n: number): EditorField | null { const fields = document.getElementById("fields").children; return (fields[n] as EditorField) ?? null; } -function forEditorField( +export function forEditorField( values: T[], func: (field: EditorField, value: T) => void ): void {