Refactor DateSeparator using MVVM and move to shared-components (#32482)

* Refactor DateSeparator using MVVM and move to shared-components

* Add a few more stories, tests and screenshots

* Use the shared component and viewmodel in element-web

* Renaming custom content property an updating snapshots

* Fix lint errors and update snapshot after merge

* Change lifecycle handling for DateSeparatoreViewModel in components where manual handling is preferrable over wrapper component.

* Move context menu from viewmodel to shared components - step 1

* Create a jump to date picker component in shared components

* Add tests for coverage and fix layout issues and roving indexes

* Make element-web use the new component

* Simplify context menu and adjusting tests

* The HTMLExport now render shared components and need a I18nContext.Provider

* Updating unit tests for context menu

* Changed to {translate: _t} to let scripts pick up translations

* Fix lint issue and updating screenshots after merge

* Update snaps for element web components

* Renaming MVVM view components with suffix View.

* Fixing problem with input date calendar icon and system dark theme

* Changed the rendering of the menu and added a separate button component

* Handle input control with useRef in onKeyDown

* Updating DateSeparator snapshots on unit tests

* Updating layout after compound Menu got a className property

* Move files to new subfolder after merge

* Updated snapshot after merge

* Updating lock file

* Updates to styling from PR review

* Updates to focus/blur functionality

* Fixed tabbing and export documentation to stories

* Updated snapshots

---------

Co-authored-by: Zack <zazi21@student.bth.se>
This commit is contained in:
rbondesson
2026-03-02 13:18:51 +01:00
committed by GitHub
parent 76cbfb1ae4
commit 11030ae68d
48 changed files with 1819 additions and 1048 deletions
+28 -13
View File
@@ -13,7 +13,7 @@ import { renderToStaticMarkup } from "react-dom/server";
import { logger } from "matrix-js-sdk/src/logger";
import escapeHtml from "escape-html";
import { TooltipProvider } from "@vector-im/compound-web";
import { I18nContext } from "@element-hq/web-shared-components";
import { DateSeparatorView, I18nContext } from "@element-hq/web-shared-components";
import Exporter from "./Exporter";
import { mediaFromMxc } from "../../customisations/Media";
@@ -24,7 +24,6 @@ import { RoomPermalinkCreator } from "../permalinks/Permalinks";
import { _t } from "../../languageHandler";
import * as Avatar from "../../Avatar";
import EventTile from "../../components/views/rooms/EventTile";
import DateSeparator from "../../components/views/messages/DateSeparator";
import BaseAvatar from "../../components/views/avatars/BaseAvatar";
import { type ExportType, type IExportOptions } from "./exportUtils";
import MatrixClientContext from "../../contexts/MatrixClientContext";
@@ -32,6 +31,7 @@ import getExportCSS from "./exportCSS";
import { textForEvent } from "../../TextForEvent";
import { haveRendererForEvent } from "../../events/EventTileFactory";
import { SDKContext, SdkContextClass } from "../../contexts/SDKContext.ts";
import { DateSeparatorViewModel } from "../../viewmodels/timeline/DateSeparatorViewModel";
import exportJS from "!!raw-loader!./exportJS";
@@ -56,6 +56,12 @@ export default class HTMLExporter extends Exporter {
: _t("export_chat|media_omitted_file_size");
}
private renderToStaticMarkupWithProviders(element: JSX.Element): string {
return renderToStaticMarkup(
<I18nContext.Provider value={window.mxModuleApi.i18n}>{element}</I18nContext.Provider>,
);
}
protected async getRoomAvatar(): Promise<string> {
let blob: Blob | undefined = undefined;
const avatarUrl = Avatar.avatarUrlForRoom(this.room, 32, 32, "crop");
@@ -73,7 +79,7 @@ export default class HTMLExporter extends Exporter {
const avatar = (
<BaseAvatar size="32px" name={this.room.name} title={this.room.name} url={blob ? avatarPath : ""} />
);
return renderToStaticMarkup(avatar);
return this.renderToStaticMarkupWithProviders(avatar);
}
protected async wrapHTML(content: string, currentPage: number, nbPages: number): Promise<string> {
@@ -93,7 +99,7 @@ export default class HTMLExporter extends Exporter {
const safeExporter = escapeHtml(exporter);
const safeRoomName = escapeHtml(this.room.name);
const safeTopic = escapeHtml(topic);
const safeExportedText = renderToStaticMarkup(
const safeExportedText = this.renderToStaticMarkupWithProviders(
<p>
{_t(
"export_chat|export_info",
@@ -123,7 +129,7 @@ export default class HTMLExporter extends Exporter {
);
const safeTopicText = topic ? _t("export_chat|topic", { topic: safeTopic }) : "";
const previousMessagesLink = renderToStaticMarkup(
const previousMessagesLink = this.renderToStaticMarkupWithProviders(
currentPage !== 0 ? (
<div style={{ textAlign: "center" }}>
<a href={`./messages${currentPage === 1 ? "" : currentPage}.html`} style={{ fontWeight: "bold" }}>
@@ -135,7 +141,7 @@ export default class HTMLExporter extends Exporter {
),
);
const nextMessagesLink = renderToStaticMarkup(
const nextMessagesLink = this.renderToStaticMarkupWithProviders(
currentPage < nbPages - 1 ? (
<div style={{ textAlign: "center", margin: "10px" }}>
<a href={"./messages" + (currentPage + 2) + ".html"} style={{ fontWeight: "bold" }}>
@@ -252,12 +258,21 @@ export default class HTMLExporter extends Exporter {
protected getDateSeparator(event: MatrixEvent): string {
const ts = event.getTs();
const dateSeparator = (
<li key={ts}>
<DateSeparator forExport={true} key={ts} roomId={event.getRoomId()!} ts={ts} />
</li>
);
return renderToStaticMarkup(dateSeparator);
const dateSeparatorViewModel = new DateSeparatorViewModel({
roomId: event.getRoomId()!,
ts,
forExport: true,
});
try {
const dateSeparator = (
<li key={ts}>
<DateSeparatorView vm={dateSeparatorViewModel} />
</li>
);
return this.renderToStaticMarkupWithProviders(dateSeparator);
} finally {
dateSeparatorViewModel.dispose();
}
}
protected needsDateSeparator(event: MatrixEvent, prevEvent: MatrixEvent | null): boolean {
@@ -326,7 +341,7 @@ export default class HTMLExporter extends Exporter {
eventTileMarkup = tempElement.innerHTML;
tempRoot.unmount();
} else {
eventTileMarkup = renderToStaticMarkup(EventTile);
eventTileMarkup = this.renderToStaticMarkupWithProviders(EventTile);
}
if (filePath) {