diff --git a/src/platform/web/ui/avatar.js b/src/platform/web/ui/avatar.js index 2e2b0142..5bc019cb 100644 --- a/src/platform/web/ui/avatar.js +++ b/src/platform/web/ui/avatar.js @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -import {tag, text, classNames, setAttribute} from "./general/html.js"; +import {tag, text, classNames, setAttribute} from "./general/html"; /** * @param {Object} vm view model with {avatarUrl, avatarColorNumber, avatarTitle, avatarLetter} * @param {Number} size diff --git a/src/platform/web/ui/general/LazyListView.js b/src/platform/web/ui/general/LazyListView.js index 6f565e30..4426d97c 100644 --- a/src/platform/web/ui/general/LazyListView.js +++ b/src/platform/web/ui/general/LazyListView.js @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -import {el} from "./html.js"; +import {el} from "./html"; import {mountView} from "./utils"; import {ListView} from "./ListView"; import {insertAt} from "./utils"; diff --git a/src/platform/web/ui/general/ListView.ts b/src/platform/web/ui/general/ListView.ts index 2c45fb1c..2bd3b7d3 100644 --- a/src/platform/web/ui/general/ListView.ts +++ b/src/platform/web/ui/general/ListView.ts @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -import {el} from "./html.js"; +import {el} from "./html"; import {mountView, insertAt} from "./utils"; import {BaseObservableList as ObservableList} from "../../../../observable/list/BaseObservableList.js"; import {UIView, IMountArgs} from "./types"; @@ -79,7 +79,7 @@ export class ListView implements UIView { if (this._className) { attr.className = this._className; } - this._root = el(this._tagName, attr); + this._root = el(this._tagName, attr) as HTMLElement; this.loadList(); if (this._onItemClick) { this._root!.addEventListener("click", this); diff --git a/src/platform/web/ui/general/StaticView.js b/src/platform/web/ui/general/StaticView.js index c87a0f3c..1c3f3ea2 100644 --- a/src/platform/web/ui/general/StaticView.js +++ b/src/platform/web/ui/general/StaticView.js @@ -15,7 +15,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -import {tag} from "../general/html.js"; +import {tag} from "../general/html"; export class StaticView { constructor(value, render = undefined) { diff --git a/src/platform/web/ui/general/TemplateView.js b/src/platform/web/ui/general/TemplateView.js index 3e9727dc..f6c7247f 100644 --- a/src/platform/web/ui/general/TemplateView.js +++ b/src/platform/web/ui/general/TemplateView.js @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -import { setAttribute, text, isChildren, classNames, TAG_NAMES, HTML_NS } from "./html.js"; +import { setAttribute, text, isChildren, classNames, TAG_NAMES, HTML_NS } from "./html"; import {mountView} from "./utils"; import {BaseUpdateView} from "./BaseUpdateView.js"; diff --git a/src/platform/web/ui/general/html.js b/src/platform/web/ui/general/html.ts similarity index 61% rename from src/platform/web/ui/general/html.js rename to src/platform/web/ui/general/html.ts index f12ad306..db320017 100644 --- a/src/platform/web/ui/general/html.js +++ b/src/platform/web/ui/general/html.ts @@ -1,5 +1,6 @@ /* Copyright 2020 Bruno Windels +Copyright 2021 Daniel Fedorin Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -16,12 +17,16 @@ limitations under the License. // DOM helper functions -export function isChildren(children) { +export type ClassNames = { [className: string]: boolean | ((value?: T) => boolean) } +export type BasicAttributes = { [attribute: string]: ClassNames | boolean | string } +export type Child = string | Text | Element + +export function isChildren(children: object | Child | Child[]): children is Child | Child[] { // children should be an not-object (that's the attributes), or a domnode, or an array - return typeof children !== "object" || !!children.nodeType || Array.isArray(children); + return typeof children !== "object" || "nodeType" in children || Array.isArray(children); } -export function classNames(obj, value) { +export function classNames(obj: ClassNames, value?: T): string { return Object.entries(obj).reduce((cn, [name, enabled]) => { if (typeof enabled === "function") { enabled = enabled(value); @@ -34,7 +39,7 @@ export function classNames(obj, value) { }, ""); } -export function setAttribute(el, name, value) { +export function setAttribute(el: Element, name: string, value: string | boolean): void { if (name === "className") { name = "class"; } @@ -48,22 +53,24 @@ export function setAttribute(el, name, value) { } } -export function el(elementName, attributes, children) { +export function el(elementName: string, attributes?: BasicAttributes | Child | Child[], children?: Child | Child[]): Element { return elNS(HTML_NS, elementName, attributes, children); } -export function elNS(ns, elementName, attributes, children) { +export function elNS(ns: string, elementName: string, attributes?: BasicAttributes | Child | Child[], children?: Child | Child[]): Element { if (attributes && isChildren(attributes)) { children = attributes; - attributes = null; + attributes = undefined; } const e = document.createElementNS(ns, elementName); if (attributes) { for (let [name, value] of Object.entries(attributes)) { - if (name === "className" && typeof value === "object" && value !== null) { - value = classNames(value); + if (typeof value === "object") { + // Only className should ever be an object; be careful + // here anyway and ignore object-valued non-className attributes. + value = (value !== null && name === "className") ? classNames(value) : false; } setAttribute(e, name, value); } @@ -74,7 +81,7 @@ export function elNS(ns, elementName, attributes, children) { children = [children]; } for (let c of children) { - if (!c.nodeType) { + if (typeof c === "string") { c = text(c); } e.appendChild(c); @@ -83,12 +90,12 @@ export function elNS(ns, elementName, attributes, children) { return e; } -export function text(str) { +export function text(str: string): Text { return document.createTextNode(str); } -export const HTML_NS = "http://www.w3.org/1999/xhtml"; -export const SVG_NS = "http://www.w3.org/2000/svg"; +export const HTML_NS: string = "http://www.w3.org/1999/xhtml"; +export const SVG_NS: string = "http://www.w3.org/2000/svg"; export const TAG_NAMES = { [HTML_NS]: [ @@ -97,10 +104,9 @@ export const TAG_NAMES = { "table", "thead", "tbody", "tr", "th", "td", "hr", "pre", "code", "button", "time", "input", "textarea", "label", "form", "progress", "output", "video"], [SVG_NS]: ["svg", "circle"] -}; - -export const tag = {}; +} as const; +export const tag: { [tagName in typeof TAG_NAMES[string][number]]: (attributes?: BasicAttributes | Child | Child[], children?: Child | Child[]) => Element } = {} as any; for (const [ns, tags] of Object.entries(TAG_NAMES)) { for (const tagName of tags) { diff --git a/src/platform/web/ui/general/utils.ts b/src/platform/web/ui/general/utils.ts index f4469ca1..1e4bbdaa 100644 --- a/src/platform/web/ui/general/utils.ts +++ b/src/platform/web/ui/general/utils.ts @@ -15,7 +15,7 @@ limitations under the License. */ import {UIView, IMountArgs} from "./types"; -import {tag} from "./html.js"; +import {tag} from "./html"; export function mountView(view: UIView, mountArgs: IMountArgs): HTMLElement { let node; @@ -38,7 +38,7 @@ export function errorToDOM(error: Error): HTMLElement { tag.h3(error.message), tag.p(`This occurred while running ${callee}.`), tag.pre(error.stack), - ]); + ]) as HTMLElement; } export function insertAt(parentNode: HTMLElement, idx: number, childNode: HTMLElement): void { diff --git a/src/platform/web/ui/session/rightpanel/RoomDetailsView.js b/src/platform/web/ui/session/rightpanel/RoomDetailsView.js index a9c4c109..763968ca 100644 --- a/src/platform/web/ui/session/rightpanel/RoomDetailsView.js +++ b/src/platform/web/ui/session/rightpanel/RoomDetailsView.js @@ -15,7 +15,7 @@ limitations under the License. */ import {TemplateView} from "../../general/TemplateView.js"; -import {classNames, tag} from "../../general/html.js"; +import {classNames, tag} from "../../general/html"; import {AvatarView} from "../../AvatarView.js"; export class RoomDetailsView extends TemplateView { diff --git a/src/platform/web/ui/session/room/timeline/BaseMessageView.js b/src/platform/web/ui/session/room/timeline/BaseMessageView.js index 9469d201..69ae4bd3 100644 --- a/src/platform/web/ui/session/room/timeline/BaseMessageView.js +++ b/src/platform/web/ui/session/room/timeline/BaseMessageView.js @@ -16,7 +16,7 @@ limitations under the License. */ import {renderStaticAvatar} from "../../../avatar.js"; -import {tag} from "../../../general/html.js"; +import {tag} from "../../../general/html"; import {mountView} from "../../../general/utils"; import {TemplateView} from "../../../general/TemplateView.js"; import {Popup} from "../../../general/Popup.js"; diff --git a/src/platform/web/ui/session/room/timeline/TextMessageView.js b/src/platform/web/ui/session/room/timeline/TextMessageView.js index fcafaf27..c1674501 100644 --- a/src/platform/web/ui/session/room/timeline/TextMessageView.js +++ b/src/platform/web/ui/session/room/timeline/TextMessageView.js @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -import {tag, text} from "../../../general/html.js"; +import {tag, text} from "../../../general/html"; import {BaseMessageView} from "./BaseMessageView.js"; export class TextMessageView extends BaseMessageView {