diff --git a/src/platform/web/ui/session/room/MessageComposer.js b/src/platform/web/ui/session/room/MessageComposer.js index 6ce8148f..b03b21a4 100644 --- a/src/platform/web/ui/session/room/MessageComposer.js +++ b/src/platform/web/ui/session/room/MessageComposer.js @@ -55,7 +55,7 @@ export class MessageComposer extends TemplateView { className: "cancel", onClick: () => this._clearReplyingTo() }, "Close"), - t.view(new TileView(rvm, this._viewClassForTile, { interactive: false }, "div")) + t.view(new TileView(rvm, this._viewClassForTile, { interactive: false })) ]); }); const input = t.div({className: "MessageComposer_input"}, [ diff --git a/src/platform/web/ui/session/room/TimelineView.ts b/src/platform/web/ui/session/room/TimelineView.ts index 5a04991f..bde581df 100644 --- a/src/platform/web/ui/session/room/TimelineView.ts +++ b/src/platform/web/ui/session/room/TimelineView.ts @@ -192,6 +192,7 @@ class TilesListView extends ListView { super({ list: tiles, onItemClick: (tileView, evt) => tileView.onClick(evt), + tagName: "div" }, tile => { const TileView = viewClassForTile(tile); return new TileView(tile, viewClassForTile); diff --git a/src/platform/web/ui/session/room/timeline/AnnouncementView.js b/src/platform/web/ui/session/room/timeline/AnnouncementView.js index 8b68d33b..5d0e0e49 100644 --- a/src/platform/web/ui/session/room/timeline/AnnouncementView.js +++ b/src/platform/web/ui/session/room/timeline/AnnouncementView.js @@ -14,21 +14,13 @@ See the License for the specific language governing permissions and limitations under the License. */ -import {TemplateView} from "../../../general/TemplateView"; +import {BaseTileView} from "./BaseTileView"; -export class AnnouncementView extends TemplateView { - // ignore other arguments - constructor(vm) { - super(vm); - } - - render(t, vm) { - return t.li({ +export class AnnouncementView extends BaseTileView { + renderTile(t, vm) { + return t.div({ className: "AnnouncementView", 'data-event-id': vm.eventId }, t.div(vm => vm.announcement)); } - - /* This is called by the parent ListView, which just has 1 listener for the whole list */ - onClick() {} } diff --git a/src/platform/web/ui/session/room/timeline/BaseMessageView.js b/src/platform/web/ui/session/room/timeline/BaseMessageView.js index ee0a37db..366f87b4 100644 --- a/src/platform/web/ui/session/room/timeline/BaseMessageView.js +++ b/src/platform/web/ui/session/room/timeline/BaseMessageView.js @@ -18,17 +18,15 @@ limitations under the License. import {renderStaticAvatar} from "../../../avatar"; import {tag} from "../../../general/html"; import {mountView} from "../../../general/utils"; -import {TemplateView} from "../../../general/TemplateView"; +import {BaseTileView} from "./BaseTileView"; import {Popup} from "../../../general/Popup.js"; import {Menu} from "../../../general/Menu.js"; import {ReactionsView} from "./ReactionsView.js"; -export class BaseMessageView extends TemplateView { - constructor(value, viewClassForTile, renderFlags, tagName = "li") { - super(value); +export class BaseMessageView extends BaseTileView { + constructor(value, viewClassForTile, renderFlags) { + super(value, viewClassForTile); this._menuPopup = null; - this._tagName = tagName; - this._viewClassForTile = viewClassForTile; // TODO An enum could be nice to make code easier to read at call sites. this._renderFlags = renderFlags; } @@ -36,12 +34,12 @@ export class BaseMessageView extends TemplateView { get _interactive() { return this._renderFlags?.interactive ?? true; } get _isReplyPreview() { return this._renderFlags?.reply; } - render(t, vm) { + renderTile(t, vm) { const children = [this.renderMessageBody(t, vm)]; if (this._interactive) { children.push(t.button({className: "Timeline_messageOptions"}, "⋯")); } - const li = t.el(this._tagName, { + const tile = t.div({ className: { "Timeline_message": true, own: vm.isOwn, @@ -59,13 +57,13 @@ export class BaseMessageView extends TemplateView { // don't use `t` from within the side-effect callback t.mapSideEffect(vm => vm.isContinuation, (isContinuation, wasContinuation) => { if (isContinuation && wasContinuation === false) { - li.removeChild(li.querySelector(".Timeline_messageAvatar")); - li.removeChild(li.querySelector(".Timeline_messageSender")); + tile.removeChild(tile.querySelector(".Timeline_messageAvatar")); + tile.removeChild(tile.querySelector(".Timeline_messageSender")); } else if (!isContinuation && !this._isReplyPreview) { const avatar = tag.a({href: vm.memberPanelLink, className: "Timeline_messageAvatar"}, [renderStaticAvatar(vm, 30)]); const sender = tag.div({className: `Timeline_messageSender usercolor${vm.avatarColorNumber}`}, vm.displayName); - li.insertBefore(avatar, li.firstChild); - li.insertBefore(sender, li.firstChild); + tile.insertBefore(avatar, tile.firstChild); + tile.insertBefore(sender, tile.firstChild); } }); // similarly, we could do this with a simple ifView, @@ -75,15 +73,15 @@ export class BaseMessageView extends TemplateView { if (reactions && this._interactive && !reactionsView) { reactionsView = new ReactionsView(reactions); this.addSubView(reactionsView); - li.appendChild(mountView(reactionsView)); + tile.appendChild(mountView(reactionsView)); } else if (!reactions && reactionsView) { - li.removeChild(reactionsView.root()); + tile.removeChild(reactionsView.root()); reactionsView.unmount(); this.removeSubView(reactionsView); reactionsView = null; } }); - return li; + return tile; } /* This is called by the parent ListView, which just has 1 listener for the whole list */ diff --git a/src/platform/web/ui/session/room/timeline/BaseTileView.js b/src/platform/web/ui/session/room/timeline/BaseTileView.js new file mode 100644 index 00000000..ba0b543e --- /dev/null +++ b/src/platform/web/ui/session/room/timeline/BaseTileView.js @@ -0,0 +1,56 @@ +/* +Copyright 2020 Bruno Windels + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +import {TemplateView} from "../../../general/TemplateView"; + +export class BaseTileView extends TemplateView { + // ignore other arguments + constructor(vm, viewClassForTile) { + super(vm); + this._viewClassForTile = viewClassForTile; + this._root = undefined; + } + + root() { + return this._root; + } + + render(t, vm) { + const tile = this.renderTile(t, vm); + const swapRoot = newRoot => { + this._root?.replaceWith(newRoot); + this._root = newRoot; + } + t.mapSideEffect(vm => vm.hasDateSeparator, hasDateSeparator => { + if (hasDateSeparator) { + const container = t.div([this._renderDateSeparator(t, vm)]); + swapRoot(container); + container.appendChild(tile); + } else { + swapRoot(tile); + } + }); + return this._root; + } + + _renderDateSeparator(t, vm) { + // if this needs any bindings, we need to use a subview + return t.div({className: "DateSeparator"}, t.time(vm.date)); + } + + /* This is called by the parent ListView, which just has 1 listener for the whole list */ + onClick() {} +}