From 4d82dd22b6482a9c01dd4922bebdb5b9918a160f Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Mon, 14 Feb 2022 17:50:17 +0100 Subject: [PATCH 001/238] convert ViewModel to typescript --- src/domain/{ViewModel.js => ViewModel.ts} | 67 ++++++++++++++--------- src/utils/Disposables.ts | 23 ++++---- 2 files changed, 52 insertions(+), 38 deletions(-) rename src/domain/{ViewModel.js => ViewModel.ts} (65%) diff --git a/src/domain/ViewModel.js b/src/domain/ViewModel.ts similarity index 65% rename from src/domain/ViewModel.js rename to src/domain/ViewModel.ts index 0c665194..99b23918 100644 --- a/src/domain/ViewModel.js +++ b/src/domain/ViewModel.ts @@ -1,5 +1,6 @@ /* Copyright 2020 Bruno Windels +Copyright 2022 The Matrix.org Foundation C.I.C. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -21,54 +22,70 @@ limitations under the License. import {EventEmitter} from "../utils/EventEmitter"; import {Disposables} from "../utils/Disposables"; -export class ViewModel extends EventEmitter { - constructor(options = {}) { +import type {Disposable} from "../utils/Disposables"; +import type {Platform} from "../platform/web/Platform"; +import type {Clock} from "../platform/web/dom/Clock"; +import type {ILogger} from "../logging/types"; +import type {Navigation} from "./navigation/Navigation"; +import type {URLRouter} from "./navigation/URLRouter"; + +type Options = { + platform: Platform + logger: ILogger + urlCreator: URLRouter + navigation: Navigation + emitChange?: (params: any) => void +} + +export class ViewModel extends EventEmitter<{change: never}> { + private disposables?: Disposables; + private _isDisposed = false; + private _options: O; + + constructor(options: O) { super(); - this.disposables = null; - this._isDisposed = false; this._options = options; } - childOptions(explicitOptions) { - const {navigation, urlCreator, platform} = this._options; - return Object.assign({navigation, urlCreator, platform}, explicitOptions); + childOptions(explicitOptions: T): T & Options { + return Object.assign({}, this._options, explicitOptions); } // makes it easier to pass through dependencies of a sub-view model - getOption(name) { + getOption(name: N): O[N] { return this._options[name]; } - track(disposable) { + track(disposable: D): D { if (!this.disposables) { this.disposables = new Disposables(); } return this.disposables.track(disposable); } - untrack(disposable) { + untrack(disposable: Disposable): undefined { if (this.disposables) { return this.disposables.untrack(disposable); } - return null; + return undefined; } - dispose() { + dispose(): void { if (this.disposables) { this.disposables.dispose(); } this._isDisposed = true; } - get isDisposed() { + get isDisposed(): boolean { return this._isDisposed; } - disposeTracked(disposable) { + disposeTracked(disposable: Disposable | undefined): undefined { if (this.disposables) { return this.disposables.disposeTracked(disposable); } - return null; + return undefined; } // TODO: this will need to support binding @@ -76,7 +93,7 @@ export class ViewModel extends EventEmitter { // // translated string should probably always be bindings, unless we're fine with a refresh when changing the language? // we probably are, if we're using routing with a url, we could just refresh. - i18n(parts, ...expr) { + i18n(parts: string[], ...expr: any[]) { // just concat for now let result = ""; for (let i = 0; i < parts.length; ++i) { @@ -88,11 +105,11 @@ export class ViewModel extends EventEmitter { return result; } - updateOptions(options) { + updateOptions(options: O): void { this._options = Object.assign(this._options, options); } - emitChange(changedProps) { + emitChange(changedProps: any): void { if (this._options.emitChange) { this._options.emitChange(changedProps); } else { @@ -100,27 +117,23 @@ export class ViewModel extends EventEmitter { } } - get platform() { + get platform(): Platform { return this._options.platform; } - get clock() { + get clock(): Clock { return this._options.platform.clock; } - get logger() { + get logger(): ILogger { return this.platform.logger; } - /** - * The url router, only meant to be used to create urls with from view models. - * @return {URLRouter} - */ - get urlCreator() { + get urlCreator(): URLRouter { return this._options.urlCreator; } - get navigation() { + get navigation(): Navigation { return this._options.navigation; } } diff --git a/src/utils/Disposables.ts b/src/utils/Disposables.ts index 19a5983c..f7c7eb53 100644 --- a/src/utils/Disposables.ts +++ b/src/utils/Disposables.ts @@ -1,5 +1,6 @@ /* Copyright 2020 Bruno Windels +Copyright 2022 The Matrix.org Foundation C.I.C. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -18,7 +19,7 @@ export interface IDisposable { dispose(): void; } -type Disposable = IDisposable | (() => void); +export type Disposable = IDisposable | (() => void); function disposeValue(value: Disposable): void { if (typeof value === "function") { @@ -33,9 +34,9 @@ function isDisposable(value: Disposable): boolean { } export class Disposables { - private _disposables: Disposable[] | null = []; + private _disposables?: Disposable[] = []; - track(disposable: Disposable): Disposable { + track(disposable: D): D { if (!isDisposable(disposable)) { throw new Error("Not a disposable"); } @@ -48,16 +49,16 @@ export class Disposables { return disposable; } - untrack(disposable: Disposable): null { + untrack(disposable: Disposable): undefined { if (this.isDisposed) { console.warn("Disposables already disposed, cannot untrack"); - return null; + return undefined; } const idx = this._disposables!.indexOf(disposable); if (idx >= 0) { this._disposables!.splice(idx, 1); } - return null; + return undefined; } dispose(): void { @@ -65,17 +66,17 @@ export class Disposables { for (const d of this._disposables) { disposeValue(d); } - this._disposables = null; + this._disposables = undefined; } } get isDisposed(): boolean { - return this._disposables === null; + return this._disposables === undefined; } - disposeTracked(value: Disposable): null { + disposeTracked(value: Disposable | undefined): undefined { if (value === undefined || value === null || this.isDisposed) { - return null; + return undefined; } const idx = this._disposables!.indexOf(value); if (idx !== -1) { @@ -84,6 +85,6 @@ export class Disposables { } else { console.warn("disposable not found, did it leak?", value); } - return null; + return undefined; } } From 1795f58ba5bbdddcc5854a6967dccbebb6873320 Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Mon, 14 Feb 2022 17:53:59 +0100 Subject: [PATCH 002/238] rename imports --- src/domain/AccountSetupViewModel.js | 2 +- src/domain/LogoutViewModel.js | 2 +- src/domain/RootViewModel.js | 2 +- src/domain/SessionLoadViewModel.js | 2 +- src/domain/SessionPickerViewModel.js | 2 +- src/domain/login/CompleteSSOLoginViewModel.js | 2 +- src/domain/login/LoginViewModel.js | 2 +- src/domain/login/PasswordLoginViewModel.js | 2 +- src/domain/login/StartSSOLoginViewModel.js | 2 +- src/domain/session/CreateRoomViewModel.js | 2 +- src/domain/session/RoomGridViewModel.js | 2 +- src/domain/session/SessionStatusViewModel.js | 2 +- src/domain/session/SessionViewModel.js | 2 +- src/domain/session/leftpanel/BaseTileViewModel.js | 2 +- src/domain/session/leftpanel/LeftPanelViewModel.js | 2 +- src/domain/session/rightpanel/MemberDetailsViewModel.js | 2 +- src/domain/session/rightpanel/MemberListViewModel.js | 2 +- src/domain/session/rightpanel/MemberTileViewModel.js | 2 +- src/domain/session/rightpanel/RightPanelViewModel.js | 2 +- src/domain/session/rightpanel/RoomDetailsViewModel.js | 2 +- src/domain/session/room/ComposerViewModel.js | 2 +- src/domain/session/room/InviteViewModel.js | 2 +- src/domain/session/room/LightboxViewModel.js | 2 +- src/domain/session/room/RoomBeingCreatedViewModel.js | 2 +- src/domain/session/room/RoomViewModel.js | 2 +- src/domain/session/room/UnknownRoomViewModel.js | 4 ++-- src/domain/session/room/timeline/TimelineViewModel.js | 2 +- src/domain/session/settings/KeyBackupViewModel.js | 2 +- src/domain/session/settings/SettingsViewModel.js | 2 +- src/lib.ts | 2 +- 30 files changed, 31 insertions(+), 31 deletions(-) diff --git a/src/domain/AccountSetupViewModel.js b/src/domain/AccountSetupViewModel.js index 4ad0d8d5..74d680d0 100644 --- a/src/domain/AccountSetupViewModel.js +++ b/src/domain/AccountSetupViewModel.js @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -import {ViewModel} from "./ViewModel.js"; +import {ViewModel} from "./ViewModel"; import {KeyType} from "../matrix/ssss/index"; import {Status} from "./session/settings/KeyBackupViewModel.js"; diff --git a/src/domain/LogoutViewModel.js b/src/domain/LogoutViewModel.js index f22637de..24ca440e 100644 --- a/src/domain/LogoutViewModel.js +++ b/src/domain/LogoutViewModel.js @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -import {ViewModel} from "./ViewModel.js"; +import {ViewModel} from "./ViewModel"; import {Client} from "../matrix/Client.js"; export class LogoutViewModel extends ViewModel { diff --git a/src/domain/RootViewModel.js b/src/domain/RootViewModel.js index 70f5b554..642e43f4 100644 --- a/src/domain/RootViewModel.js +++ b/src/domain/RootViewModel.js @@ -20,7 +20,7 @@ import {SessionLoadViewModel} from "./SessionLoadViewModel.js"; import {LoginViewModel} from "./login/LoginViewModel.js"; import {LogoutViewModel} from "./LogoutViewModel.js"; import {SessionPickerViewModel} from "./SessionPickerViewModel.js"; -import {ViewModel} from "./ViewModel.js"; +import {ViewModel} from "./ViewModel"; export class RootViewModel extends ViewModel { constructor(options) { diff --git a/src/domain/SessionLoadViewModel.js b/src/domain/SessionLoadViewModel.js index 24df2546..b23b54bc 100644 --- a/src/domain/SessionLoadViewModel.js +++ b/src/domain/SessionLoadViewModel.js @@ -17,7 +17,7 @@ limitations under the License. import {AccountSetupViewModel} from "./AccountSetupViewModel.js"; import {LoadStatus} from "../matrix/Client.js"; import {SyncStatus} from "../matrix/Sync.js"; -import {ViewModel} from "./ViewModel.js"; +import {ViewModel} from "./ViewModel"; export class SessionLoadViewModel extends ViewModel { constructor(options) { diff --git a/src/domain/SessionPickerViewModel.js b/src/domain/SessionPickerViewModel.js index e4bbc7ec..6714e96f 100644 --- a/src/domain/SessionPickerViewModel.js +++ b/src/domain/SessionPickerViewModel.js @@ -15,7 +15,7 @@ limitations under the License. */ import {SortedArray} from "../observable/index.js"; -import {ViewModel} from "./ViewModel.js"; +import {ViewModel} from "./ViewModel"; import {avatarInitials, getIdentifierColorNumber} from "./avatar.js"; class SessionItemViewModel extends ViewModel { diff --git a/src/domain/login/CompleteSSOLoginViewModel.js b/src/domain/login/CompleteSSOLoginViewModel.js index daa2aa9f..d41d53ec 100644 --- a/src/domain/login/CompleteSSOLoginViewModel.js +++ b/src/domain/login/CompleteSSOLoginViewModel.js @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -import {ViewModel} from "../ViewModel.js"; +import {ViewModel} from "../ViewModel"; import {LoginFailure} from "../../matrix/Client.js"; export class CompleteSSOLoginViewModel extends ViewModel { diff --git a/src/domain/login/LoginViewModel.js b/src/domain/login/LoginViewModel.js index b91df4cc..bf77e624 100644 --- a/src/domain/login/LoginViewModel.js +++ b/src/domain/login/LoginViewModel.js @@ -15,7 +15,7 @@ limitations under the License. */ import {Client} from "../../matrix/Client.js"; -import {ViewModel} from "../ViewModel.js"; +import {ViewModel} from "../ViewModel"; import {PasswordLoginViewModel} from "./PasswordLoginViewModel.js"; import {StartSSOLoginViewModel} from "./StartSSOLoginViewModel.js"; import {CompleteSSOLoginViewModel} from "./CompleteSSOLoginViewModel.js"; diff --git a/src/domain/login/PasswordLoginViewModel.js b/src/domain/login/PasswordLoginViewModel.js index 5fd8271f..7c4ff78a 100644 --- a/src/domain/login/PasswordLoginViewModel.js +++ b/src/domain/login/PasswordLoginViewModel.js @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -import {ViewModel} from "../ViewModel.js"; +import {ViewModel} from "../ViewModel"; import {LoginFailure} from "../../matrix/Client.js"; export class PasswordLoginViewModel extends ViewModel { diff --git a/src/domain/login/StartSSOLoginViewModel.js b/src/domain/login/StartSSOLoginViewModel.js index 54218d22..dba0bcb5 100644 --- a/src/domain/login/StartSSOLoginViewModel.js +++ b/src/domain/login/StartSSOLoginViewModel.js @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -import {ViewModel} from "../ViewModel.js"; +import {ViewModel} from "../ViewModel"; export class StartSSOLoginViewModel extends ViewModel{ constructor(options) { diff --git a/src/domain/session/CreateRoomViewModel.js b/src/domain/session/CreateRoomViewModel.js index 51a9b7a4..12b4fbd5 100644 --- a/src/domain/session/CreateRoomViewModel.js +++ b/src/domain/session/CreateRoomViewModel.js @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -import {ViewModel} from "../ViewModel.js"; +import {ViewModel} from "../ViewModel"; import {imageToInfo} from "./common.js"; import {RoomType} from "../../matrix/room/common"; diff --git a/src/domain/session/RoomGridViewModel.js b/src/domain/session/RoomGridViewModel.js index d89d821a..a7d19054 100644 --- a/src/domain/session/RoomGridViewModel.js +++ b/src/domain/session/RoomGridViewModel.js @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -import {ViewModel} from "../ViewModel.js"; +import {ViewModel} from "../ViewModel"; import {addPanelIfNeeded} from "../navigation/index.js"; function dedupeSparse(roomIds) { diff --git a/src/domain/session/SessionStatusViewModel.js b/src/domain/session/SessionStatusViewModel.js index 3f2263ac..8f1d0748 100644 --- a/src/domain/session/SessionStatusViewModel.js +++ b/src/domain/session/SessionStatusViewModel.js @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -import {ViewModel} from "../ViewModel.js"; +import {ViewModel} from "../ViewModel"; import {createEnum} from "../../utils/enum"; import {ConnectionStatus} from "../../matrix/net/Reconnector"; import {SyncStatus} from "../../matrix/Sync.js"; diff --git a/src/domain/session/SessionViewModel.js b/src/domain/session/SessionViewModel.js index 24276f42..a67df3a7 100644 --- a/src/domain/session/SessionViewModel.js +++ b/src/domain/session/SessionViewModel.js @@ -25,7 +25,7 @@ import {SessionStatusViewModel} from "./SessionStatusViewModel.js"; import {RoomGridViewModel} from "./RoomGridViewModel.js"; import {SettingsViewModel} from "./settings/SettingsViewModel.js"; import {CreateRoomViewModel} from "./CreateRoomViewModel.js"; -import {ViewModel} from "../ViewModel.js"; +import {ViewModel} from "../ViewModel"; import {RoomViewModelObservable} from "./RoomViewModelObservable.js"; import {RightPanelViewModel} from "./rightpanel/RightPanelViewModel.js"; diff --git a/src/domain/session/leftpanel/BaseTileViewModel.js b/src/domain/session/leftpanel/BaseTileViewModel.js index b360b1d4..e1d6dfff 100644 --- a/src/domain/session/leftpanel/BaseTileViewModel.js +++ b/src/domain/session/leftpanel/BaseTileViewModel.js @@ -16,7 +16,7 @@ limitations under the License. */ import {avatarInitials, getIdentifierColorNumber, getAvatarHttpUrl} from "../../avatar.js"; -import {ViewModel} from "../../ViewModel.js"; +import {ViewModel} from "../../ViewModel"; const KIND_ORDER = ["roomBeingCreated", "invite", "room"]; diff --git a/src/domain/session/leftpanel/LeftPanelViewModel.js b/src/domain/session/leftpanel/LeftPanelViewModel.js index 843ed1ca..2fd3ca7e 100644 --- a/src/domain/session/leftpanel/LeftPanelViewModel.js +++ b/src/domain/session/leftpanel/LeftPanelViewModel.js @@ -15,7 +15,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -import {ViewModel} from "../../ViewModel.js"; +import {ViewModel} from "../../ViewModel"; import {RoomTileViewModel} from "./RoomTileViewModel.js"; import {InviteTileViewModel} from "./InviteTileViewModel.js"; import {RoomBeingCreatedTileViewModel} from "./RoomBeingCreatedTileViewModel.js"; diff --git a/src/domain/session/rightpanel/MemberDetailsViewModel.js b/src/domain/session/rightpanel/MemberDetailsViewModel.js index f6cbd747..8ee50030 100644 --- a/src/domain/session/rightpanel/MemberDetailsViewModel.js +++ b/src/domain/session/rightpanel/MemberDetailsViewModel.js @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -import {ViewModel} from "../../ViewModel.js"; +import {ViewModel} from "../../ViewModel"; import {RoomType} from "../../../matrix/room/common"; import {avatarInitials, getIdentifierColorNumber, getAvatarHttpUrl} from "../../avatar.js"; diff --git a/src/domain/session/rightpanel/MemberListViewModel.js b/src/domain/session/rightpanel/MemberListViewModel.js index 5b8bb83e..b75a3d1c 100644 --- a/src/domain/session/rightpanel/MemberListViewModel.js +++ b/src/domain/session/rightpanel/MemberListViewModel.js @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -import {ViewModel} from "../../ViewModel.js"; +import {ViewModel} from "../../ViewModel"; import {MemberTileViewModel} from "./MemberTileViewModel.js"; import {createMemberComparator} from "./members/comparator.js"; import {Disambiguator} from "./members/disambiguator.js"; diff --git a/src/domain/session/rightpanel/MemberTileViewModel.js b/src/domain/session/rightpanel/MemberTileViewModel.js index eac6a6d4..9062ea7d 100644 --- a/src/domain/session/rightpanel/MemberTileViewModel.js +++ b/src/domain/session/rightpanel/MemberTileViewModel.js @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -import {ViewModel} from "../../ViewModel.js"; +import {ViewModel} from "../../ViewModel"; import {avatarInitials, getIdentifierColorNumber, getAvatarHttpUrl} from "../../avatar.js"; export class MemberTileViewModel extends ViewModel { diff --git a/src/domain/session/rightpanel/RightPanelViewModel.js b/src/domain/session/rightpanel/RightPanelViewModel.js index 3cfe378b..b4b6b4eb 100644 --- a/src/domain/session/rightpanel/RightPanelViewModel.js +++ b/src/domain/session/rightpanel/RightPanelViewModel.js @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -import {ViewModel} from "../../ViewModel.js"; +import {ViewModel} from "../../ViewModel"; import {RoomDetailsViewModel} from "./RoomDetailsViewModel.js"; import {MemberListViewModel} from "./MemberListViewModel.js"; import {MemberDetailsViewModel} from "./MemberDetailsViewModel.js"; diff --git a/src/domain/session/rightpanel/RoomDetailsViewModel.js b/src/domain/session/rightpanel/RoomDetailsViewModel.js index 5e509fd5..97e8588e 100644 --- a/src/domain/session/rightpanel/RoomDetailsViewModel.js +++ b/src/domain/session/rightpanel/RoomDetailsViewModel.js @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -import {ViewModel} from "../../ViewModel.js"; +import {ViewModel} from "../../ViewModel"; import {avatarInitials, getIdentifierColorNumber, getAvatarHttpUrl} from "../../avatar.js"; export class RoomDetailsViewModel extends ViewModel { diff --git a/src/domain/session/room/ComposerViewModel.js b/src/domain/session/room/ComposerViewModel.js index 730e1b20..833b17f5 100644 --- a/src/domain/session/room/ComposerViewModel.js +++ b/src/domain/session/room/ComposerViewModel.js @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -import {ViewModel} from "../../ViewModel.js"; +import {ViewModel} from "../../ViewModel"; export class ComposerViewModel extends ViewModel { constructor(roomVM) { diff --git a/src/domain/session/room/InviteViewModel.js b/src/domain/session/room/InviteViewModel.js index 81a08e44..c2ff74e0 100644 --- a/src/domain/session/room/InviteViewModel.js +++ b/src/domain/session/room/InviteViewModel.js @@ -16,7 +16,7 @@ limitations under the License. */ import {avatarInitials, getIdentifierColorNumber, getAvatarHttpUrl} from "../../avatar.js"; -import {ViewModel} from "../../ViewModel.js"; +import {ViewModel} from "../../ViewModel"; export class InviteViewModel extends ViewModel { constructor(options) { diff --git a/src/domain/session/room/LightboxViewModel.js b/src/domain/session/room/LightboxViewModel.js index f6da39b0..8ce8757a 100644 --- a/src/domain/session/room/LightboxViewModel.js +++ b/src/domain/session/room/LightboxViewModel.js @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -import {ViewModel} from "../../ViewModel.js"; +import {ViewModel} from "../../ViewModel"; export class LightboxViewModel extends ViewModel { constructor(options) { diff --git a/src/domain/session/room/RoomBeingCreatedViewModel.js b/src/domain/session/room/RoomBeingCreatedViewModel.js index f98c86f9..f5c5d3cd 100644 --- a/src/domain/session/room/RoomBeingCreatedViewModel.js +++ b/src/domain/session/room/RoomBeingCreatedViewModel.js @@ -16,7 +16,7 @@ limitations under the License. */ import {avatarInitials, getIdentifierColorNumber, getAvatarHttpUrl} from "../../avatar.js"; -import {ViewModel} from "../../ViewModel.js"; +import {ViewModel} from "../../ViewModel"; export class RoomBeingCreatedViewModel extends ViewModel { constructor(options) { diff --git a/src/domain/session/room/RoomViewModel.js b/src/domain/session/room/RoomViewModel.js index 2d10c7ca..b7af00ce 100644 --- a/src/domain/session/room/RoomViewModel.js +++ b/src/domain/session/room/RoomViewModel.js @@ -19,7 +19,7 @@ import {TimelineViewModel} from "./timeline/TimelineViewModel.js"; import {ComposerViewModel} from "./ComposerViewModel.js" import {avatarInitials, getIdentifierColorNumber, getAvatarHttpUrl} from "../../avatar.js"; import {tilesCreator} from "./timeline/tilesCreator.js"; -import {ViewModel} from "../../ViewModel.js"; +import {ViewModel} from "../../ViewModel"; import {imageToInfo} from "../common.js"; export class RoomViewModel extends ViewModel { diff --git a/src/domain/session/room/UnknownRoomViewModel.js b/src/domain/session/room/UnknownRoomViewModel.js index e7969298..8bb5fb0a 100644 --- a/src/domain/session/room/UnknownRoomViewModel.js +++ b/src/domain/session/room/UnknownRoomViewModel.js @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -import {ViewModel} from "../../ViewModel.js"; +import {ViewModel} from "../../ViewModel"; export class UnknownRoomViewModel extends ViewModel { constructor(options) { @@ -55,4 +55,4 @@ export class UnknownRoomViewModel extends ViewModel { get kind() { return "unknown"; } -} \ No newline at end of file +} diff --git a/src/domain/session/room/timeline/TimelineViewModel.js b/src/domain/session/room/timeline/TimelineViewModel.js index 9c936218..2408146d 100644 --- a/src/domain/session/room/timeline/TimelineViewModel.js +++ b/src/domain/session/room/timeline/TimelineViewModel.js @@ -32,7 +32,7 @@ to the room timeline, which unload entries from memory. when loading, it just reads events from a sortkey backwards or forwards... */ import {TilesCollection} from "./TilesCollection.js"; -import {ViewModel} from "../../../ViewModel.js"; +import {ViewModel} from "../../../ViewModel"; export class TimelineViewModel extends ViewModel { constructor(options) { diff --git a/src/domain/session/settings/KeyBackupViewModel.js b/src/domain/session/settings/KeyBackupViewModel.js index b44de7e5..243b0d7c 100644 --- a/src/domain/session/settings/KeyBackupViewModel.js +++ b/src/domain/session/settings/KeyBackupViewModel.js @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -import {ViewModel} from "../../ViewModel.js"; +import {ViewModel} from "../../ViewModel"; import {KeyType} from "../../../matrix/ssss/index"; import {createEnum} from "../../../utils/enum"; diff --git a/src/domain/session/settings/SettingsViewModel.js b/src/domain/session/settings/SettingsViewModel.js index 0b68f168..7464a659 100644 --- a/src/domain/session/settings/SettingsViewModel.js +++ b/src/domain/session/settings/SettingsViewModel.js @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -import {ViewModel} from "../../ViewModel.js"; +import {ViewModel} from "../../ViewModel"; import {KeyBackupViewModel} from "./KeyBackupViewModel.js"; class PushNotificationStatus { diff --git a/src/lib.ts b/src/lib.ts index 3e191d45..cc88690c 100644 --- a/src/lib.ts +++ b/src/lib.ts @@ -30,6 +30,6 @@ export {Navigation} from "./domain/navigation/Navigation.js"; export {ComposerViewModel} from "./domain/session/room/ComposerViewModel.js"; export {MessageComposer} from "./platform/web/ui/session/room/MessageComposer.js"; export {TemplateView} from "./platform/web/ui/general/TemplateView"; -export {ViewModel} from "./domain/ViewModel.js"; +export {ViewModel} from "./domain/ViewModel"; export {LoadingView} from "./platform/web/ui/general/LoadingView.js"; export {AvatarView} from "./platform/web/ui/AvatarView.js"; From 1a159f9e9a834026af3a28d8a57782937225a9c8 Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Mon, 14 Feb 2022 18:01:04 +0100 Subject: [PATCH 003/238] WIP --- CONTRIBUTING.md | 191 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 191 insertions(+) create mode 100644 CONTRIBUTING.md diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 00000000..80f1fc6a --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,191 @@ +Contributing code to matrix-js-sdk +================================== + +Everyone is welcome to contribute code to hydrogen-web, provided that they are +willing to license their contributions under the same license as the project +itself. We follow a simple 'inbound=outbound' model for contributions: the act +of submitting an 'inbound' contribution means that the contributor agrees to +license the code under the same terms as the project's overall 'outbound' +license - in this case, Apache Software License v2 (see +[LICENSE](LICENSE)). + +How to contribute +----------------- + +The preferred and easiest way to contribute changes to the project is to fork +it on github, and then create a pull request to ask us to pull your changes +into our repo (https://help.github.com/articles/using-pull-requests/) + +We use GitHub's pull request workflow to review the contribution, and either +ask you to make any refinements needed or merge it and make them ourselves. + +Things that should go into your PR description: + * Please disable any automatic formatting tools you may have active. + You'll be asked to undo any unrelated whitespace changes during code review. + * References to any bugs fixed by the change (in GitHub's `Fixes` notation) + * Describe the why and what is changing in the PR description so it's easy for + onlookers and reviewers to onboard and context switch. + * Include both **before** and **after** screenshots to easily compare and discuss + what's changing. + * Include a step-by-step testing strategy so that a reviewer can check out the + code locally and easily get to the point of testing your change. + * Add comments to the diff for the reviewer that might help them to understand + why the change is necessary or how they might better understand and review it. + +To add a longer, more detailed description of the change for the changelog: + + +*Fix llama herding bug* + +``` +Notes: Fix a bug (https://github.com/matrix-org/notaproject/issues/123) where the 'Herd' button would not herd more than 8 Llamas if the moon was in the waxing gibbous phase +``` + +*Remove outdated comment from `Ungulates.ts`* +``` +Notes: none +``` + +Sometimes, you're fixing a bug in a downstream project, in which case you want +an entry in that project's changelog. You can do that too: + +*Fix another herding bug* +``` +Notes: Fix a bug where the `herd()` function would only work on Tuesdays +element-web notes: Fix a bug where the 'Herd' button only worked on Tuesdays +``` + +If your PR introduces a breaking change, add the `X-Breaking-Change` label (see below) +and remember to tell the developer how to migrate: + +*Remove legacy class* + +``` +Notes: Remove legacy `Camelopard` class. `Giraffe` should be used instead. +``` + +Other metadata can be added using labels. + * `X-Breaking-Change`: A breaking change - adding this label will mean the change causes a *major* version bump. + +If you don't have permission to add labels, your PR reviewer(s) can work with you +to add them: ask in the PR description or comments. + +We use continuous integration, and all pull requests get automatically tested: +if your change breaks the build, then the PR will show that there are failed +checks, so please check back after a few minutes. + +Tests +----- +If your PR is a feature (ie. if it's being labelled with the 'T-Enhancement' +label) then we require that the PR also includes tests. These need to test that +your feature works as expected and ideally test edge cases too. For the js-sdk +itself, your tests should generally be unit tests. matrix-react-sdk also uses +these guidelines, so for that your tests can be unit tests using +react-test-utils, snapshot tests or screenshot tests. + +We don't require tests for bug fixes (T-Defect) but strongly encourage regression +tests for the bug itself wherever possible. + +In the future we may formalise this more with a minimum test coverage +percentage for the diff. + +Code style +---------- +The js-sdk aims to target TypeScript/ES6. All new files should be written in +TypeScript and existing files should use ES6 principles where possible. + +Members should not be exported as a default export in general - it causes problems +with the architecture of the SDK (index file becomes less clear) and could +introduce naming problems (as default exports get aliased upon import). In +general, avoid using `export default`. + +The remaining code-style for matrix-js-sdk is not formally documented, but +contributors are encouraged to read the +[code style document for matrix-react-sdk](https://github.com/matrix-org/matrix-react-sdk/blob/master/code_style.md) +and follow the principles set out there. + +Please ensure your changes match the cosmetic style of the existing project, +and ***never*** mix cosmetic and functional changes in the same commit, as it +makes it horribly hard to review otherwise. + +Attribution +----------- +Everyone who contributes anything to Matrix is welcome to be listed in the +AUTHORS.rst file for the project in question. Please feel free to include a +change to AUTHORS.rst in your pull request to list yourself and a short +description of the area(s) you've worked on. Also, we sometimes have swag to +give away to contributors - if you feel that Matrix-branded apparel is missing +from your life, please mail us your shipping address to matrix at matrix.org +and we'll try to fix it :) + +Sign off +-------- +In order to have a concrete record that your contribution is intentional +and you agree to license it under the same terms as the project's license, we've +adopted the same lightweight approach that the Linux Kernel +(https://www.kernel.org/doc/Documentation/SubmittingPatches), Docker +(https://github.com/docker/docker/blob/master/CONTRIBUTING.md), and many other +projects use: the DCO (Developer Certificate of Origin: +http://developercertificate.org/). This is a simple declaration that you wrote +the contribution or otherwise have the right to contribute it to Matrix: + +``` +Developer Certificate of Origin +Version 1.1 + +Copyright (C) 2004, 2006 The Linux Foundation and its contributors. +660 York Street, Suite 102, +San Francisco, CA 94110 USA + +Everyone is permitted to copy and distribute verbatim copies of this +license document, but changing it is not allowed. + +Developer's Certificate of Origin 1.1 + +By making a contribution to this project, I certify that: + +(a) The contribution was created in whole or in part by me and I + have the right to submit it under the open source license + indicated in the file; or + +(b) The contribution is based upon previous work that, to the best + of my knowledge, is covered under an appropriate open source + license and I have the right under that license to submit that + work with modifications, whether created in whole or in part + by me, under the same open source license (unless I am + permitted to submit under a different license), as indicated + in the file; or + +(c) The contribution was provided directly to me by some other + person who certified (a), (b) or (c) and I have not modified + it. + +(d) I understand and agree that this project and the contribution + are public and that a record of the contribution (including all + personal information I submit with it, including my sign-off) is + maintained indefinitely and may be redistributed consistent with + this project or the open source license(s) involved. +``` + +If you agree to this for your contribution, then all that's needed is to +include the line in your commit or pull request comment: + +``` +Signed-off-by: Your Name +``` + +We accept contributions under a legally identifiable name, such as your name on +government documentation or common-law names (names claimed by legitimate usage +or repute). Unfortunately, we cannot accept anonymous contributions at this +time. + +Git allows you to add this signoff automatically when using the `-s` flag to +`git commit`, which uses the name and email set in your `user.name` and +`user.email` git configs. + +If you forgot to sign off your commits before making your pull request and are +on Git 2.17+ you can mass signoff using rebase: + +``` +git rebase --signoff origin/develop +``` From 7179758c5051b6273195f88fa027fb6607bd5e5d Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Tue, 15 Feb 2022 08:22:09 +0100 Subject: [PATCH 004/238] also here --- src/domain/session/room/timeline/tiles/SimpleTile.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/domain/session/room/timeline/tiles/SimpleTile.js b/src/domain/session/room/timeline/tiles/SimpleTile.js index 4c1c1de0..3497b689 100644 --- a/src/domain/session/room/timeline/tiles/SimpleTile.js +++ b/src/domain/session/room/timeline/tiles/SimpleTile.js @@ -15,7 +15,7 @@ limitations under the License. */ import {UpdateAction} from "../UpdateAction.js"; -import {ViewModel} from "../../../../ViewModel.js"; +import {ViewModel} from "../../../../ViewModel"; import {SendStatus} from "../../../../../matrix/room/sending/PendingEvent.js"; export class SimpleTile extends ViewModel { From dea1e7eaf36896d1db89921b68cb24251eb6af5e Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Tue, 15 Feb 2022 11:31:50 +0100 Subject: [PATCH 005/238] bump sdk version --- scripts/sdk/base-manifest.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/sdk/base-manifest.json b/scripts/sdk/base-manifest.json index 3ee2ca3b..3e468181 100644 --- a/scripts/sdk/base-manifest.json +++ b/scripts/sdk/base-manifest.json @@ -1,7 +1,7 @@ { "name": "hydrogen-view-sdk", "description": "Embeddable matrix client library, including view components", - "version": "0.0.5", + "version": "0.0.6", "main": "./hydrogen.es.js", "type": "module" } From 7aeda70ff66614c74e69a4345202ebcbbfde8769 Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Tue, 15 Feb 2022 18:19:49 +0100 Subject: [PATCH 006/238] convert DecryptionResult --- ...ecryptionResult.js => DecryptionResult.ts} | 44 +++++++++++-------- .../megolm/decryption/SessionDecryption.ts | 2 +- 2 files changed, 26 insertions(+), 20 deletions(-) rename src/matrix/e2ee/{DecryptionResult.js => DecryptionResult.ts} (66%) diff --git a/src/matrix/e2ee/DecryptionResult.js b/src/matrix/e2ee/DecryptionResult.ts similarity index 66% rename from src/matrix/e2ee/DecryptionResult.js rename to src/matrix/e2ee/DecryptionResult.ts index e1c2bcc4..67c242bc 100644 --- a/src/matrix/e2ee/DecryptionResult.js +++ b/src/matrix/e2ee/DecryptionResult.ts @@ -26,35 +26,41 @@ limitations under the License. * see DeviceTracker */ +import type {DeviceIdentity} from "../storage/idb/stores/DeviceIdentityStore"; +type DecryptedEvent = { + type?: string, + content?: Record +} export class DecryptionResult { - constructor(event, senderCurve25519Key, claimedEd25519Key) { - this.event = event; - this.senderCurve25519Key = senderCurve25519Key; - this.claimedEd25519Key = claimedEd25519Key; - this._device = null; - this._roomTracked = true; + private device?: DeviceIdentity; + private roomTracked: boolean = true; + + constructor( + public readonly event: DecryptedEvent, + public readonly senderCurve25519Key: string, + public readonly claimedEd25519Key: string + ) {} + + setDevice(device: DeviceIdentity) { + this.device = device; } - setDevice(device) { - this._device = device; + setRoomNotTrackedYet(): void { + this.roomTracked = false; } - setRoomNotTrackedYet() { - this._roomTracked = false; - } - - get isVerified() { - if (this._device) { - const comesFromDevice = this._device.ed25519Key === this.claimedEd25519Key; + get isVerified(): boolean { + if (this.device) { + const comesFromDevice = this.device.ed25519Key === this.claimedEd25519Key; return comesFromDevice; } return false; } - get isUnverified() { - if (this._device) { + get isUnverified(): boolean { + if (this.device) { return !this.isVerified; } else if (this.isVerificationUnknown) { return false; @@ -63,8 +69,8 @@ export class DecryptionResult { } } - get isVerificationUnknown() { + get isVerificationUnknown(): boolean { // verification is unknown if we haven't yet fetched the devices for the room - return !this._device && !this._roomTracked; + return !this.device && !this.roomTracked; } } diff --git a/src/matrix/e2ee/megolm/decryption/SessionDecryption.ts b/src/matrix/e2ee/megolm/decryption/SessionDecryption.ts index f56feb47..57ef9a96 100644 --- a/src/matrix/e2ee/megolm/decryption/SessionDecryption.ts +++ b/src/matrix/e2ee/megolm/decryption/SessionDecryption.ts @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -import {DecryptionResult} from "../../DecryptionResult.js"; +import {DecryptionResult} from "../../DecryptionResult"; import {DecryptionError} from "../../common.js"; import {ReplayDetectionEntry} from "./ReplayDetectionEntry"; import type {RoomKey} from "./RoomKey"; From 74c640f9375d59f84ca450d4635c77b2829be94b Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Tue, 15 Feb 2022 18:20:49 +0100 Subject: [PATCH 007/238] convert Session --- src/matrix/e2ee/olm/Encryption.js | 2 +- .../e2ee/olm/{Session.js => Session.ts} | 35 +++++++++++-------- .../storage/idb/stores/OlmSessionStore.ts | 18 +++++----- 3 files changed, 31 insertions(+), 24 deletions(-) rename src/matrix/e2ee/olm/{Session.js => Session.ts} (53%) diff --git a/src/matrix/e2ee/olm/Encryption.js b/src/matrix/e2ee/olm/Encryption.js index 652c657c..3e78470d 100644 --- a/src/matrix/e2ee/olm/Encryption.js +++ b/src/matrix/e2ee/olm/Encryption.js @@ -16,7 +16,7 @@ limitations under the License. import {groupByWithCreator} from "../../../utils/groupBy"; import {verifyEd25519Signature, OLM_ALGORITHM} from "../common.js"; -import {createSessionEntry} from "./Session.js"; +import {createSessionEntry} from "./Session"; function findFirstSessionId(sessionIds) { return sessionIds.reduce((first, sessionId) => { diff --git a/src/matrix/e2ee/olm/Session.js b/src/matrix/e2ee/olm/Session.ts similarity index 53% rename from src/matrix/e2ee/olm/Session.js rename to src/matrix/e2ee/olm/Session.ts index 9b5f4db0..f97c8478 100644 --- a/src/matrix/e2ee/olm/Session.js +++ b/src/matrix/e2ee/olm/Session.ts @@ -14,7 +14,11 @@ See the License for the specific language governing permissions and limitations under the License. */ -export function createSessionEntry(olmSession, senderKey, timestamp, pickleKey) { +import type {OlmSessionEntry} from "../../storage/idb/stores/OlmSessionStore"; +import type * as OlmNamespace from "@matrix-org/olm"; +type Olm = typeof OlmNamespace; + +export function createSessionEntry(olmSession: Olm.Session, senderKey: string, timestamp: number, pickleKey: string): OlmSessionEntry { return { session: olmSession.pickle(pickleKey), sessionId: olmSession.session_id(), @@ -24,35 +28,38 @@ export function createSessionEntry(olmSession, senderKey, timestamp, pickleKey) } export class Session { - constructor(data, pickleKey, olm, isNew = false) { - this.data = data; - this._olm = olm; - this._pickleKey = pickleKey; - this.isNew = isNew; + public isModified: boolean; + + constructor( + public readonly data: OlmSessionEntry, + private readonly pickleKey: string, + private readonly olm: Olm, + public isNew: boolean = false + ) { this.isModified = isNew; } - static create(senderKey, olmSession, olm, pickleKey, timestamp) { + static create(senderKey: string, olmSession: Olm.Session, olm: Olm, pickleKey: string, timestamp: number): Session { const data = createSessionEntry(olmSession, senderKey, timestamp, pickleKey); return new Session(data, pickleKey, olm, true); } - get id() { + get id(): string { return this.data.sessionId; } - load() { - const session = new this._olm.Session(); - session.unpickle(this._pickleKey, this.data.session); + load(): Olm.Session { + const session = new this.olm.Session(); + session.unpickle(this.pickleKey, this.data.session); return session; } - unload(olmSession) { + unload(olmSession: Olm.Session): void { olmSession.free(); } - save(olmSession) { - this.data.session = olmSession.pickle(this._pickleKey); + save(olmSession: Olm.Session): void { + this.data.session = olmSession.pickle(this.pickleKey); this.isModified = true; } } diff --git a/src/matrix/storage/idb/stores/OlmSessionStore.ts b/src/matrix/storage/idb/stores/OlmSessionStore.ts index d5a79de2..1263a649 100644 --- a/src/matrix/storage/idb/stores/OlmSessionStore.ts +++ b/src/matrix/storage/idb/stores/OlmSessionStore.ts @@ -24,19 +24,19 @@ function decodeKey(key: string): { senderKey: string, sessionId: string } { return {senderKey, sessionId}; } -interface OlmSession { +export type OlmSessionEntry = { session: string; sessionId: string; senderKey: string; lastUsed: number; } -type OlmSessionEntry = OlmSession & { key: string }; +type OlmSessionStoredEntry = OlmSessionEntry & { key: string }; export class OlmSessionStore { - private _store: Store; + private _store: Store; - constructor(store: Store) { + constructor(store: Store) { this._store = store; } @@ -55,20 +55,20 @@ export class OlmSessionStore { return sessionIds; } - getAll(senderKey: string): Promise { + getAll(senderKey: string): Promise { const range = this._store.IDBKeyRange.lowerBound(encodeKey(senderKey, "")); return this._store.selectWhile(range, session => { return session.senderKey === senderKey; }); } - get(senderKey: string, sessionId: string): Promise { + get(senderKey: string, sessionId: string): Promise { return this._store.get(encodeKey(senderKey, sessionId)); } - set(session: OlmSession): void { - (session as OlmSessionEntry).key = encodeKey(session.senderKey, session.sessionId); - this._store.put(session as OlmSessionEntry); + set(session: OlmSessionEntry): void { + (session as OlmSessionStoredEntry).key = encodeKey(session.senderKey, session.sessionId); + this._store.put(session as OlmSessionStoredEntry); } remove(senderKey: string, sessionId: string): void { From a4fd1615ddbc0472e02825eb9a3fe3185a225c0c Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Tue, 15 Feb 2022 18:21:29 +0100 Subject: [PATCH 008/238] convert decryption --- src/matrix/Session.js | 18 +- .../e2ee/olm/{Decryption.js => Decryption.ts} | 161 ++++++++++-------- src/matrix/e2ee/olm/types.ts | 43 +++++ src/utils/Lock.ts | 8 +- 4 files changed, 150 insertions(+), 80 deletions(-) rename src/matrix/e2ee/olm/{Decryption.js => Decryption.ts} (70%) create mode 100644 src/matrix/e2ee/olm/types.ts diff --git a/src/matrix/Session.js b/src/matrix/Session.js index 83a2df02..72d8a313 100644 --- a/src/matrix/Session.js +++ b/src/matrix/Session.js @@ -26,7 +26,7 @@ import {User} from "./User.js"; import {DeviceMessageHandler} from "./DeviceMessageHandler.js"; import {Account as E2EEAccount} from "./e2ee/Account.js"; import {uploadAccountAsDehydratedDevice} from "./e2ee/Dehydration.js"; -import {Decryption as OlmDecryption} from "./e2ee/olm/Decryption.js"; +import {Decryption as OlmDecryption} from "./e2ee/olm/Decryption"; import {Encryption as OlmEncryption} from "./e2ee/olm/Encryption.js"; import {Decryption as MegOlmDecryption} from "./e2ee/megolm/Decryption"; import {KeyLoader as MegOlmKeyLoader} from "./e2ee/megolm/decryption/KeyLoader"; @@ -123,15 +123,15 @@ export class Session { // TODO: this should all go in a wrapper in e2ee/ that is bootstrapped by passing in the account // and can create RoomEncryption objects and handle encrypted to_device messages and device list changes. const senderKeyLock = new LockMap(); - const olmDecryption = new OlmDecryption({ - account: this._e2eeAccount, - pickleKey: PICKLE_KEY, - olm: this._olm, - storage: this._storage, - now: this._platform.clock.now, - ownUserId: this._user.id, + const olmDecryption = new OlmDecryption( + this._e2eeAccount, + PICKLE_KEY, + this._olm, + this._storage, + this._platform.clock.now, + this._user.id, senderKeyLock - }); + ); this._olmEncryption = new OlmEncryption({ account: this._e2eeAccount, pickleKey: PICKLE_KEY, diff --git a/src/matrix/e2ee/olm/Decryption.js b/src/matrix/e2ee/olm/Decryption.ts similarity index 70% rename from src/matrix/e2ee/olm/Decryption.js rename to src/matrix/e2ee/olm/Decryption.ts index 16e617a5..0fd4f0f9 100644 --- a/src/matrix/e2ee/olm/Decryption.js +++ b/src/matrix/e2ee/olm/Decryption.ts @@ -16,32 +16,52 @@ limitations under the License. import {DecryptionError} from "../common.js"; import {groupBy} from "../../../utils/groupBy"; -import {MultiLock} from "../../../utils/Lock"; +import {MultiLock, ILock} from "../../../utils/Lock"; import {Session} from "./Session.js"; -import {DecryptionResult} from "../DecryptionResult.js"; +import {DecryptionResult} from "../DecryptionResult"; + +import type {OlmMessage, OlmPayload} from "./types"; +import type {Account} from "../Account"; +import type {LockMap} from "../../../utils/LockMap"; +import type {Storage} from "../../storage/idb/Storage"; +import type {Transaction} from "../../storage/idb/Transaction"; +import type {OlmEncryptedEvent} from "./types"; +import type * as OlmNamespace from "@matrix-org/olm"; +type Olm = typeof OlmNamespace; const SESSION_LIMIT_PER_SENDER_KEY = 4; -function isPreKeyMessage(message) { +type DecryptionResults = { + results: DecryptionResult[], + errors: DecryptionError[], + senderKeyDecryption: SenderKeyDecryption +}; + +type CreateAndDecryptResult = { + session: Session, + plaintext: string +}; + +function isPreKeyMessage(message: OlmMessage): boolean { return message.type === 0; } -function sortSessions(sessions) { +function sortSessions(sessions: Session[]) { sessions.sort((a, b) => { return b.data.lastUsed - a.data.lastUsed; }); } export class Decryption { - constructor({account, pickleKey, now, ownUserId, storage, olm, senderKeyLock}) { - this._account = account; - this._pickleKey = pickleKey; - this._now = now; - this._ownUserId = ownUserId; - this._storage = storage; - this._olm = olm; - this._senderKeyLock = senderKeyLock; - } + constructor( + private readonly account: Account, + private readonly pickleKey: string, + private readonly now: () => number, + private readonly ownUserId: string, + private readonly storage: Storage, + private readonly olm: Olm, + private readonly senderKeyLock: LockMap + ) {} // we need to lock because both encryption and decryption can't be done in one txn, // so for them not to step on each other toes, we need to lock. @@ -50,8 +70,8 @@ export class Decryption { // - decryptAll below fails (to release the lock as early as we can) // - DecryptionChanges.write succeeds // - Sync finishes the writeSync phase (or an error was thrown, in case we never get to DecryptionChanges.write) - async obtainDecryptionLock(events) { - const senderKeys = new Set(); + async obtainDecryptionLock(events: OlmEncryptedEvent[]): Promise { + const senderKeys = new Set(); for (const event of events) { const senderKey = event.content?.["sender_key"]; if (senderKey) { @@ -61,7 +81,7 @@ export class Decryption { // take a lock on all senderKeys so encryption or other calls to decryptAll (should not happen) // don't modify the sessions at the same time const locks = await Promise.all(Array.from(senderKeys).map(senderKey => { - return this._senderKeyLock.takeLock(senderKey); + return this.senderKeyLock.takeLock(senderKey); })); return new MultiLock(locks); } @@ -83,18 +103,18 @@ export class Decryption { * @param {[type]} events * @return {Promise} [description] */ - async decryptAll(events, lock, txn) { + async decryptAll(events: OlmEncryptedEvent[], lock: ILock, txn: Transaction): Promise { try { - const eventsPerSenderKey = groupBy(events, event => event.content?.["sender_key"]); - const timestamp = this._now(); + const eventsPerSenderKey = groupBy(events, (event: OlmEncryptedEvent) => event.content?.["sender_key"]); + const timestamp = this.now(); // decrypt events for different sender keys in parallel const senderKeyOperations = await Promise.all(Array.from(eventsPerSenderKey.entries()).map(([senderKey, events]) => { - return this._decryptAllForSenderKey(senderKey, events, timestamp, txn); + return this._decryptAllForSenderKey(senderKey!, events, timestamp, txn); })); - const results = senderKeyOperations.reduce((all, r) => all.concat(r.results), []); - const errors = senderKeyOperations.reduce((all, r) => all.concat(r.errors), []); + const results = senderKeyOperations.reduce((all, r) => all.concat(r.results), [] as DecryptionResult[]); + const errors = senderKeyOperations.reduce((all, r) => all.concat(r.errors), [] as DecryptionError[]); const senderKeyDecryptions = senderKeyOperations.map(r => r.senderKeyDecryption); - return new DecryptionChanges(senderKeyDecryptions, results, errors, this._account, lock); + return new DecryptionChanges(senderKeyDecryptions, results, errors, this.account, lock); } catch (err) { // make sure the locks are release if something throws // otherwise they will be released in DecryptionChanges after having written @@ -104,11 +124,11 @@ export class Decryption { } } - async _decryptAllForSenderKey(senderKey, events, timestamp, readSessionsTxn) { + async _decryptAllForSenderKey(senderKey: string, events: OlmEncryptedEvent[], timestamp: number, readSessionsTxn: Transaction): Promise { const sessions = await this._getSessions(senderKey, readSessionsTxn); - const senderKeyDecryption = new SenderKeyDecryption(senderKey, sessions, this._olm, timestamp); - const results = []; - const errors = []; + const senderKeyDecryption = new SenderKeyDecryption(senderKey, sessions, this.olm, timestamp); + const results: DecryptionResult[] = []; + const errors: DecryptionError[] = []; // events for a single senderKey need to be decrypted one by one for (const event of events) { try { @@ -121,10 +141,10 @@ export class Decryption { return {results, errors, senderKeyDecryption}; } - _decryptForSenderKey(senderKeyDecryption, event, timestamp) { + _decryptForSenderKey(senderKeyDecryption: SenderKeyDecryption, event: OlmEncryptedEvent, timestamp: number): DecryptionResult { const senderKey = senderKeyDecryption.senderKey; const message = this._getMessageAndValidateEvent(event); - let plaintext; + let plaintext: string | undefined; try { plaintext = senderKeyDecryption.decrypt(message); } catch (err) { @@ -133,7 +153,7 @@ export class Decryption { } // could not decrypt with any existing session if (typeof plaintext !== "string" && isPreKeyMessage(message)) { - let createResult; + let createResult: CreateAndDecryptResult; try { createResult = this._createSessionAndDecrypt(senderKey, message, timestamp); } catch (error) { @@ -143,14 +163,14 @@ export class Decryption { plaintext = createResult.plaintext; } if (typeof plaintext === "string") { - let payload; + let payload: OlmPayload; try { payload = JSON.parse(plaintext); } catch (error) { throw new DecryptionError("PLAINTEXT_NOT_JSON", event, {plaintext, error}); } this._validatePayload(payload, event); - return new DecryptionResult(payload, senderKey, payload.keys.ed25519); + return new DecryptionResult(payload, senderKey, payload.keys!.ed25519!); } else { throw new DecryptionError("OLM_NO_MATCHING_SESSION", event, {knownSessionIds: senderKeyDecryption.sessions.map(s => s.id)}); @@ -158,16 +178,16 @@ export class Decryption { } // only for pre-key messages after having attempted decryption with existing sessions - _createSessionAndDecrypt(senderKey, message, timestamp) { + _createSessionAndDecrypt(senderKey: string, message: OlmMessage, timestamp: number): CreateAndDecryptResult { let plaintext; // if we have multiple messages encrypted with the same new session, // this could create multiple sessions as the OTK isn't removed yet // (this only happens in DecryptionChanges.write) // This should be ok though as we'll first try to decrypt with the new session - const olmSession = this._account.createInboundOlmSession(senderKey, message.body); + const olmSession = this.account.createInboundOlmSession(senderKey, message.body); try { plaintext = olmSession.decrypt(message.type, message.body); - const session = Session.create(senderKey, olmSession, this._olm, this._pickleKey, timestamp); + const session = Session.create(senderKey, olmSession, this.olm, this.pickleKey, timestamp); session.unload(olmSession); return {session, plaintext}; } catch (err) { @@ -176,12 +196,12 @@ export class Decryption { } } - _getMessageAndValidateEvent(event) { + _getMessageAndValidateEvent(event: OlmEncryptedEvent): OlmMessage { const ciphertext = event.content?.ciphertext; if (!ciphertext) { throw new DecryptionError("OLM_MISSING_CIPHERTEXT", event); } - const message = ciphertext?.[this._account.identityKeys.curve25519]; + const message = ciphertext?.[this.account.identityKeys.curve25519]; if (!message) { throw new DecryptionError("OLM_NOT_INCLUDED_IN_RECIPIENTS", event); } @@ -189,22 +209,22 @@ export class Decryption { return message; } - async _getSessions(senderKey, txn) { + async _getSessions(senderKey: string, txn: Transaction): Promise { const sessionEntries = await txn.olmSessions.getAll(senderKey); // sort most recent used sessions first - const sessions = sessionEntries.map(s => new Session(s, this._pickleKey, this._olm)); + const sessions = sessionEntries.map(s => new Session(s, this.pickleKey, this.olm)); sortSessions(sessions); return sessions; } - _validatePayload(payload, event) { + _validatePayload(payload: OlmPayload, event: OlmEncryptedEvent): void { if (payload.sender !== event.sender) { throw new DecryptionError("OLM_FORWARDED_MESSAGE", event, {sentBy: event.sender, encryptedBy: payload.sender}); } - if (payload.recipient !== this._ownUserId) { + if (payload.recipient !== this.ownUserId) { throw new DecryptionError("OLM_BAD_RECIPIENT", event, {recipient: payload.recipient}); } - if (payload.recipient_keys?.ed25519 !== this._account.identityKeys.ed25519) { + if (payload.recipient_keys?.ed25519 !== this.account.identityKeys.ed25519) { throw new DecryptionError("OLM_BAD_RECIPIENT_KEY", event, {key: payload.recipient_keys?.ed25519}); } // TODO: check room_id @@ -219,21 +239,21 @@ export class Decryption { // decryption helper for a single senderKey class SenderKeyDecryption { - constructor(senderKey, sessions, olm, timestamp) { - this.senderKey = senderKey; - this.sessions = sessions; - this._olm = olm; - this._timestamp = timestamp; - } + constructor( + public readonly senderKey: string, + public readonly sessions: Session[], + private readonly olm: Olm, + private readonly timestamp: number + ) {} - addNewSession(session) { + addNewSession(session: Session) { // add at top as it is most recent this.sessions.unshift(session); } - decrypt(message) { + decrypt(message: OlmMessage): string | undefined { for (const session of this.sessions) { - const plaintext = this._decryptWithSession(session, message); + const plaintext = this.decryptWithSession(session, message); if (typeof plaintext === "string") { // keep them sorted so will try the same session first for other messages // and so we can assume the excess ones are at the end @@ -244,11 +264,11 @@ class SenderKeyDecryption { } } - getModifiedSessions() { + getModifiedSessions(): Session[] { return this.sessions.filter(session => session.isModified); } - get hasNewSessions() { + get hasNewSessions(): boolean { return this.sessions.some(session => session.isNew); } @@ -257,7 +277,10 @@ class SenderKeyDecryption { // if this turns out to be a real cost for IE11, // we could look into adding a less expensive serialization mechanism // for olm sessions to libolm - _decryptWithSession(session, message) { + private decryptWithSession(session: Session, message: OlmMessage): string | undefined { + if (message.type === undefined || message.body === undefined) { + throw new Error("Invalid message without type or body"); + } const olmSession = session.load(); try { if (isPreKeyMessage(message) && !olmSession.matches_inbound(message.body)) { @@ -266,7 +289,7 @@ class SenderKeyDecryption { try { const plaintext = olmSession.decrypt(message.type, message.body); session.save(olmSession); - session.lastUsed = this._timestamp; + session.data.lastUsed = this.timestamp; return plaintext; } catch (err) { if (isPreKeyMessage(message)) { @@ -286,27 +309,27 @@ class SenderKeyDecryption { * @property {Array} errors see DecryptionError.event to retrieve the event that failed to decrypt. */ class DecryptionChanges { - constructor(senderKeyDecryptions, results, errors, account, lock) { - this._senderKeyDecryptions = senderKeyDecryptions; - this._account = account; - this.results = results; - this.errors = errors; - this._lock = lock; + constructor( + private readonly senderKeyDecryptions: SenderKeyDecryption[], + private readonly results: DecryptionResult[], + private readonly errors: DecryptionError[], + private readonly account: Account, + private readonly lock: ILock + ) {} + + get hasNewSessions(): boolean { + return this.senderKeyDecryptions.some(skd => skd.hasNewSessions); } - get hasNewSessions() { - return this._senderKeyDecryptions.some(skd => skd.hasNewSessions); - } - - write(txn) { + write(txn: Transaction): void { try { - for (const senderKeyDecryption of this._senderKeyDecryptions) { + for (const senderKeyDecryption of this.senderKeyDecryptions) { for (const session of senderKeyDecryption.getModifiedSessions()) { txn.olmSessions.set(session.data); if (session.isNew) { const olmSession = session.load(); try { - this._account.writeRemoveOneTimeKey(olmSession, txn); + this.account.writeRemoveOneTimeKey(olmSession, txn); } finally { session.unload(olmSession); } @@ -322,7 +345,7 @@ class DecryptionChanges { } } } finally { - this._lock.release(); + this.lock.release(); } } } diff --git a/src/matrix/e2ee/olm/types.ts b/src/matrix/e2ee/olm/types.ts new file mode 100644 index 00000000..b9e394d5 --- /dev/null +++ b/src/matrix/e2ee/olm/types.ts @@ -0,0 +1,43 @@ +/* +Copyright 2022 The Matrix.org Foundation C.I.C. + +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. +*/ + +export type OlmMessage = { + type?: 0 | 1, + body?: string +} + +export type OlmEncryptedMessageContent = { + algorithm?: "m.olm.v1.curve25519-aes-sha2" + sender_key?: string, + ciphertext?: { + [deviceCurve25519Key: string]: OlmMessage + } +} + +export type OlmEncryptedEvent = { + type?: "m.room.encrypted", + content?: OlmEncryptedMessageContent + sender?: string +} + +export type OlmPayload = { + type?: string; + content?: Record; + sender?: string; + recipient?: string; + recipient_keys?: {ed25519?: string}; + keys?: {ed25519?: string}; +} diff --git a/src/utils/Lock.ts b/src/utils/Lock.ts index 238d88f9..ff623eba 100644 --- a/src/utils/Lock.ts +++ b/src/utils/Lock.ts @@ -14,7 +14,11 @@ See the License for the specific language governing permissions and limitations under the License. */ -export class Lock { +export interface ILock { + release(): void; +} + +export class Lock implements ILock { private _promise?: Promise; private _resolve?: (() => void); @@ -52,7 +56,7 @@ export class Lock { } } -export class MultiLock { +export class MultiLock implements ILock { constructor(public readonly locks: Lock[]) { } From c40801efd991ba93ae09139d354a90398006a447 Mon Sep 17 00:00:00 2001 From: RMidhunSuresh Date: Wed, 16 Feb 2022 12:33:24 +0530 Subject: [PATCH 009/238] Implement the registration stage --- src/matrix/registration/stages/TokenAuth.ts | 43 +++++++++++++++++++++ 1 file changed, 43 insertions(+) create mode 100644 src/matrix/registration/stages/TokenAuth.ts diff --git a/src/matrix/registration/stages/TokenAuth.ts b/src/matrix/registration/stages/TokenAuth.ts new file mode 100644 index 00000000..6f1a3335 --- /dev/null +++ b/src/matrix/registration/stages/TokenAuth.ts @@ -0,0 +1,43 @@ +/* +Copyright 2022 The Matrix.org Foundation C.I.C. + +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 {AuthenticationData} from "../types"; +import {BaseRegistrationStage} from "./BaseRegistrationStage"; + +export const TOKEN_AUTH_TYPE = "org.matrix.msc3231.login.registration_token"; + +export class TokenAuth extends BaseRegistrationStage { + private _token?: string; + + generateAuthenticationData(): AuthenticationData { + if (!this._token) { + throw new Error("No token provided for TokenAuth"); + } + return { + session: this._session, + type: this.type, + token: this._token, + }; + } + + setToken(token: string) { + this._token = token; + } + + get type(): string { + return TOKEN_AUTH_TYPE; + } +} From ed151c8567b175b2c785710b299bc7a618e7c3a5 Mon Sep 17 00:00:00 2001 From: RMidhunSuresh Date: Wed, 16 Feb 2022 12:33:59 +0530 Subject: [PATCH 010/238] Return token stage from createRegistrationStage --- src/matrix/registration/Registration.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/matrix/registration/Registration.ts b/src/matrix/registration/Registration.ts index c9c9af87..0d342639 100644 --- a/src/matrix/registration/Registration.ts +++ b/src/matrix/registration/Registration.ts @@ -18,6 +18,7 @@ import type {HomeServerApi} from "../net/HomeServerApi"; import type {BaseRegistrationStage} from "./stages/BaseRegistrationStage"; import {DummyAuth} from "./stages/DummyAuth"; import {TermsAuth} from "./stages/TermsAuth"; +import {TokenAuth, TOKEN_AUTH_TYPE} from "./stages/TokenAuth"; import type { AccountDetails, RegistrationFlow, @@ -108,6 +109,8 @@ export class Registration { return new DummyAuth(session, params?.[type]); case "m.login.terms": return new TermsAuth(session, params?.[type]); + case TOKEN_AUTH_TYPE: + return new TokenAuth(session, params?.[type]); default: throw new Error(`Unknown stage: ${type}`); } From 60bc4450f3c6d26bf4f2e9f17a4b6210f59d98d0 Mon Sep 17 00:00:00 2001 From: RMidhunSuresh Date: Wed, 16 Feb 2022 13:21:04 +0530 Subject: [PATCH 011/238] Use type from server --- src/matrix/registration/Registration.ts | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/src/matrix/registration/Registration.ts b/src/matrix/registration/Registration.ts index 0d342639..8bfce8ad 100644 --- a/src/matrix/registration/Registration.ts +++ b/src/matrix/registration/Registration.ts @@ -18,7 +18,7 @@ import type {HomeServerApi} from "../net/HomeServerApi"; import type {BaseRegistrationStage} from "./stages/BaseRegistrationStage"; import {DummyAuth} from "./stages/DummyAuth"; import {TermsAuth} from "./stages/TermsAuth"; -import {TokenAuth, TOKEN_AUTH_TYPE} from "./stages/TokenAuth"; +import {TokenAuth} from "./stages/TokenAuth"; import type { AccountDetails, RegistrationFlow, @@ -94,7 +94,9 @@ export class Registration { this._sessionInfo = response; return undefined; case 401: - if (response.completed?.includes(currentStage.type)) { + // Support unstable prefix for TokenAuth + const typeFromServer = (currentStage as TokenAuth).typeFromServer; + if (response.completed?.includes(typeFromServer ?? currentStage.type)) { return currentStage.nextStage; } else { @@ -109,8 +111,9 @@ export class Registration { return new DummyAuth(session, params?.[type]); case "m.login.terms": return new TermsAuth(session, params?.[type]); - case TOKEN_AUTH_TYPE: - return new TokenAuth(session, params?.[type]); + case "org.matrix.msc3231.login.registration_token": + case "m.login.registration_token": + return new TokenAuth(session, params?.[type], type); default: throw new Error(`Unknown stage: ${type}`); } From a76bcd1739cd87770d716cdfdc0e317af970c1ac Mon Sep 17 00:00:00 2001 From: RMidhunSuresh Date: Wed, 16 Feb 2022 13:36:24 +0530 Subject: [PATCH 012/238] Changes in TokenAuth --- src/matrix/registration/stages/TokenAuth.ts | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) diff --git a/src/matrix/registration/stages/TokenAuth.ts b/src/matrix/registration/stages/TokenAuth.ts index 6f1a3335..607ee910 100644 --- a/src/matrix/registration/stages/TokenAuth.ts +++ b/src/matrix/registration/stages/TokenAuth.ts @@ -14,13 +14,18 @@ See the License for the specific language governing permissions and limitations under the License. */ -import {AuthenticationData} from "../types"; +import {AuthenticationData, RegistrationParams} from "../types"; import {BaseRegistrationStage} from "./BaseRegistrationStage"; -export const TOKEN_AUTH_TYPE = "org.matrix.msc3231.login.registration_token"; - export class TokenAuth extends BaseRegistrationStage { private _token?: string; + private readonly _type: string; + + constructor(session: string, params: RegistrationParams | undefined, type: string) { + super(session, params); + this._type = type; + } + generateAuthenticationData(): AuthenticationData { if (!this._token) { @@ -28,7 +33,7 @@ export class TokenAuth extends BaseRegistrationStage { } return { session: this._session, - type: this.type, + type: this._type, token: this._token, }; } @@ -38,6 +43,10 @@ export class TokenAuth extends BaseRegistrationStage { } get type(): string { - return TOKEN_AUTH_TYPE; + return "m.login.registration_token"; + } + + get typeFromServer(): string { + return this._type; } } From 7a9298328f3ac0b2348177f6919c1bc1018deca1 Mon Sep 17 00:00:00 2001 From: RMidhunSuresh Date: Wed, 16 Feb 2022 14:37:18 +0530 Subject: [PATCH 013/238] Return _type from getter --- src/matrix/registration/Registration.ts | 4 +--- src/matrix/registration/stages/TokenAuth.ts | 4 ---- 2 files changed, 1 insertion(+), 7 deletions(-) diff --git a/src/matrix/registration/Registration.ts b/src/matrix/registration/Registration.ts index 8bfce8ad..ded66719 100644 --- a/src/matrix/registration/Registration.ts +++ b/src/matrix/registration/Registration.ts @@ -94,9 +94,7 @@ export class Registration { this._sessionInfo = response; return undefined; case 401: - // Support unstable prefix for TokenAuth - const typeFromServer = (currentStage as TokenAuth).typeFromServer; - if (response.completed?.includes(typeFromServer ?? currentStage.type)) { + if (response.completed?.includes(currentStage.type)) { return currentStage.nextStage; } else { diff --git a/src/matrix/registration/stages/TokenAuth.ts b/src/matrix/registration/stages/TokenAuth.ts index 607ee910..cb238bcb 100644 --- a/src/matrix/registration/stages/TokenAuth.ts +++ b/src/matrix/registration/stages/TokenAuth.ts @@ -43,10 +43,6 @@ export class TokenAuth extends BaseRegistrationStage { } get type(): string { - return "m.login.registration_token"; - } - - get typeFromServer(): string { return this._type; } } From 61b264be3b244df6e13b4c4ea9b428ddddc2cf3a Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Wed, 16 Feb 2022 10:20:53 +0100 Subject: [PATCH 014/238] bump sdk version to 0.0.7 --- scripts/sdk/base-manifest.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/sdk/base-manifest.json b/scripts/sdk/base-manifest.json index 3e468181..b9e4ed5c 100644 --- a/scripts/sdk/base-manifest.json +++ b/scripts/sdk/base-manifest.json @@ -1,7 +1,7 @@ { "name": "hydrogen-view-sdk", "description": "Embeddable matrix client library, including view components", - "version": "0.0.6", + "version": "0.0.7", "main": "./hydrogen.es.js", "type": "module" } From eb5ca200f2559a29545e1c3390960bc723317baf Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Wed, 16 Feb 2022 18:00:03 +0100 Subject: [PATCH 015/238] missed rename here --- src/matrix/e2ee/olm/Decryption.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/matrix/e2ee/olm/Decryption.ts b/src/matrix/e2ee/olm/Decryption.ts index 0fd4f0f9..7d9be4a3 100644 --- a/src/matrix/e2ee/olm/Decryption.ts +++ b/src/matrix/e2ee/olm/Decryption.ts @@ -17,7 +17,7 @@ limitations under the License. import {DecryptionError} from "../common.js"; import {groupBy} from "../../../utils/groupBy"; import {MultiLock, ILock} from "../../../utils/Lock"; -import {Session} from "./Session.js"; +import {Session} from "./Session"; import {DecryptionResult} from "../DecryptionResult"; import type {OlmMessage, OlmPayload} from "./types"; @@ -246,7 +246,7 @@ class SenderKeyDecryption { private readonly timestamp: number ) {} - addNewSession(session: Session) { + addNewSession(session: Session): void { // add at top as it is most recent this.sessions.unshift(session); } From e3e90ed1671c247f623ceba95d1ff58d7bcf6f01 Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Wed, 16 Feb 2022 18:00:13 +0100 Subject: [PATCH 016/238] convert olm/Encryption to TS --- src/matrix/Session.js | 20 +-- .../e2ee/olm/{Encryption.js => Encryption.ts} | 141 +++++++++++------- 2 files changed, 94 insertions(+), 67 deletions(-) rename src/matrix/e2ee/olm/{Encryption.js => Encryption.ts} (64%) diff --git a/src/matrix/Session.js b/src/matrix/Session.js index 72d8a313..8652a1d7 100644 --- a/src/matrix/Session.js +++ b/src/matrix/Session.js @@ -27,7 +27,7 @@ import {DeviceMessageHandler} from "./DeviceMessageHandler.js"; import {Account as E2EEAccount} from "./e2ee/Account.js"; import {uploadAccountAsDehydratedDevice} from "./e2ee/Dehydration.js"; import {Decryption as OlmDecryption} from "./e2ee/olm/Decryption"; -import {Encryption as OlmEncryption} from "./e2ee/olm/Encryption.js"; +import {Encryption as OlmEncryption} from "./e2ee/olm/Encryption"; import {Decryption as MegOlmDecryption} from "./e2ee/megolm/Decryption"; import {KeyLoader as MegOlmKeyLoader} from "./e2ee/megolm/decryption/KeyLoader"; import {KeyBackup} from "./e2ee/megolm/keybackup/KeyBackup"; @@ -132,16 +132,16 @@ export class Session { this._user.id, senderKeyLock ); - this._olmEncryption = new OlmEncryption({ - account: this._e2eeAccount, - pickleKey: PICKLE_KEY, - olm: this._olm, - storage: this._storage, - now: this._platform.clock.now, - ownUserId: this._user.id, - olmUtil: this._olmUtil, + this._olmEncryption = new OlmEncryption( + this._e2eeAccount, + PICKLE_KEY, + this._olm, + this._storage, + this._platform.clock.now, + this._user.id, + this._olmUtil, senderKeyLock - }); + ); this._keyLoader = new MegOlmKeyLoader(this._olm, PICKLE_KEY, 20); this._megolmEncryption = new MegOlmEncryption({ account: this._e2eeAccount, diff --git a/src/matrix/e2ee/olm/Encryption.js b/src/matrix/e2ee/olm/Encryption.ts similarity index 64% rename from src/matrix/e2ee/olm/Encryption.js rename to src/matrix/e2ee/olm/Encryption.ts index 3e78470d..ebc38170 100644 --- a/src/matrix/e2ee/olm/Encryption.js +++ b/src/matrix/e2ee/olm/Encryption.ts @@ -18,6 +18,32 @@ import {groupByWithCreator} from "../../../utils/groupBy"; import {verifyEd25519Signature, OLM_ALGORITHM} from "../common.js"; import {createSessionEntry} from "./Session"; +import type {OlmMessage, OlmPayload, OlmEncryptedMessageContent} from "./types"; +import type {Account} from "../Account"; +import type {LockMap} from "../../../utils/LockMap"; +import type {Storage} from "../../storage/idb/Storage"; +import type {Transaction} from "../../storage/idb/Transaction"; +import type {DeviceIdentity} from "../../storage/idb/stores/DeviceIdentityStore"; +import type {HomeServerApi} from "../../net/HomeServerApi"; +import type {ILogItem} from "../../../logging/types"; +import type * as OlmNamespace from "@matrix-org/olm"; +type Olm = typeof OlmNamespace; + +type ClaimedOTKResponse = { + [userId: string]: { + [deviceId: string]: { + [algorithmAndOtk: string]: { + key: string, + signatures: { + [userId: string]: { + [algorithmAndDevice: string]: string + } + } + } + } + } +}; + function findFirstSessionId(sessionIds) { return sessionIds.reduce((first, sessionId) => { if (!first || sessionId < first) { @@ -36,19 +62,19 @@ const OTK_ALGORITHM = "signed_curve25519"; const MAX_BATCH_SIZE = 20; export class Encryption { - constructor({account, olm, olmUtil, ownUserId, storage, now, pickleKey, senderKeyLock}) { - this._account = account; - this._olm = olm; - this._olmUtil = olmUtil; - this._ownUserId = ownUserId; - this._storage = storage; - this._now = now; - this._pickleKey = pickleKey; - this._senderKeyLock = senderKeyLock; - } + constructor( + private readonly account: Account, + private readonly olm: Olm, + private readonly olmUtil: Olm.Utility, + private readonly ownUserId: string, + private readonly storage: Storage, + private readonly now: () => number, + private readonly pickleKey: string, + private readonly senderKeyLock: LockMap + ) {} - async encrypt(type, content, devices, hsApi, log) { - let messages = []; + async encrypt(type: string, content: Record, devices: DeviceIdentity[], hsApi: HomeServerApi, log: ILogItem): Promise { + let messages: EncryptedMessage[] = []; for (let i = 0; i < devices.length ; i += MAX_BATCH_SIZE) { const batchDevices = devices.slice(i, i + MAX_BATCH_SIZE); const batchMessages = await this._encryptForMaxDevices(type, content, batchDevices, hsApi, log); @@ -57,12 +83,12 @@ export class Encryption { return messages; } - async _encryptForMaxDevices(type, content, devices, hsApi, log) { + async _encryptForMaxDevices(type: string, content: Record, devices: DeviceIdentity[], hsApi: HomeServerApi, log: ILogItem): Promise { // TODO: see if we can only hold some of the locks until after the /keys/claim call (if needed) // take a lock on all senderKeys so decryption and other calls to encrypt (should not happen) // don't modify the sessions at the same time const locks = await Promise.all(devices.map(device => { - return this._senderKeyLock.takeLock(device.curve25519Key); + return this.senderKeyLock.takeLock(device.curve25519Key); })); try { const { @@ -70,9 +96,9 @@ export class Encryption { existingEncryptionTargets, } = await this._findExistingSessions(devices); - const timestamp = this._now(); + const timestamp = this.now(); - let encryptionTargets = []; + let encryptionTargets: EncryptionTarget[] = []; try { if (devicesWithoutSession.length) { const newEncryptionTargets = await log.wrap("create sessions", log => this._createNewSessions( @@ -100,8 +126,8 @@ export class Encryption { } } - async _findExistingSessions(devices) { - const txn = await this._storage.readTxn([this._storage.storeNames.olmSessions]); + async _findExistingSessions(devices: DeviceIdentity[]): Promise<{devicesWithoutSession: DeviceIdentity[], existingEncryptionTargets: EncryptionTarget[]}> { + const txn = await this.storage.readTxn([this.storage.storeNames.olmSessions]); const sessionIdsForDevice = await Promise.all(devices.map(async device => { return await txn.olmSessions.getSessionIds(device.curve25519Key); })); @@ -116,18 +142,18 @@ export class Encryption { const sessionId = findFirstSessionId(sessionIds); return EncryptionTarget.fromSessionId(device, sessionId); } - }).filter(target => !!target); + }).filter(target => !!target) as EncryptionTarget[]; return {devicesWithoutSession, existingEncryptionTargets}; } - _encryptForDevice(type, content, target) { + _encryptForDevice(type: string, content: Record, target: EncryptionTarget): OlmEncryptedMessageContent { const {session, device} = target; const plaintext = JSON.stringify(this._buildPlainTextMessageForDevice(type, content, device)); - const message = session.encrypt(plaintext); + const message = session!.encrypt(plaintext); const encryptedContent = { algorithm: OLM_ALGORITHM, - sender_key: this._account.identityKeys.curve25519, + sender_key: this.account.identityKeys.curve25519, ciphertext: { [device.curve25519Key]: message } @@ -135,27 +161,27 @@ export class Encryption { return encryptedContent; } - _buildPlainTextMessageForDevice(type, content, device) { + _buildPlainTextMessageForDevice(type: string, content: Record, device: DeviceIdentity): OlmPayload { return { keys: { - "ed25519": this._account.identityKeys.ed25519 + "ed25519": this.account.identityKeys.ed25519 }, recipient_keys: { "ed25519": device.ed25519Key }, recipient: device.userId, - sender: this._ownUserId, + sender: this.ownUserId, content, type } } - async _createNewSessions(devicesWithoutSession, hsApi, timestamp, log) { + async _createNewSessions(devicesWithoutSession: DeviceIdentity[], hsApi: HomeServerApi, timestamp: number, log: ILogItem): Promise { const newEncryptionTargets = await log.wrap("claim", log => this._claimOneTimeKeys(hsApi, devicesWithoutSession, log)); try { for (const target of newEncryptionTargets) { const {device, oneTimeKey} = target; - target.session = await this._account.createOutboundOlmSession(device.curve25519Key, oneTimeKey); + target.session = await this.account.createOutboundOlmSession(device.curve25519Key, oneTimeKey); } await this._storeSessions(newEncryptionTargets, timestamp); } catch (err) { @@ -167,12 +193,12 @@ export class Encryption { return newEncryptionTargets; } - async _claimOneTimeKeys(hsApi, deviceIdentities, log) { + async _claimOneTimeKeys(hsApi: HomeServerApi, deviceIdentities: DeviceIdentity[], log: ILogItem): Promise { // create a Map> const devicesByUser = groupByWithCreator(deviceIdentities, - device => device.userId, - () => new Map(), - (deviceMap, device) => deviceMap.set(device.deviceId, device) + (device: DeviceIdentity) => device.userId, + (): Map => new Map(), + (deviceMap: Map, device: DeviceIdentity) => deviceMap.set(device.deviceId, device) ); const oneTimeKeys = Array.from(devicesByUser.entries()).reduce((usersObj, [userId, deviceMap]) => { usersObj[userId] = Array.from(deviceMap.values()).reduce((devicesObj, device) => { @@ -188,12 +214,12 @@ export class Encryption { if (Object.keys(claimResponse.failures).length) { log.log({l: "failures", servers: Object.keys(claimResponse.failures)}, log.level.Warn); } - const userKeyMap = claimResponse?.["one_time_keys"]; + const userKeyMap = claimResponse?.["one_time_keys"] as ClaimedOTKResponse; return this._verifyAndCreateOTKTargets(userKeyMap, devicesByUser, log); } - _verifyAndCreateOTKTargets(userKeyMap, devicesByUser, log) { - const verifiedEncryptionTargets = []; + _verifyAndCreateOTKTargets(userKeyMap: ClaimedOTKResponse, devicesByUser: Map>, log: ILogItem): EncryptionTarget[] { + const verifiedEncryptionTargets: EncryptionTarget[] = []; for (const [userId, userSection] of Object.entries(userKeyMap)) { for (const [deviceId, deviceSection] of Object.entries(userSection)) { const [firstPropName, keySection] = Object.entries(deviceSection)[0]; @@ -202,7 +228,7 @@ export class Encryption { const device = devicesByUser.get(userId)?.get(deviceId); if (device) { const isValidSignature = verifyEd25519Signature( - this._olmUtil, userId, deviceId, device.ed25519Key, keySection, log); + this.olmUtil, userId, deviceId, device.ed25519Key, keySection, log); if (isValidSignature) { const target = EncryptionTarget.fromOTK(device, keySection.key); verifiedEncryptionTargets.push(target); @@ -214,8 +240,8 @@ export class Encryption { return verifiedEncryptionTargets; } - async _loadSessions(encryptionTargets) { - const txn = await this._storage.readTxn([this._storage.storeNames.olmSessions]); + async _loadSessions(encryptionTargets: EncryptionTarget[]): Promise { + const txn = await this.storage.readTxn([this.storage.storeNames.olmSessions]); // given we run loading in parallel, there might still be some // storage requests that will finish later once one has failed. // those should not allocate a session anymore. @@ -223,10 +249,10 @@ export class Encryption { try { await Promise.all(encryptionTargets.map(async encryptionTarget => { const sessionEntry = await txn.olmSessions.get( - encryptionTarget.device.curve25519Key, encryptionTarget.sessionId); + encryptionTarget.device.curve25519Key, encryptionTarget.sessionId!); if (sessionEntry && !failed) { - const olmSession = new this._olm.Session(); - olmSession.unpickle(this._pickleKey, sessionEntry.session); + const olmSession = new this.olm.Session(); + olmSession.unpickle(this.pickleKey, sessionEntry.session); encryptionTarget.session = olmSession; } })); @@ -240,12 +266,12 @@ export class Encryption { } } - async _storeSessions(encryptionTargets, timestamp) { - const txn = await this._storage.readWriteTxn([this._storage.storeNames.olmSessions]); + async _storeSessions(encryptionTargets: EncryptionTarget[], timestamp: number): Promise { + const txn = await this.storage.readWriteTxn([this.storage.storeNames.olmSessions]); try { for (const target of encryptionTargets) { const sessionEntry = createSessionEntry( - target.session, target.device.curve25519Key, timestamp, this._pickleKey); + target.session!, target.device.curve25519Key, timestamp, this.pickleKey); txn.olmSessions.set(sessionEntry); } } catch (err) { @@ -261,23 +287,24 @@ export class Encryption { // (and later converted to a session) in case of a new session // or an existing session class EncryptionTarget { - constructor(device, oneTimeKey, sessionId) { - this.device = device; - this.oneTimeKey = oneTimeKey; - this.sessionId = sessionId; - // an olmSession, should probably be called olmSession - this.session = null; - } + + public session: Olm.Session | null = null; - static fromOTK(device, oneTimeKey) { + constructor( + public readonly device: DeviceIdentity, + public readonly oneTimeKey: string | null, + public readonly sessionId: string | null + ) {} + + static fromOTK(device: DeviceIdentity, oneTimeKey: string): EncryptionTarget { return new EncryptionTarget(device, oneTimeKey, null); } - static fromSessionId(device, sessionId) { + static fromSessionId(device: DeviceIdentity, sessionId: string): EncryptionTarget { return new EncryptionTarget(device, null, sessionId); } - dispose() { + dispose(): void { if (this.session) { this.session.free(); } @@ -285,8 +312,8 @@ class EncryptionTarget { } class EncryptedMessage { - constructor(content, device) { - this.content = content; - this.device = device; - } + constructor( + public readonly content: OlmEncryptedMessageContent, + public readonly device: DeviceIdentity + ) {} } From 60f5da60bb17ffb90fa2d573feff88fbb9cc033e Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Wed, 16 Feb 2022 18:01:24 +0100 Subject: [PATCH 017/238] fix lint --- src/matrix/room/timeline/Timeline.js | 1 - 1 file changed, 1 deletion(-) diff --git a/src/matrix/room/timeline/Timeline.js b/src/matrix/room/timeline/Timeline.js index c6852492..3332a5b0 100644 --- a/src/matrix/room/timeline/Timeline.js +++ b/src/matrix/room/timeline/Timeline.js @@ -24,7 +24,6 @@ import {RoomMember} from "../members/RoomMember.js"; import {getRelation, ANNOTATION_RELATION_TYPE} from "./relations.js"; import {REDACTION_TYPE} from "../common"; import {NonPersistedEventEntry} from "./entries/NonPersistedEventEntry.js"; -import {DecryptionSource} from "../../e2ee/common.js"; import {EVENT_TYPE as MEMBER_EVENT_TYPE} from "../members/RoomMember.js"; export class Timeline { From 498a43327f2d0b2bdd97203879a51d800ab32aa3 Mon Sep 17 00:00:00 2001 From: RMidhunSuresh Date: Thu, 17 Feb 2022 11:30:04 +0530 Subject: [PATCH 018/238] Check if options exist in emitChange --- src/domain/ViewModel.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/domain/ViewModel.ts b/src/domain/ViewModel.ts index 99b23918..f3e141dc 100644 --- a/src/domain/ViewModel.ts +++ b/src/domain/ViewModel.ts @@ -110,7 +110,7 @@ export class ViewModel extends EventEmitter<{change } emitChange(changedProps: any): void { - if (this._options.emitChange) { + if (this._options?.emitChange) { this._options.emitChange(changedProps); } else { this.emit("change", changedProps); From 7f1fed6f8cf3e9479380fdfc8bbc4d1fbb016ffb Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Thu, 17 Feb 2022 09:24:18 +0100 Subject: [PATCH 019/238] always pass options to ViewModel constructor --- src/domain/AccountSetupViewModel.js | 8 ++++---- src/domain/SessionLoadViewModel.js | 2 +- src/domain/ViewModel.ts | 4 +++- src/domain/session/room/ComposerViewModel.js | 2 +- 4 files changed, 9 insertions(+), 7 deletions(-) diff --git a/src/domain/AccountSetupViewModel.js b/src/domain/AccountSetupViewModel.js index 74d680d0..e7c1301f 100644 --- a/src/domain/AccountSetupViewModel.js +++ b/src/domain/AccountSetupViewModel.js @@ -19,9 +19,9 @@ import {KeyType} from "../matrix/ssss/index"; import {Status} from "./session/settings/KeyBackupViewModel.js"; export class AccountSetupViewModel extends ViewModel { - constructor(accountSetup) { - super(); - this._accountSetup = accountSetup; + constructor(options) { + super(options); + this._accountSetup = options.accountSetup; this._dehydratedDevice = undefined; this._decryptDehydratedDeviceViewModel = undefined; if (this._accountSetup.encryptedDehydratedDevice) { @@ -53,7 +53,7 @@ export class AccountSetupViewModel extends ViewModel { // this vm adopts the same shape as KeyBackupViewModel so the same view can be reused. class DecryptDehydratedDeviceViewModel extends ViewModel { constructor(accountSetupViewModel, decryptedCallback) { - super(); + super(accountSetupViewModel.options); this._accountSetupViewModel = accountSetupViewModel; this._isBusy = false; this._status = Status.SetupKey; diff --git a/src/domain/SessionLoadViewModel.js b/src/domain/SessionLoadViewModel.js index b23b54bc..abc16299 100644 --- a/src/domain/SessionLoadViewModel.js +++ b/src/domain/SessionLoadViewModel.js @@ -43,7 +43,7 @@ export class SessionLoadViewModel extends ViewModel { this.emitChange("loading"); this._waitHandle = this._client.loadStatus.waitFor(s => { if (s === LoadStatus.AccountSetup) { - this._accountSetupViewModel = new AccountSetupViewModel(this._client.accountSetup); + this._accountSetupViewModel = new AccountSetupViewModel(this.childOptions({accountSetup: this._client.accountSetup})); } else { this._accountSetupViewModel = undefined; } diff --git a/src/domain/ViewModel.ts b/src/domain/ViewModel.ts index f3e141dc..458b2840 100644 --- a/src/domain/ViewModel.ts +++ b/src/domain/ViewModel.ts @@ -51,6 +51,8 @@ export class ViewModel extends EventEmitter<{change return Object.assign({}, this._options, explicitOptions); } + get options(): O { return this._options; } + // makes it easier to pass through dependencies of a sub-view model getOption(name: N): O[N] { return this._options[name]; @@ -110,7 +112,7 @@ export class ViewModel extends EventEmitter<{change } emitChange(changedProps: any): void { - if (this._options?.emitChange) { + if (this._options.emitChange) { this._options.emitChange(changedProps); } else { this.emit("change", changedProps); diff --git a/src/domain/session/room/ComposerViewModel.js b/src/domain/session/room/ComposerViewModel.js index 833b17f5..c20f6e86 100644 --- a/src/domain/session/room/ComposerViewModel.js +++ b/src/domain/session/room/ComposerViewModel.js @@ -18,7 +18,7 @@ import {ViewModel} from "../../ViewModel"; export class ComposerViewModel extends ViewModel { constructor(roomVM) { - super(); + super(roomVM.options); this._roomVM = roomVM; this._isEmpty = true; this._replyVM = null; From 2472f11ec0e6839cc78672c7781e1dbfa6495939 Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Thu, 17 Feb 2022 09:47:57 +0100 Subject: [PATCH 020/238] export RoomStatus --- src/lib.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/lib.ts b/src/lib.ts index cc88690c..89cd7706 100644 --- a/src/lib.ts +++ b/src/lib.ts @@ -16,6 +16,7 @@ limitations under the License. export {Platform} from "./platform/web/Platform.js"; export {Client, LoadStatus} from "./matrix/Client.js"; +export {RoomStatus} from "./matrix/room/common"; // export main view & view models export {createNavigation, createRouter} from "./domain/navigation/index.js"; export {RootViewModel} from "./domain/RootViewModel.js"; From ac48a5a4dfc00b1671a59d4f68f13488bff426a8 Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Thu, 17 Feb 2022 10:10:23 +0100 Subject: [PATCH 021/238] bump SDK version to 0.0.8 --- scripts/sdk/base-manifest.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/sdk/base-manifest.json b/scripts/sdk/base-manifest.json index b9e4ed5c..8df205cb 100644 --- a/scripts/sdk/base-manifest.json +++ b/scripts/sdk/base-manifest.json @@ -1,7 +1,7 @@ { "name": "hydrogen-view-sdk", "description": "Embeddable matrix client library, including view components", - "version": "0.0.7", + "version": "0.0.8", "main": "./hydrogen.es.js", "type": "module" } From cdd6112971bd9c70b3a2601c16a1895db460ba68 Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Thu, 17 Feb 2022 17:39:45 +0100 Subject: [PATCH 022/238] finish adapting contribution guide --- CONTRIBUTING.md | 51 +++++++++++++++++++++++-------------------------- 1 file changed, 24 insertions(+), 27 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 80f1fc6a..7a217d7f 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,4 +1,4 @@ -Contributing code to matrix-js-sdk +Contributing code to hydrogen-web ================================== Everyone is welcome to contribute code to hydrogen-web, provided that they are @@ -20,13 +20,11 @@ We use GitHub's pull request workflow to review the contribution, and either ask you to make any refinements needed or merge it and make them ourselves. Things that should go into your PR description: - * Please disable any automatic formatting tools you may have active. - You'll be asked to undo any unrelated whitespace changes during code review. * References to any bugs fixed by the change (in GitHub's `Fixes` notation) * Describe the why and what is changing in the PR description so it's easy for onlookers and reviewers to onboard and context switch. - * Include both **before** and **after** screenshots to easily compare and discuss - what's changing. + * If your PR makes visual changes, include both **before** and **after** screenshots + to easily compare and discuss what's changing. * Include a step-by-step testing strategy so that a reviewer can check out the code locally and easily get to the point of testing your change. * Add comments to the diff for the reviewer that might help them to understand @@ -76,30 +74,34 @@ checks, so please check back after a few minutes. Tests ----- -If your PR is a feature (ie. if it's being labelled with the 'T-Enhancement' -label) then we require that the PR also includes tests. These need to test that -your feature works as expected and ideally test edge cases too. For the js-sdk -itself, your tests should generally be unit tests. matrix-react-sdk also uses -these guidelines, so for that your tests can be unit tests using -react-test-utils, snapshot tests or screenshot tests. +If your PR is a feature then we require that the PR also includes tests. +These need to test that your feature works as expected and ideally test edge cases too. -We don't require tests for bug fixes (T-Defect) but strongly encourage regression -tests for the bug itself wherever possible. +Tests are written as unit tests by exporting a `tests` function from the file to be tested. +The function returns an object where the key is the test label, and the value is a +function that accepts an [assert](https://nodejs.org/api/assert.html) object, and return a Promise or nothing. -In the future we may formalise this more with a minimum test coverage -percentage for the diff. +Note that there is currently a limitation that files that are not indirectly included from `src/platform/web/main.js` won't be found by the runner. + +You can run the tests by running `yarn test`. +This uses the [impunity](https://github.com/bwindels/impunity) runner. + +We don't require tests for bug fixes. + +In the future we may formalise this more. Code style ---------- The js-sdk aims to target TypeScript/ES6. All new files should be written in TypeScript and existing files should use ES6 principles where possible. -Members should not be exported as a default export in general - it causes problems -with the architecture of the SDK (index file becomes less clear) and could -introduce naming problems (as default exports get aliased upon import). In -general, avoid using `export default`. +Please disable any automatic formatting tools you may have active. +If present, you'll be asked to undo any unrelated whitespace changes during code review. -The remaining code-style for matrix-js-sdk is not formally documented, but +Members should not be exported as a default export in general. +In general, avoid using `export default`. + +The remaining code-style for hydrogen is not formally documented, but contributors are encouraged to read the [code style document for matrix-react-sdk](https://github.com/matrix-org/matrix-react-sdk/blob/master/code_style.md) and follow the principles set out there. @@ -110,13 +112,8 @@ makes it horribly hard to review otherwise. Attribution ----------- -Everyone who contributes anything to Matrix is welcome to be listed in the -AUTHORS.rst file for the project in question. Please feel free to include a -change to AUTHORS.rst in your pull request to list yourself and a short -description of the area(s) you've worked on. Also, we sometimes have swag to -give away to contributors - if you feel that Matrix-branded apparel is missing -from your life, please mail us your shipping address to matrix at matrix.org -and we'll try to fix it :) +If you change or create a file, feel free to add yourself to the copyright holders +in the license header of that file. Sign off -------- From 91fd0e433a40c75832b182eba7469bb4622db261 Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Thu, 17 Feb 2022 17:44:44 +0100 Subject: [PATCH 023/238] remove changelog notes remainder --- CONTRIBUTING.md | 40 +--------------------------------------- 1 file changed, 1 insertion(+), 39 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 7a217d7f..25d8e3c7 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -30,44 +30,6 @@ Things that should go into your PR description: * Add comments to the diff for the reviewer that might help them to understand why the change is necessary or how they might better understand and review it. -To add a longer, more detailed description of the change for the changelog: - - -*Fix llama herding bug* - -``` -Notes: Fix a bug (https://github.com/matrix-org/notaproject/issues/123) where the 'Herd' button would not herd more than 8 Llamas if the moon was in the waxing gibbous phase -``` - -*Remove outdated comment from `Ungulates.ts`* -``` -Notes: none -``` - -Sometimes, you're fixing a bug in a downstream project, in which case you want -an entry in that project's changelog. You can do that too: - -*Fix another herding bug* -``` -Notes: Fix a bug where the `herd()` function would only work on Tuesdays -element-web notes: Fix a bug where the 'Herd' button only worked on Tuesdays -``` - -If your PR introduces a breaking change, add the `X-Breaking-Change` label (see below) -and remember to tell the developer how to migrate: - -*Remove legacy class* - -``` -Notes: Remove legacy `Camelopard` class. `Giraffe` should be used instead. -``` - -Other metadata can be added using labels. - * `X-Breaking-Change`: A breaking change - adding this label will mean the change causes a *major* version bump. - -If you don't have permission to add labels, your PR reviewer(s) can work with you -to add them: ask in the PR description or comments. - We use continuous integration, and all pull requests get automatically tested: if your change breaks the build, then the PR will show that there are failed checks, so please check back after a few minutes. @@ -101,7 +63,7 @@ If present, you'll be asked to undo any unrelated whitespace changes during code Members should not be exported as a default export in general. In general, avoid using `export default`. -The remaining code-style for hydrogen is not formally documented, but +The remaining code-style for hydrogen is [in the process of being documented](codestyle.md), but contributors are encouraged to read the [code style document for matrix-react-sdk](https://github.com/matrix-org/matrix-react-sdk/blob/master/code_style.md) and follow the principles set out there. From 347edb5988c7c133f7d1b08cb6e952251a659a0d Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Fri, 18 Feb 2022 16:47:47 +0100 Subject: [PATCH 024/238] remove unused storage property --- src/matrix/Session.js | 1 - src/matrix/e2ee/olm/Decryption.ts | 1 - 2 files changed, 2 deletions(-) diff --git a/src/matrix/Session.js b/src/matrix/Session.js index 8652a1d7..ac060b0b 100644 --- a/src/matrix/Session.js +++ b/src/matrix/Session.js @@ -127,7 +127,6 @@ export class Session { this._e2eeAccount, PICKLE_KEY, this._olm, - this._storage, this._platform.clock.now, this._user.id, senderKeyLock diff --git a/src/matrix/e2ee/olm/Decryption.ts b/src/matrix/e2ee/olm/Decryption.ts index 7d9be4a3..77990586 100644 --- a/src/matrix/e2ee/olm/Decryption.ts +++ b/src/matrix/e2ee/olm/Decryption.ts @@ -58,7 +58,6 @@ export class Decryption { private readonly pickleKey: string, private readonly now: () => number, private readonly ownUserId: string, - private readonly storage: Storage, private readonly olm: Olm, private readonly senderKeyLock: LockMap ) {} From 78e0bb1ff0feac3f545ad0921ac8cd57fadd3f0e Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Fri, 18 Feb 2022 17:00:56 +0100 Subject: [PATCH 025/238] replace isPreKeyMessage with const enum --- src/matrix/e2ee/olm/Decryption.ts | 13 +++++-------- src/matrix/e2ee/olm/types.ts | 7 ++++++- 2 files changed, 11 insertions(+), 9 deletions(-) diff --git a/src/matrix/e2ee/olm/Decryption.ts b/src/matrix/e2ee/olm/Decryption.ts index 77990586..9698add9 100644 --- a/src/matrix/e2ee/olm/Decryption.ts +++ b/src/matrix/e2ee/olm/Decryption.ts @@ -19,6 +19,7 @@ import {groupBy} from "../../../utils/groupBy"; import {MultiLock, ILock} from "../../../utils/Lock"; import {Session} from "./Session"; import {DecryptionResult} from "../DecryptionResult"; +import {OlmPayloadType} from "./types"; import type {OlmMessage, OlmPayload} from "./types"; import type {Account} from "../Account"; @@ -42,10 +43,6 @@ type CreateAndDecryptResult = { plaintext: string }; -function isPreKeyMessage(message: OlmMessage): boolean { - return message.type === 0; -} - function sortSessions(sessions: Session[]) { sessions.sort((a, b) => { return b.data.lastUsed - a.data.lastUsed; @@ -151,7 +148,7 @@ export class Decryption { throw new DecryptionError("OLM_BAD_ENCRYPTED_MESSAGE", event, {senderKey, error: err.message}); } // could not decrypt with any existing session - if (typeof plaintext !== "string" && isPreKeyMessage(message)) { + if (typeof plaintext !== "string" && message.type === OlmPayloadType.PreKey) { let createResult: CreateAndDecryptResult; try { createResult = this._createSessionAndDecrypt(senderKey, message, timestamp); @@ -282,16 +279,16 @@ class SenderKeyDecryption { } const olmSession = session.load(); try { - if (isPreKeyMessage(message) && !olmSession.matches_inbound(message.body)) { + if (message.type === OlmPayloadType.PreKey && !olmSession.matches_inbound(message.body)) { return; } try { - const plaintext = olmSession.decrypt(message.type, message.body); + const plaintext = olmSession.decrypt(message.type as number, message.body!); session.save(olmSession); session.data.lastUsed = this.timestamp; return plaintext; } catch (err) { - if (isPreKeyMessage(message)) { + if (message.type === OlmPayloadType.PreKey) { throw new Error(`Error decrypting prekey message with existing session id ${session.id}: ${err.message}`); } // decryption failed, bail out diff --git a/src/matrix/e2ee/olm/types.ts b/src/matrix/e2ee/olm/types.ts index b9e394d5..5302dad8 100644 --- a/src/matrix/e2ee/olm/types.ts +++ b/src/matrix/e2ee/olm/types.ts @@ -14,8 +14,13 @@ See the License for the specific language governing permissions and limitations under the License. */ +export const enum OlmPayloadType { + PreKey = 0, + Normal = 1 +} + export type OlmMessage = { - type?: 0 | 1, + type?: OlmPayloadType, body?: string } From 620409b3f0bace8694ebd3698cf3dd9d4dfc351e Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Fri, 18 Feb 2022 17:10:26 +0100 Subject: [PATCH 026/238] fixup: ctor argument order as it was an object before, order didn't matter --- src/matrix/Session.js | 2 +- src/matrix/e2ee/olm/Encryption.ts | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/matrix/Session.js b/src/matrix/Session.js index ac060b0b..ae1dea61 100644 --- a/src/matrix/Session.js +++ b/src/matrix/Session.js @@ -126,9 +126,9 @@ export class Session { const olmDecryption = new OlmDecryption( this._e2eeAccount, PICKLE_KEY, - this._olm, this._platform.clock.now, this._user.id, + this._olm, senderKeyLock ); this._olmEncryption = new OlmEncryption( diff --git a/src/matrix/e2ee/olm/Encryption.ts b/src/matrix/e2ee/olm/Encryption.ts index ebc38170..9b754272 100644 --- a/src/matrix/e2ee/olm/Encryption.ts +++ b/src/matrix/e2ee/olm/Encryption.ts @@ -64,12 +64,12 @@ const MAX_BATCH_SIZE = 20; export class Encryption { constructor( private readonly account: Account, + private readonly pickleKey: string, private readonly olm: Olm, - private readonly olmUtil: Olm.Utility, - private readonly ownUserId: string, private readonly storage: Storage, private readonly now: () => number, - private readonly pickleKey: string, + private readonly ownUserId: string, + private readonly olmUtil: Olm.Utility, private readonly senderKeyLock: LockMap ) {} From 3330530f68e44a2d088c9a1fe09ebed5a757b9d0 Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Fri, 18 Feb 2022 17:18:25 +0100 Subject: [PATCH 027/238] Update src/matrix/e2ee/DecryptionResult.ts Co-authored-by: R Midhun Suresh --- src/matrix/e2ee/DecryptionResult.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/matrix/e2ee/DecryptionResult.ts b/src/matrix/e2ee/DecryptionResult.ts index 67c242bc..7735856a 100644 --- a/src/matrix/e2ee/DecryptionResult.ts +++ b/src/matrix/e2ee/DecryptionResult.ts @@ -43,7 +43,7 @@ export class DecryptionResult { public readonly claimedEd25519Key: string ) {} - setDevice(device: DeviceIdentity) { + setDevice(device: DeviceIdentity): void { this.device = device; } From 82299e5aeab357750ca7f27f271da5d73f6f2829 Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Fri, 18 Feb 2022 17:18:33 +0100 Subject: [PATCH 028/238] Update src/matrix/e2ee/olm/Decryption.ts Co-authored-by: R Midhun Suresh --- src/matrix/e2ee/olm/Decryption.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/matrix/e2ee/olm/Decryption.ts b/src/matrix/e2ee/olm/Decryption.ts index 9698add9..e437716a 100644 --- a/src/matrix/e2ee/olm/Decryption.ts +++ b/src/matrix/e2ee/olm/Decryption.ts @@ -43,7 +43,7 @@ type CreateAndDecryptResult = { plaintext: string }; -function sortSessions(sessions: Session[]) { +function sortSessions(sessions: Session[]): void { sessions.sort((a, b) => { return b.data.lastUsed - a.data.lastUsed; }); From 3f9f0e98c7166af620dcc26297ebac0765ff6313 Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Fri, 18 Feb 2022 17:21:14 +0100 Subject: [PATCH 029/238] remove unused olm property in SenderKeyDecryption --- src/matrix/e2ee/olm/Decryption.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/matrix/e2ee/olm/Decryption.ts b/src/matrix/e2ee/olm/Decryption.ts index e437716a..06ad18dc 100644 --- a/src/matrix/e2ee/olm/Decryption.ts +++ b/src/matrix/e2ee/olm/Decryption.ts @@ -122,7 +122,7 @@ export class Decryption { async _decryptAllForSenderKey(senderKey: string, events: OlmEncryptedEvent[], timestamp: number, readSessionsTxn: Transaction): Promise { const sessions = await this._getSessions(senderKey, readSessionsTxn); - const senderKeyDecryption = new SenderKeyDecryption(senderKey, sessions, this.olm, timestamp); + const senderKeyDecryption = new SenderKeyDecryption(senderKey, sessions, timestamp); const results: DecryptionResult[] = []; const errors: DecryptionError[] = []; // events for a single senderKey need to be decrypted one by one @@ -238,7 +238,6 @@ class SenderKeyDecryption { constructor( public readonly senderKey: string, public readonly sessions: Session[], - private readonly olm: Olm, private readonly timestamp: number ) {} From 8adc5a9faea2b30390944e01273c6ca0038cf133 Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Fri, 18 Feb 2022 17:24:55 +0100 Subject: [PATCH 030/238] these were public actually --- src/matrix/e2ee/olm/Decryption.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/matrix/e2ee/olm/Decryption.ts b/src/matrix/e2ee/olm/Decryption.ts index 06ad18dc..9db198be 100644 --- a/src/matrix/e2ee/olm/Decryption.ts +++ b/src/matrix/e2ee/olm/Decryption.ts @@ -306,8 +306,8 @@ class SenderKeyDecryption { class DecryptionChanges { constructor( private readonly senderKeyDecryptions: SenderKeyDecryption[], - private readonly results: DecryptionResult[], - private readonly errors: DecryptionError[], + public readonly results: DecryptionResult[], + public readonly errors: DecryptionError[], private readonly account: Account, private readonly lock: ILock ) {} From b993331e06e94971f575046e90d8d344dc4737c9 Mon Sep 17 00:00:00 2001 From: Eric Eastwood Date: Fri, 25 Feb 2022 01:40:52 -0600 Subject: [PATCH 031/238] Add more HTML form and SVG elements Split off from https://github.com/vector-im/hydrogen-web/pull/653 Personally using `select`, `option`, and `path` currently in https://github.com/matrix-org/matrix-public-archive but added a few extra SVG elements that seemed common to me. --- src/platform/web/ui/general/html.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/platform/web/ui/general/html.ts b/src/platform/web/ui/general/html.ts index 9ebcfaaf..44f7476a 100644 --- a/src/platform/web/ui/general/html.ts +++ b/src/platform/web/ui/general/html.ts @@ -104,8 +104,9 @@ export const TAG_NAMES = { "br", "a", "ol", "ul", "li", "div", "h1", "h2", "h3", "h4", "h5", "h6", "p", "strong", "em", "span", "img", "section", "main", "article", "aside", "del", "blockquote", "table", "thead", "tbody", "tr", "th", "td", "hr", - "pre", "code", "button", "time", "input", "textarea", "label", "form", "progress", "output", "video"], - [SVG_NS]: ["svg", "circle"] + "pre", "code", "button", "time", "input", "textarea", "select", "option", "label", "form", + "progress", "output", "video"], + [SVG_NS]: ["svg", "g", "path", "circle", "ellipse", "rect", "use"] } as const; export const tag: { [tagName in typeof TAG_NAMES[string][number]]: (attributes?: BasicAttributes | Child | Child[], children?: Child | Child[]) => Element } = {} as any; From 0935f2d23aebccf3bb1bdd7974189dde4c8fc2bc Mon Sep 17 00:00:00 2001 From: Eric Eastwood Date: Fri, 25 Feb 2022 01:59:48 -0600 Subject: [PATCH 032/238] Only try to use window.crypto.subtle in secure contexts to avoid it throwing and stopping all JavaScript Relevant error if you crypto is used in a non-secure context like a local LAN IP `http://192.168.1.151:3050/` ``` Uncaught (in promise) TypeError: Cannot read properties of undefined (reading 'deriveBits') at new Crypto at new Platform at mountHydrogen ``` For my use-case with https://github.com/matrix-org/matrix-public-archive, I don't need crypto/encryption at all. Docs: - https://developer.mozilla.org/en-US/docs/Web/Security/Secure_Contexts - https://developer.mozilla.org/en-US/docs/Web/API/Crypto/subtle - "Secure context: This feature is available only in secure contexts (HTTPS), in some or all supporting browsers." --- Related to https://github.com/vector-im/hydrogen-web/issues/579 --- src/platform/web/Platform.js | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/platform/web/Platform.js b/src/platform/web/Platform.js index 9de3d4ce..b6fdfae5 100644 --- a/src/platform/web/Platform.js +++ b/src/platform/web/Platform.js @@ -143,7 +143,10 @@ export class Platform { this._serviceWorkerHandler.registerAndStart(assetPaths.serviceWorker); } this.notificationService = new NotificationService(this._serviceWorkerHandler, config.push); - this.crypto = new Crypto(cryptoExtras); + // `window.crypto.subtle` is only available in a secure context + if(window.isSecureContext) { + this.crypto = new Crypto(cryptoExtras); + } this.storageFactory = new StorageFactory(this._serviceWorkerHandler); this.sessionInfoStorage = new SessionInfoStorage("hydrogen_sessions_v1"); this.estimateStorageUsage = estimateStorageUsage; From 7055f02f168f0689c340e0b080129261015999a1 Mon Sep 17 00:00:00 2001 From: Tushar Date: Fri, 25 Feb 2022 15:52:54 +0530 Subject: [PATCH 033/238] typescriptify domain/avatar.js --- src/domain/SessionPickerViewModel.js | 2 +- src/domain/{avatar.js => avatar.ts} | 15 +++++++++------ src/domain/session/leftpanel/BaseTileViewModel.js | 2 +- .../session/rightpanel/MemberDetailsViewModel.js | 2 +- .../session/rightpanel/MemberTileViewModel.js | 2 +- .../session/rightpanel/RoomDetailsViewModel.js | 2 +- src/domain/session/room/InviteViewModel.js | 2 +- .../session/room/RoomBeingCreatedViewModel.js | 2 +- src/domain/session/room/RoomViewModel.js | 2 +- src/domain/session/room/timeline/MessageBody.js | 2 +- .../room/timeline/tiles/BaseMessageTile.js | 2 +- src/platform/web/Platform.js | 2 +- src/platform/web/ui/AvatarView.js | 2 +- src/platform/web/ui/session/room/InviteView.js | 2 +- .../ui/session/room/timeline/BaseMessageView.js | 2 +- 15 files changed, 23 insertions(+), 20 deletions(-) rename src/domain/{avatar.js => avatar.ts} (74%) diff --git a/src/domain/SessionPickerViewModel.js b/src/domain/SessionPickerViewModel.js index 6714e96f..e486c64f 100644 --- a/src/domain/SessionPickerViewModel.js +++ b/src/domain/SessionPickerViewModel.js @@ -16,7 +16,7 @@ limitations under the License. import {SortedArray} from "../observable/index.js"; import {ViewModel} from "./ViewModel"; -import {avatarInitials, getIdentifierColorNumber} from "./avatar.js"; +import {avatarInitials, getIdentifierColorNumber} from "./avatar"; class SessionItemViewModel extends ViewModel { constructor(options, pickerVM) { diff --git a/src/domain/avatar.js b/src/domain/avatar.ts similarity index 74% rename from src/domain/avatar.js rename to src/domain/avatar.ts index 5b32020b..6f1ef8b0 100644 --- a/src/domain/avatar.js +++ b/src/domain/avatar.ts @@ -14,7 +14,10 @@ See the License for the specific language governing permissions and limitations under the License. */ -export function avatarInitials(name) { +import { Platform } from "../platform/web/Platform"; +import { MediaRepository } from "../matrix/net/MediaRepository"; + +export function avatarInitials(name: string): string { let firstChar = name.charAt(0); if (firstChar === "!" || firstChar === "@" || firstChar === "#") { firstChar = name.charAt(1); @@ -29,10 +32,10 @@ export function avatarInitials(name) { * * @return {number} */ -function hashCode(str) { +function hashCode(str: string): number { let hash = 0; - let i; - let chr; + let i: number; + let chr: number; if (str.length === 0) { return hash; } @@ -44,11 +47,11 @@ function hashCode(str) { return Math.abs(hash); } -export function getIdentifierColorNumber(id) { +export function getIdentifierColorNumber(id: string): number { return (hashCode(id) % 8) + 1; } -export function getAvatarHttpUrl(avatarUrl, cssSize, platform, mediaRepository) { +export function getAvatarHttpUrl(avatarUrl: string, cssSize: number, platform: Platform, mediaRepository: MediaRepository): string | null { if (avatarUrl) { const imageSize = cssSize * platform.devicePixelRatio; return mediaRepository.mxcUrlThumbnail(avatarUrl, imageSize, imageSize, "crop"); diff --git a/src/domain/session/leftpanel/BaseTileViewModel.js b/src/domain/session/leftpanel/BaseTileViewModel.js index e1d6dfff..8f5106bf 100644 --- a/src/domain/session/leftpanel/BaseTileViewModel.js +++ b/src/domain/session/leftpanel/BaseTileViewModel.js @@ -15,7 +15,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -import {avatarInitials, getIdentifierColorNumber, getAvatarHttpUrl} from "../../avatar.js"; +import {avatarInitials, getIdentifierColorNumber, getAvatarHttpUrl} from "../../avatar"; import {ViewModel} from "../../ViewModel"; const KIND_ORDER = ["roomBeingCreated", "invite", "room"]; diff --git a/src/domain/session/rightpanel/MemberDetailsViewModel.js b/src/domain/session/rightpanel/MemberDetailsViewModel.js index 8ee50030..b3c8278c 100644 --- a/src/domain/session/rightpanel/MemberDetailsViewModel.js +++ b/src/domain/session/rightpanel/MemberDetailsViewModel.js @@ -16,7 +16,7 @@ limitations under the License. import {ViewModel} from "../../ViewModel"; import {RoomType} from "../../../matrix/room/common"; -import {avatarInitials, getIdentifierColorNumber, getAvatarHttpUrl} from "../../avatar.js"; +import {avatarInitials, getIdentifierColorNumber, getAvatarHttpUrl} from "../../avatar"; export class MemberDetailsViewModel extends ViewModel { constructor(options) { diff --git a/src/domain/session/rightpanel/MemberTileViewModel.js b/src/domain/session/rightpanel/MemberTileViewModel.js index 9062ea7d..153c70c8 100644 --- a/src/domain/session/rightpanel/MemberTileViewModel.js +++ b/src/domain/session/rightpanel/MemberTileViewModel.js @@ -15,7 +15,7 @@ limitations under the License. */ import {ViewModel} from "../../ViewModel"; -import {avatarInitials, getIdentifierColorNumber, getAvatarHttpUrl} from "../../avatar.js"; +import {avatarInitials, getIdentifierColorNumber, getAvatarHttpUrl} from "../../avatar"; export class MemberTileViewModel extends ViewModel { constructor(options) { diff --git a/src/domain/session/rightpanel/RoomDetailsViewModel.js b/src/domain/session/rightpanel/RoomDetailsViewModel.js index 97e8588e..4e2735b1 100644 --- a/src/domain/session/rightpanel/RoomDetailsViewModel.js +++ b/src/domain/session/rightpanel/RoomDetailsViewModel.js @@ -15,7 +15,7 @@ limitations under the License. */ import {ViewModel} from "../../ViewModel"; -import {avatarInitials, getIdentifierColorNumber, getAvatarHttpUrl} from "../../avatar.js"; +import {avatarInitials, getIdentifierColorNumber, getAvatarHttpUrl} from "../../avatar"; export class RoomDetailsViewModel extends ViewModel { constructor(options) { diff --git a/src/domain/session/room/InviteViewModel.js b/src/domain/session/room/InviteViewModel.js index c2ff74e0..00697642 100644 --- a/src/domain/session/room/InviteViewModel.js +++ b/src/domain/session/room/InviteViewModel.js @@ -15,7 +15,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -import {avatarInitials, getIdentifierColorNumber, getAvatarHttpUrl} from "../../avatar.js"; +import {avatarInitials, getIdentifierColorNumber, getAvatarHttpUrl} from "../../avatar"; import {ViewModel} from "../../ViewModel"; export class InviteViewModel extends ViewModel { diff --git a/src/domain/session/room/RoomBeingCreatedViewModel.js b/src/domain/session/room/RoomBeingCreatedViewModel.js index f5c5d3cd..b503af73 100644 --- a/src/domain/session/room/RoomBeingCreatedViewModel.js +++ b/src/domain/session/room/RoomBeingCreatedViewModel.js @@ -15,7 +15,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -import {avatarInitials, getIdentifierColorNumber, getAvatarHttpUrl} from "../../avatar.js"; +import {avatarInitials, getIdentifierColorNumber, getAvatarHttpUrl} from "../../avatar"; import {ViewModel} from "../../ViewModel"; export class RoomBeingCreatedViewModel extends ViewModel { diff --git a/src/domain/session/room/RoomViewModel.js b/src/domain/session/room/RoomViewModel.js index b7af00ce..71060728 100644 --- a/src/domain/session/room/RoomViewModel.js +++ b/src/domain/session/room/RoomViewModel.js @@ -17,7 +17,7 @@ limitations under the License. import {TimelineViewModel} from "./timeline/TimelineViewModel.js"; import {ComposerViewModel} from "./ComposerViewModel.js" -import {avatarInitials, getIdentifierColorNumber, getAvatarHttpUrl} from "../../avatar.js"; +import {avatarInitials, getIdentifierColorNumber, getAvatarHttpUrl} from "../../avatar"; import {tilesCreator} from "./timeline/tilesCreator.js"; import {ViewModel} from "../../ViewModel"; import {imageToInfo} from "../common.js"; diff --git a/src/domain/session/room/timeline/MessageBody.js b/src/domain/session/room/timeline/MessageBody.js index a8bf2497..65b487a9 100644 --- a/src/domain/session/room/timeline/MessageBody.js +++ b/src/domain/session/room/timeline/MessageBody.js @@ -1,5 +1,5 @@ import { linkify } from "./linkify/linkify.js"; -import { getIdentifierColorNumber, avatarInitials } from "../../../avatar.js"; +import { getIdentifierColorNumber, avatarInitials } from "../../../avatar"; /** * Parse text into parts such as newline, links and text. diff --git a/src/domain/session/room/timeline/tiles/BaseMessageTile.js b/src/domain/session/room/timeline/tiles/BaseMessageTile.js index 6b0b4356..3385a587 100644 --- a/src/domain/session/room/timeline/tiles/BaseMessageTile.js +++ b/src/domain/session/room/timeline/tiles/BaseMessageTile.js @@ -16,7 +16,7 @@ limitations under the License. import {SimpleTile} from "./SimpleTile.js"; import {ReactionsViewModel} from "../ReactionsViewModel.js"; -import {getIdentifierColorNumber, avatarInitials, getAvatarHttpUrl} from "../../../../avatar.js"; +import {getIdentifierColorNumber, avatarInitials, getAvatarHttpUrl} from "../../../../avatar"; export class BaseMessageTile extends SimpleTile { constructor(options) { diff --git a/src/platform/web/Platform.js b/src/platform/web/Platform.js index 9de3d4ce..56cf4fc5 100644 --- a/src/platform/web/Platform.js +++ b/src/platform/web/Platform.js @@ -37,7 +37,7 @@ import {hasReadPixelPermission, ImageHandle, VideoHandle} from "./dom/ImageHandl import {downloadInIframe} from "./dom/download.js"; import {Disposables} from "../../utils/Disposables"; import {parseHTML} from "./parsehtml.js"; -import {handleAvatarError} from "./ui/avatar.js"; +import {handleAvatarError} from "./ui/avatar"; function addScript(src) { return new Promise(function (resolve, reject) { diff --git a/src/platform/web/ui/AvatarView.js b/src/platform/web/ui/AvatarView.js index f2d94e3b..551f7307 100644 --- a/src/platform/web/ui/AvatarView.js +++ b/src/platform/web/ui/AvatarView.js @@ -15,7 +15,7 @@ limitations under the License. */ import {BaseUpdateView} from "./general/BaseUpdateView"; -import {renderStaticAvatar, renderImg} from "./avatar.js"; +import {renderStaticAvatar, renderImg} from "./avatar"; /* optimization to not use a sub view when changing between img and text diff --git a/src/platform/web/ui/session/room/InviteView.js b/src/platform/web/ui/session/room/InviteView.js index 9d808abf..99345360 100644 --- a/src/platform/web/ui/session/room/InviteView.js +++ b/src/platform/web/ui/session/room/InviteView.js @@ -16,7 +16,7 @@ limitations under the License. */ import {TemplateView} from "../../general/TemplateView"; -import {renderStaticAvatar} from "../../avatar.js"; +import {renderStaticAvatar} from "../../avatar"; export class InviteView extends TemplateView { render(t, vm) { diff --git a/src/platform/web/ui/session/room/timeline/BaseMessageView.js b/src/platform/web/ui/session/room/timeline/BaseMessageView.js index a6fbb9be..9b583103 100644 --- a/src/platform/web/ui/session/room/timeline/BaseMessageView.js +++ b/src/platform/web/ui/session/room/timeline/BaseMessageView.js @@ -15,7 +15,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -import {renderStaticAvatar} from "../../../avatar.js"; +import {renderStaticAvatar} from "../../../avatar"; import {tag} from "../../../general/html"; import {mountView} from "../../../general/utils"; import {TemplateView} from "../../../general/TemplateView"; From 17acda77414fe0d4ca5ae74c949dfd65b1e75a20 Mon Sep 17 00:00:00 2001 From: Tushar Date: Fri, 25 Feb 2022 16:45:07 +0530 Subject: [PATCH 034/238] typescriptify domain/LogoutViewModel.js --- ...{LogoutViewModel.js => LogoutViewModel.ts} | 23 ++++++++++++------- src/domain/RootViewModel.js | 2 +- src/domain/ViewModel.ts | 4 ++-- 3 files changed, 18 insertions(+), 11 deletions(-) rename src/domain/{LogoutViewModel.js => LogoutViewModel.ts} (76%) diff --git a/src/domain/LogoutViewModel.js b/src/domain/LogoutViewModel.ts similarity index 76% rename from src/domain/LogoutViewModel.js rename to src/domain/LogoutViewModel.ts index 24ca440e..3edfcad5 100644 --- a/src/domain/LogoutViewModel.js +++ b/src/domain/LogoutViewModel.ts @@ -14,11 +14,18 @@ See the License for the specific language governing permissions and limitations under the License. */ -import {ViewModel} from "./ViewModel"; +import {Options, ViewModel} from "./ViewModel"; import {Client} from "../matrix/Client.js"; -export class LogoutViewModel extends ViewModel { - constructor(options) { +type LogoutOptions = { sessionId: string; } & Options; + +export class LogoutViewModel extends ViewModel { + private _sessionId: string; + private _busy: boolean; + private _showConfirm: boolean; + private _error?: Error; + + constructor(options: LogoutOptions) { super(options); this._sessionId = options.sessionId; this._busy = false; @@ -26,19 +33,19 @@ export class LogoutViewModel extends ViewModel { this._error = undefined; } - get showConfirm() { + get showConfirm(): boolean { return this._showConfirm; } - get busy() { + get busy(): boolean { return this._busy; } - get cancelUrl() { + get cancelUrl(): string { return this.urlCreator.urlForSegment("session", true); } - async logout() { + async logout(): Promise { this._busy = true; this._showConfirm = false; this.emitChange("busy"); @@ -53,7 +60,7 @@ export class LogoutViewModel extends ViewModel { } } - get status() { + get status(): string { if (this._error) { return this.i18n`Could not log out of device: ${this._error.message}`; } else { diff --git a/src/domain/RootViewModel.js b/src/domain/RootViewModel.js index 642e43f4..2711cd2f 100644 --- a/src/domain/RootViewModel.js +++ b/src/domain/RootViewModel.js @@ -18,7 +18,7 @@ import {Client} from "../matrix/Client.js"; import {SessionViewModel} from "./session/SessionViewModel.js"; import {SessionLoadViewModel} from "./SessionLoadViewModel.js"; import {LoginViewModel} from "./login/LoginViewModel.js"; -import {LogoutViewModel} from "./LogoutViewModel.js"; +import {LogoutViewModel} from "./LogoutViewModel"; import {SessionPickerViewModel} from "./SessionPickerViewModel.js"; import {ViewModel} from "./ViewModel"; diff --git a/src/domain/ViewModel.ts b/src/domain/ViewModel.ts index 458b2840..cfe22326 100644 --- a/src/domain/ViewModel.ts +++ b/src/domain/ViewModel.ts @@ -29,7 +29,7 @@ import type {ILogger} from "../logging/types"; import type {Navigation} from "./navigation/Navigation"; import type {URLRouter} from "./navigation/URLRouter"; -type Options = { +export type Options = { platform: Platform logger: ILogger urlCreator: URLRouter @@ -95,7 +95,7 @@ export class ViewModel extends EventEmitter<{change // // translated string should probably always be bindings, unless we're fine with a refresh when changing the language? // we probably are, if we're using routing with a url, we could just refresh. - i18n(parts: string[], ...expr: any[]) { + i18n(parts: TemplateStringsArray, ...expr: any[]) { // just concat for now let result = ""; for (let i = 0; i < parts.length; ++i) { From cd007b40e1f2cf2f23a429c51a4971d6770b3312 Mon Sep 17 00:00:00 2001 From: Eric Eastwood Date: Sat, 26 Feb 2022 01:12:00 -0600 Subject: [PATCH 035/238] Make the SDK friendly to locally link and develop on MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Fix https://github.com/vector-im/hydrogen-web/issues/686 Fix https://github.com/vector-im/hydrogen-web/issues/682 Instead of deleting the whole `target/` directory, leave it alone so the symlink driving the `npm link`/`yarn link` stays in tact. Leave Vite builds in their build directories (`/lib-build`/`/asset-build`) so you can `vite build --watch` to build on local changes and still have a consisent place to reference in the `package.json` `exports`. Previously, everything relied on `build.sh` which does a bunch of moving and renaming and made it hard to rebuild on changes. Add back support for CommonJS (adding the `package.json` `exports`). The last piece is making sure the `?url` imports (`import workerPath from 'hydrogen-view-sdk/main.js?url';`) work still. It looks like this may have just been solved via https://github.com/vitejs/vite/issues/6725 -> https://github.com/vitejs/vite/pull/7073 (literally 2 days ago) and we just need to wait for the next Vite release 🎉 --- scripts/sdk/base-manifest.json | 13 ++++++++++++- scripts/sdk/build.sh | 12 +++--------- scripts/sdk/create-manifest.js | 16 +--------------- vite.sdk-assets-config.js | 19 +++++++++++++++++++ 4 files changed, 35 insertions(+), 25 deletions(-) diff --git a/scripts/sdk/base-manifest.json b/scripts/sdk/base-manifest.json index 3ee2ca3b..a7e287e4 100644 --- a/scripts/sdk/base-manifest.json +++ b/scripts/sdk/base-manifest.json @@ -2,6 +2,17 @@ "name": "hydrogen-view-sdk", "description": "Embeddable matrix client library, including view components", "version": "0.0.5", - "main": "./hydrogen.es.js", + "main": "./hydrogen.cjs.js", + "exports": { + ".": { + "import": "./lib-build/hydrogen.es.js", + "require": "./lib-build/hydrogen.cjs.js" + }, + "./paths/vite": "./paths/vite.js", + "./style.css": "./asset-build/assets/index.css", + "./main.js": "./asset-build/assets/download-sandbox.html", + "./download-sandbox.html": "./asset-build/assets/download-sandbox.html", + "./assets/*": "./asset-build/assets/*" + }, "type": "module" } diff --git a/scripts/sdk/build.sh b/scripts/sdk/build.sh index 5534601e..9063d7fb 100755 --- a/scripts/sdk/build.sh +++ b/scripts/sdk/build.sh @@ -1,5 +1,5 @@ #!/bin/bash -rm -rf target +rm -rf target/* yarn run vite build -c vite.sdk-assets-config.js yarn run vite build -c vite.sdk-lib-config.js yarn tsc -p tsconfig-declaration.json @@ -10,13 +10,7 @@ mkdir target/paths cp doc/SDK.md target/README.md pushd target pushd asset-build/assets -mv main.*.js ../../main.js -mv index.*.css ../../style.css -mv download-sandbox.*.html ../../download-sandbox.html -rm *.js *.wasm -mv ./* ../../ +rm !(main).js *.wasm popd -rm -rf asset-build -mv lib-build/* . -rm -rf lib-build +rm index.html popd diff --git a/scripts/sdk/create-manifest.js b/scripts/sdk/create-manifest.js index b420e679..9d5cebb2 100755 --- a/scripts/sdk/create-manifest.js +++ b/scripts/sdk/create-manifest.js @@ -3,21 +3,7 @@ const fs = require("fs"); const appManifest = require("../../package.json"); const baseSDKManifest = require("./base-manifest.json"); /* - need to leave exports out of base-manifest.json because of #vite-bug, - with the downside that we can't support environments that support - both esm and commonjs modules, so we pick just esm. - ``` - "exports": { - ".": { - "import": "./hydrogen.es.js", - "require": "./hydrogen.cjs.js" - }, - "./paths/vite": "./paths/vite.js", - "./style.css": "./style.css" - }, - ``` - - Also need to leave typescript type definitions out until the + Need to leave typescript type definitions out until the typescript conversion is complete and all imports in the d.ts files exists. ``` diff --git a/vite.sdk-assets-config.js b/vite.sdk-assets-config.js index 90720966..ebb95e4e 100644 --- a/vite.sdk-assets-config.js +++ b/vite.sdk-assets-config.js @@ -2,10 +2,29 @@ const path = require("path"); const mergeOptions = require('merge-options'); const commonOptions = require("./vite.common-config.js"); +const pathsToExport = [ + "index.js", + "index.css", + "download-sandbox.html" +]; + export default mergeOptions(commonOptions, { root: "src/", base: "./", build: { outDir: "../target/asset-build/", + rollupOptions: { + output: { + assetFileNames: (chunkInfo) => { + // Get rid of the hash so we can consistently reference these + // files in our `package.json` `exports` + if(pathsToExport.includes(path.basename(chunkInfo.name))) { + return "assets/[name].[ext]"; + } + + return "assets/[name]-[hash][extname]"; + } + } + } }, }); From 8fb2b2755a489f70e5ef32c6ffdc4441010d47b2 Mon Sep 17 00:00:00 2001 From: Eric Eastwood Date: Sat, 26 Feb 2022 03:08:16 -0600 Subject: [PATCH 036/238] Fix typos pointing to wrong files --- scripts/sdk/base-manifest.json | 2 +- vite.sdk-assets-config.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/scripts/sdk/base-manifest.json b/scripts/sdk/base-manifest.json index a7e287e4..71ef28e2 100644 --- a/scripts/sdk/base-manifest.json +++ b/scripts/sdk/base-manifest.json @@ -10,7 +10,7 @@ }, "./paths/vite": "./paths/vite.js", "./style.css": "./asset-build/assets/index.css", - "./main.js": "./asset-build/assets/download-sandbox.html", + "./main.js": "./asset-build/assets/main.js", "./download-sandbox.html": "./asset-build/assets/download-sandbox.html", "./assets/*": "./asset-build/assets/*" }, diff --git a/vite.sdk-assets-config.js b/vite.sdk-assets-config.js index ebb95e4e..0d769734 100644 --- a/vite.sdk-assets-config.js +++ b/vite.sdk-assets-config.js @@ -3,7 +3,7 @@ const mergeOptions = require('merge-options'); const commonOptions = require("./vite.common-config.js"); const pathsToExport = [ - "index.js", + "main.js", "index.css", "download-sandbox.html" ]; From 0023ab34baf0666b9fa573edcec8861db6d62056 Mon Sep 17 00:00:00 2001 From: Eric Eastwood Date: Sat, 26 Feb 2022 05:19:59 -0600 Subject: [PATCH 037/238] Add a placeholder for upgrading vite to comment on --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 8fa27f47..cec780ae 100644 --- a/package.json +++ b/package.json @@ -44,7 +44,7 @@ "regenerator-runtime": "^0.13.7", "text-encoding": "^0.7.0", "typescript": "^4.3.5", - "vite": "^2.6.14", + "vite": "todo: wait for next Vite release", "xxhashjs": "^0.2.2", "bs58": "^4.0.1" }, From 4a4856a29e973dc8076c2b02eb738141b0383af9 Mon Sep 17 00:00:00 2001 From: Ajay Bura <32841439+ajbura@users.noreply.github.com> Date: Mon, 28 Feb 2022 17:19:01 +0530 Subject: [PATCH 038/238] export module --- src/lib.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/lib.ts b/src/lib.ts index 89cd7706..ca75f6c9 100644 --- a/src/lib.ts +++ b/src/lib.ts @@ -34,3 +34,7 @@ export {TemplateView} from "./platform/web/ui/general/TemplateView"; export {ViewModel} from "./domain/ViewModel"; export {LoadingView} from "./platform/web/ui/general/LoadingView.js"; export {AvatarView} from "./platform/web/ui/AvatarView.js"; +export {RoomType} from "./matrix/room/common"; +export {EventEmitter} from "./utils/EventEmitter"; +export {Disposables} from "./utils/Disposables"; +export * from "./observable/"; From 4c50dbf7ec5d204183a58c4a2d95af9a46dff27c Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Tue, 1 Mar 2022 15:41:44 +0100 Subject: [PATCH 039/238] make SDK exports explicit --- src/lib.ts | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/src/lib.ts b/src/lib.ts index ca75f6c9..e846a378 100644 --- a/src/lib.ts +++ b/src/lib.ts @@ -37,4 +37,12 @@ export {AvatarView} from "./platform/web/ui/AvatarView.js"; export {RoomType} from "./matrix/room/common"; export {EventEmitter} from "./utils/EventEmitter"; export {Disposables} from "./utils/Disposables"; -export * from "./observable/"; +// these should eventually be moved to another library +export { + ObservableArray, + SortedArray, + MappedList, + AsyncMappedList, + ConcatList, + ObservableMap +} from "./observable/index"; From ee8e45926f89d7cf8516a840fa392680d2d74dda Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Tue, 1 Mar 2022 15:42:04 +0100 Subject: [PATCH 040/238] also export observable value classes --- src/lib.ts | 6 ++++++ src/observable/ObservableValue.ts | 2 +- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/src/lib.ts b/src/lib.ts index e846a378..c0b05032 100644 --- a/src/lib.ts +++ b/src/lib.ts @@ -46,3 +46,9 @@ export { ConcatList, ObservableMap } from "./observable/index"; +export { + BaseObservableValue, + IWaitHandle, + ObservableValue, + RetainedObservableValue +} from "./observable/ObservableValue"; diff --git a/src/observable/ObservableValue.ts b/src/observable/ObservableValue.ts index ad0a226d..65406700 100644 --- a/src/observable/ObservableValue.ts +++ b/src/observable/ObservableValue.ts @@ -41,7 +41,7 @@ export abstract class BaseObservableValue extends BaseObservable<(value: T) = } } -interface IWaitHandle { +export interface IWaitHandle { promise: Promise; dispose(): void; } From 42141c7063c91c909dc40e4c72ddb6fd7e6e3bfe Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Tue, 1 Mar 2022 15:45:24 +0100 Subject: [PATCH 041/238] bump SDK version --- scripts/sdk/base-manifest.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/sdk/base-manifest.json b/scripts/sdk/base-manifest.json index 8df205cb..ba0e1f4f 100644 --- a/scripts/sdk/base-manifest.json +++ b/scripts/sdk/base-manifest.json @@ -1,7 +1,7 @@ { "name": "hydrogen-view-sdk", "description": "Embeddable matrix client library, including view components", - "version": "0.0.8", + "version": "0.0.9", "main": "./hydrogen.es.js", "type": "module" } From 643ab1a5f302594f315e1476573740d53425a90f Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Tue, 1 Mar 2022 15:48:42 +0100 Subject: [PATCH 042/238] cant export this for some reason --- src/lib.ts | 1 - src/observable/ObservableValue.ts | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/src/lib.ts b/src/lib.ts index c0b05032..a0ada84f 100644 --- a/src/lib.ts +++ b/src/lib.ts @@ -48,7 +48,6 @@ export { } from "./observable/index"; export { BaseObservableValue, - IWaitHandle, ObservableValue, RetainedObservableValue } from "./observable/ObservableValue"; diff --git a/src/observable/ObservableValue.ts b/src/observable/ObservableValue.ts index 65406700..ad0a226d 100644 --- a/src/observable/ObservableValue.ts +++ b/src/observable/ObservableValue.ts @@ -41,7 +41,7 @@ export abstract class BaseObservableValue extends BaseObservable<(value: T) = } } -export interface IWaitHandle { +interface IWaitHandle { promise: Promise; dispose(): void; } From b6d9993ed03e52fa052ec8c306aae7027e56ba56 Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Tue, 1 Mar 2022 17:08:49 +0100 Subject: [PATCH 043/238] remove unused import --- src/matrix/e2ee/olm/Decryption.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/matrix/e2ee/olm/Decryption.ts b/src/matrix/e2ee/olm/Decryption.ts index 9db198be..0f96f2fc 100644 --- a/src/matrix/e2ee/olm/Decryption.ts +++ b/src/matrix/e2ee/olm/Decryption.ts @@ -24,7 +24,6 @@ import {OlmPayloadType} from "./types"; import type {OlmMessage, OlmPayload} from "./types"; import type {Account} from "../Account"; import type {LockMap} from "../../../utils/LockMap"; -import type {Storage} from "../../storage/idb/Storage"; import type {Transaction} from "../../storage/idb/Transaction"; import type {OlmEncryptedEvent} from "./types"; import type * as OlmNamespace from "@matrix-org/olm"; From c09964dc30c0782e84427413d2493de7e673018e Mon Sep 17 00:00:00 2001 From: Eric Eastwood Date: Tue, 1 Mar 2022 18:36:14 -0600 Subject: [PATCH 044/238] Add `data-event-id="$xxx"` attributes to timeline items for easy selecting in end-to-end tests (#690) Split out from https://github.com/vector-im/hydrogen-web/pull/653 Example test assertions: https://github.com/matrix-org/matrix-public-archive/blob/db6d3797d74104ad7c93e572ed106f1a685a90d0/test/e2e-tests.js#L248-L252 ```js // Make sure the $abc event on the page has "foobarbaz" text in it assert.match( dom.document.querySelector(`[data-event-id="$abc"]`).outerHTML, new RegExp(`.*foobarbaz.*`) ); ``` --- .../session/room/timeline/tiles/SimpleTile.js | 4 ++++ .../session/room/timeline/BaseMessageView.js | 19 +++++++++++-------- 2 files changed, 15 insertions(+), 8 deletions(-) diff --git a/src/domain/session/room/timeline/tiles/SimpleTile.js b/src/domain/session/room/timeline/tiles/SimpleTile.js index 3497b689..af2b0e12 100644 --- a/src/domain/session/room/timeline/tiles/SimpleTile.js +++ b/src/domain/session/room/timeline/tiles/SimpleTile.js @@ -44,6 +44,10 @@ export class SimpleTile extends ViewModel { return this._entry.asEventKey(); } + get eventId() { + return this._entry.id; + } + get isPending() { return this._entry.isPending; } diff --git a/src/platform/web/ui/session/room/timeline/BaseMessageView.js b/src/platform/web/ui/session/room/timeline/BaseMessageView.js index 9b583103..7356cd2b 100644 --- a/src/platform/web/ui/session/room/timeline/BaseMessageView.js +++ b/src/platform/web/ui/session/room/timeline/BaseMessageView.js @@ -40,14 +40,17 @@ export class BaseMessageView extends TemplateView { if (this._interactive) { children.push(t.button({className: "Timeline_messageOptions"}, "⋯")); } - const li = t.el(this._tagName, {className: { - "Timeline_message": true, - own: vm.isOwn, - unsent: vm.isUnsent, - unverified: vm.isUnverified, - disabled: !this._interactive, - continuation: vm => vm.isContinuation, - }}, children); + const li = t.el(this._tagName, { + className: { + "Timeline_message": true, + own: vm.isOwn, + unsent: vm.isUnsent, + unverified: vm.isUnverified, + disabled: !this._interactive, + continuation: vm => vm.isContinuation, + }, + 'data-event-id': vm.eventId + }, children); // given that there can be many tiles, we don't add // unneeded DOM nodes in case of a continuation, and we add it // with a side-effect binding to not have to create sub views, From 2f4c639cef3246386cf9e739ffbd1039184f5678 Mon Sep 17 00:00:00 2001 From: Eric Eastwood Date: Wed, 2 Mar 2022 03:17:59 -0600 Subject: [PATCH 045/238] Only initialize Crypto when olm is provided See https://github.com/vector-im/hydrogen-web/pull/691#discussion_r816988082 --- src/platform/web/Platform.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/platform/web/Platform.js b/src/platform/web/Platform.js index b6fdfae5..8c8b4fac 100644 --- a/src/platform/web/Platform.js +++ b/src/platform/web/Platform.js @@ -143,8 +143,8 @@ export class Platform { this._serviceWorkerHandler.registerAndStart(assetPaths.serviceWorker); } this.notificationService = new NotificationService(this._serviceWorkerHandler, config.push); - // `window.crypto.subtle` is only available in a secure context - if(window.isSecureContext) { + // Only try to use crypto when olm is provided + if(this._assetPaths.olm) { this.crypto = new Crypto(cryptoExtras); } this.storageFactory = new StorageFactory(this._serviceWorkerHandler); From 61ce2f9e3d1a408d8c8f6b0494d5b0e0687d0beb Mon Sep 17 00:00:00 2001 From: Ajay Bura <32841439+ajbura@users.noreply.github.com> Date: Thu, 3 Mar 2022 15:36:25 +0530 Subject: [PATCH 046/238] Add observeNavigation in ViewModel --- src/domain/ViewModel.ts | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/domain/ViewModel.ts b/src/domain/ViewModel.ts index cfe22326..04fec86e 100644 --- a/src/domain/ViewModel.ts +++ b/src/domain/ViewModel.ts @@ -58,6 +58,14 @@ export class ViewModel extends EventEmitter<{change return this._options[name]; } + observeNavigation(type: string, onChange: (value: true | string, type: string) => void) { + const segmentObservable = this.navigation.observe(type); + const unsubscribe = segmentObservable.subscribe((value: true | string) => { + onChange(value, type); + }) + this.track(unsubscribe); + } + track(disposable: D): D { if (!this.disposables) { this.disposables = new Disposables(); From 60d60e95723de1e3d64ebd0bdc71d3314d67875b Mon Sep 17 00:00:00 2001 From: RMidhunSuresh Date: Thu, 3 Mar 2022 19:58:46 +0530 Subject: [PATCH 047/238] WIP --- package.json | 5 +- postcss/css-compile-variables.js | 87 ++++++++++++++++++++++++++++++++ yarn.lock | 30 ++++++++++- 3 files changed, 119 insertions(+), 3 deletions(-) create mode 100644 postcss/css-compile-variables.js diff --git a/package.json b/package.json index 8fa27f47..3c007244 100644 --- a/package.json +++ b/package.json @@ -30,6 +30,7 @@ "acorn": "^8.6.0", "acorn-walk": "^8.2.0", "aes-js": "^3.1.2", + "bs58": "^4.0.1", "core-js": "^3.6.5", "es6-promise": "https://github.com/bwindels/es6-promise.git#bwindels/expose-flush", "escodegen": "^2.0.0", @@ -45,13 +46,13 @@ "text-encoding": "^0.7.0", "typescript": "^4.3.5", "vite": "^2.6.14", - "xxhashjs": "^0.2.2", - "bs58": "^4.0.1" + "xxhashjs": "^0.2.2" }, "dependencies": { "@matrix-org/olm": "https://gitlab.matrix.org/api/v4/projects/27/packages/npm/@matrix-org/olm/-/@matrix-org/olm-3.2.3.tgz", "another-json": "^0.2.0", "base64-arraybuffer": "^0.2.0", + "color": "^4.2.1", "dompurify": "^2.3.0" } } diff --git a/postcss/css-compile-variables.js b/postcss/css-compile-variables.js new file mode 100644 index 00000000..2395f87d --- /dev/null +++ b/postcss/css-compile-variables.js @@ -0,0 +1,87 @@ +import { Color } from "color"; + +let aliasMap; +let resolvedMap; +const RE_VARIABLE_VALUE = /var\((--(.+)--(.+)-(.+))\)/; + +function getValueFromAlias(alias) { + const derivedVariable = aliasMap.get(`--${alias}`); + return resolvedMap.get(derivedVariable); // what if we haven't resolved this variable yet? +} + +function resolveDerivedVariable(decl, variables) { + const matches = decl.value.match(RE_VARIABLE_VALUE); + if (matches) { + const [,wholeVariable, baseVariable, operation, argument] = matches; + if (!variables[baseVariable]) { + // hmm.. baseVariable should be in config..., maybe this is an alias? + if (!aliasMap.get(`--${baseVariable}`)) { + throw new Error(`Cannot derive from ${baseVariable} because it is neither defined in config nor is it an alias!`); + } + } + switch (operation) { + case "darker": { + const colorString = variables[baseVariable] ?? getValueFromAlias(baseVariable); + const newColorString = new Color(colorString).darken(argument / 100).hex(); + resolvedMap.set(wholeVariable, newColorString); + break; + } + case "lighter": { + const colorString = variables[baseVariable] ?? getValueFromAlias(baseVariable); + const newColorString = new Color(colorString).lighten(argument / 100).hex(); + resolvedMap.set(wholeVariable, newColorString); + break; + } + } + } +} + +function extractAlias(decl) { + const RE_VARIABLE_PROP = /--(.+)/; + const wholeVariable = decl.value.match(RE_VARIABLE_VALUE)?.[1]; + if (RE_VARIABLE_PROP.test(decl.prop) && wholeVariable) { + aliasMap.set(decl.prop, wholeVariable); + } +} + +/* * + * @type {import('postcss').PluginCreator} + */ +module.exports = (opts = {}) => { + aliasMap = new Map(); + resolvedMap = new Map(); + const { variables } = opts; + return { + postcssPlugin: "postcss-compile-variables", + + Once(root) { + /* + Go through the CSS file once to extract all aliases. + We use the extracted alias when resolving derived variables + later. + */ + root.walkDecls(decl => extractAlias(decl)); + }, + + Declaration(declaration) { + resolveDerivedVariable(declaration, variables); + }, + + OnceExit(root, { Rule, Declaration }) { + const newRule = new Rule({ selector: ":root", source: root.source }) + // Add base css variables to :root + for (const [key, value] of Object.entries(variables)) { + const declaration = new Declaration({ prop: `--${key}`, value }); + newRule.append(declaration); + } + // Add derived css variables to :root + resolvedMap.forEach((value, key) => { + const declaration = new Declaration({ prop: key, value }); + newRule.append(declaration); + }); + root.append(newRule); + }, + }; +}; + +module.exports.postcss = true; diff --git a/yarn.lock b/yarn.lock index 87b8ef96..31dde2dd 100644 --- a/yarn.lock +++ b/yarn.lock @@ -332,11 +332,27 @@ color-name@1.1.3: resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.3.tgz#a7d0558bd89c42f795dd42328f740831ca53bc25" integrity sha1-p9BVi9icQveV3UIyj3QIMcpTvCU= -color-name@~1.1.4: +color-name@^1.0.0, color-name@~1.1.4: version "1.1.4" resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.4.tgz#c2a09a87acbde69543de6f63fa3995c826c536a2" integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA== +color-string@^1.9.0: + version "1.9.0" + resolved "https://registry.yarnpkg.com/color-string/-/color-string-1.9.0.tgz#63b6ebd1bec11999d1df3a79a7569451ac2be8aa" + integrity sha512-9Mrz2AQLefkH1UvASKj6v6hj/7eWgjnT/cVsR8CumieLoT+g900exWeNogqtweI8dxloXN9BDQTYro1oWu/5CQ== + dependencies: + color-name "^1.0.0" + simple-swizzle "^0.2.2" + +color@^4.2.1: + version "4.2.1" + resolved "https://registry.yarnpkg.com/color/-/color-4.2.1.tgz#498aee5fce7fc982606c8875cab080ac0547c884" + integrity sha512-MFJr0uY4RvTQUKvPq7dh9grVOTYSFeXja2mBXioCGjnjJoXrAp9jJ1NQTDR73c9nwBSAQiNKloKl5zq9WB9UPw== + dependencies: + color-convert "^2.0.1" + color-string "^1.9.0" + colors@^1.3.3: version "1.4.0" resolved "https://registry.yarnpkg.com/colors/-/colors-1.4.0.tgz#c50491479d4c1bdaed2c9ced32cf7c7dc2360f78" @@ -961,6 +977,11 @@ inherits@2: resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c" integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== +is-arrayish@^0.3.1: + version "0.3.2" + resolved "https://registry.yarnpkg.com/is-arrayish/-/is-arrayish-0.3.2.tgz#4574a2ae56f7ab206896fb431eaeed066fdf8f03" + integrity sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ== + is-core-module@^2.2.0: version "2.5.0" resolved "https://registry.yarnpkg.com/is-core-module/-/is-core-module-2.5.0.tgz#f754843617c70bfd29b7bd87327400cda5c18491" @@ -1342,6 +1363,13 @@ shebang-regex@^3.0.0: resolved "https://registry.yarnpkg.com/shebang-regex/-/shebang-regex-3.0.0.tgz#ae16f1644d873ecad843b0307b143362d4c42172" integrity sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A== +simple-swizzle@^0.2.2: + version "0.2.2" + resolved "https://registry.yarnpkg.com/simple-swizzle/-/simple-swizzle-0.2.2.tgz#a4da6b635ffcccca33f70d17cb92592de95e557a" + integrity sha1-pNprY1/8zMoz9w0Xy5JZLeleVXo= + dependencies: + is-arrayish "^0.3.1" + slash@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/slash/-/slash-3.0.0.tgz#6539be870c165adbd5240220dbe361f1bc4d4634" From 92084e80056d5869be4f2461ed39d0013fa96452 Mon Sep 17 00:00:00 2001 From: RMidhunSuresh Date: Mon, 7 Mar 2022 11:32:30 +0530 Subject: [PATCH 048/238] Move all code under the Once event Apparently the other events are common to all plugins. --- postcss/css-compile-variables.js | 40 +++++++++++++++----------------- 1 file changed, 19 insertions(+), 21 deletions(-) diff --git a/postcss/css-compile-variables.js b/postcss/css-compile-variables.js index 2395f87d..faf46fe2 100644 --- a/postcss/css-compile-variables.js +++ b/postcss/css-compile-variables.js @@ -1,4 +1,4 @@ -import { Color } from "color"; +const Color = require("color"); let aliasMap; let resolvedMap; @@ -44,6 +44,21 @@ function extractAlias(decl) { } } +function addResolvedVariablesToRootSelector( root, variables, { Rule, Declaration }) { + const newRule = new Rule({ selector: ":root", source: root.source }); + // Add base css variables to :root + for (const [key, value] of Object.entries(variables)) { + const declaration = new Declaration({ prop: `--${key}`, value }); + newRule.append(declaration); + } + // Add derived css variables to :root + resolvedMap.forEach((value, key) => { + const declaration = new Declaration({ prop: key, value }); + newRule.append(declaration); + }); + root.append(newRule); +} + /* * * @type {import('postcss').PluginCreator} */ @@ -54,32 +69,15 @@ module.exports = (opts = {}) => { return { postcssPlugin: "postcss-compile-variables", - Once(root) { + Once(root, {Rule, Declaration}) { /* Go through the CSS file once to extract all aliases. We use the extracted alias when resolving derived variables later. */ root.walkDecls(decl => extractAlias(decl)); - }, - - Declaration(declaration) { - resolveDerivedVariable(declaration, variables); - }, - - OnceExit(root, { Rule, Declaration }) { - const newRule = new Rule({ selector: ":root", source: root.source }) - // Add base css variables to :root - for (const [key, value] of Object.entries(variables)) { - const declaration = new Declaration({ prop: `--${key}`, value }); - newRule.append(declaration); - } - // Add derived css variables to :root - resolvedMap.forEach((value, key) => { - const declaration = new Declaration({ prop: key, value }); - newRule.append(declaration); - }); - root.append(newRule); + root.walkDecls(decl => resolveDerivedVariable(decl, variables)); + addResolvedVariablesToRootSelector(root, variables, { Rule, Declaration }); }, }; }; From b6f5e68e9e1580d2b32bcb0eb74cacbd68ff4277 Mon Sep 17 00:00:00 2001 From: RMidhunSuresh Date: Mon, 7 Mar 2022 11:33:44 +0530 Subject: [PATCH 049/238] Format file --- postcss/css-compile-variables.js | 84 ++++++++++++++++---------------- 1 file changed, 42 insertions(+), 42 deletions(-) diff --git a/postcss/css-compile-variables.js b/postcss/css-compile-variables.js index faf46fe2..125e79ca 100644 --- a/postcss/css-compile-variables.js +++ b/postcss/css-compile-variables.js @@ -5,35 +5,35 @@ let resolvedMap; const RE_VARIABLE_VALUE = /var\((--(.+)--(.+)-(.+))\)/; function getValueFromAlias(alias) { - const derivedVariable = aliasMap.get(`--${alias}`); - return resolvedMap.get(derivedVariable); // what if we haven't resolved this variable yet? + const derivedVariable = aliasMap.get(`--${alias}`); + return resolvedMap.get(derivedVariable); // what if we haven't resolved this variable yet? } function resolveDerivedVariable(decl, variables) { - const matches = decl.value.match(RE_VARIABLE_VALUE); - if (matches) { - const [,wholeVariable, baseVariable, operation, argument] = matches; - if (!variables[baseVariable]) { - // hmm.. baseVariable should be in config..., maybe this is an alias? - if (!aliasMap.get(`--${baseVariable}`)) { - throw new Error(`Cannot derive from ${baseVariable} because it is neither defined in config nor is it an alias!`); - } + const matches = decl.value.match(RE_VARIABLE_VALUE); + if (matches) { + const [, wholeVariable, baseVariable, operation, argument] = matches; + if (!variables[baseVariable]) { + // hmm.. baseVariable should be in config..., maybe this is an alias? + if (!aliasMap.get(`--${baseVariable}`)) { + throw new Error(`Cannot derive from ${baseVariable} because it is neither defined in config nor is it an alias!`); + } + } + switch (operation) { + case "darker": { + const colorString = variables[baseVariable] ?? getValueFromAlias(baseVariable); + const newColorString = new Color(colorString).darken(argument / 100).hex(); + resolvedMap.set(wholeVariable, newColorString); + break; + } + case "lighter": { + const colorString = variables[baseVariable] ?? getValueFromAlias(baseVariable); + const newColorString = new Color(colorString).lighten(argument / 100).hex(); + resolvedMap.set(wholeVariable, newColorString); + break; + } + } } - switch (operation) { - case "darker": { - const colorString = variables[baseVariable] ?? getValueFromAlias(baseVariable); - const newColorString = new Color(colorString).darken(argument / 100).hex(); - resolvedMap.set(wholeVariable, newColorString); - break; - } - case "lighter": { - const colorString = variables[baseVariable] ?? getValueFromAlias(baseVariable); - const newColorString = new Color(colorString).lighten(argument / 100).hex(); - resolvedMap.set(wholeVariable, newColorString); - break; - } - } - } } function extractAlias(decl) { @@ -44,7 +44,7 @@ function extractAlias(decl) { } } -function addResolvedVariablesToRootSelector( root, variables, { Rule, Declaration }) { +function addResolvedVariablesToRootSelector(root, variables, { Rule, Declaration }) { const newRule = new Rule({ selector: ":root", source: root.source }); // Add base css variables to :root for (const [key, value] of Object.entries(variables)) { @@ -63,23 +63,23 @@ function addResolvedVariablesToRootSelector( root, variables, { Rule, Declaratio * @type {import('postcss').PluginCreator} */ module.exports = (opts = {}) => { - aliasMap = new Map(); - resolvedMap = new Map(); - const { variables } = opts; - return { - postcssPlugin: "postcss-compile-variables", + aliasMap = new Map(); + resolvedMap = new Map(); + const { variables } = opts; + return { + postcssPlugin: "postcss-compile-variables", - Once(root, {Rule, Declaration}) { - /* - Go through the CSS file once to extract all aliases. - We use the extracted alias when resolving derived variables - later. - */ - root.walkDecls(decl => extractAlias(decl)); - root.walkDecls(decl => resolveDerivedVariable(decl, variables)); - addResolvedVariablesToRootSelector(root, variables, { Rule, Declaration }); - }, - }; + Once(root, { Rule, Declaration }) { + /* + Go through the CSS file once to extract all aliases. + We use the extracted alias when resolving derived variables + later. + */ + root.walkDecls(decl => extractAlias(decl)); + root.walkDecls(decl => resolveDerivedVariable(decl, variables)); + addResolvedVariablesToRootSelector(root, variables, { Rule, Declaration }); + }, + }; }; module.exports.postcss = true; From e07abfa02a543f1ddc9515d039f3fe56f81b5b88 Mon Sep 17 00:00:00 2001 From: Ajay Bura <32841439+ajbura@users.noreply.github.com> Date: Mon, 7 Mar 2022 11:33:51 +0530 Subject: [PATCH 050/238] Add missing type --- src/domain/ViewModel.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/domain/ViewModel.ts b/src/domain/ViewModel.ts index 04fec86e..8b8581ae 100644 --- a/src/domain/ViewModel.ts +++ b/src/domain/ViewModel.ts @@ -58,9 +58,9 @@ export class ViewModel extends EventEmitter<{change return this._options[name]; } - observeNavigation(type: string, onChange: (value: true | string, type: string) => void) { + observeNavigation(type: string, onChange: (value: string | true | undefined, type: string) => void) { const segmentObservable = this.navigation.observe(type); - const unsubscribe = segmentObservable.subscribe((value: true | string) => { + const unsubscribe = segmentObservable.subscribe((value: string | true | undefined) => { onChange(value, type); }) this.track(unsubscribe); From f170ef0206ec7b9a5797af103a468fc167ba52d5 Mon Sep 17 00:00:00 2001 From: RMidhunSuresh Date: Mon, 7 Mar 2022 11:38:39 +0530 Subject: [PATCH 051/238] Switch over to off-color --- package.json | 4 ++-- postcss/css-compile-variables.js | 6 +++--- yarn.lock | 37 +++++++------------------------- 3 files changed, 13 insertions(+), 34 deletions(-) diff --git a/package.json b/package.json index 3c007244..041100a5 100644 --- a/package.json +++ b/package.json @@ -52,7 +52,7 @@ "@matrix-org/olm": "https://gitlab.matrix.org/api/v4/projects/27/packages/npm/@matrix-org/olm/-/@matrix-org/olm-3.2.3.tgz", "another-json": "^0.2.0", "base64-arraybuffer": "^0.2.0", - "color": "^4.2.1", - "dompurify": "^2.3.0" + "dompurify": "^2.3.0", + "off-color": "^2.0.0" } } diff --git a/postcss/css-compile-variables.js b/postcss/css-compile-variables.js index 125e79ca..e212ee69 100644 --- a/postcss/css-compile-variables.js +++ b/postcss/css-compile-variables.js @@ -1,4 +1,4 @@ -const Color = require("color"); +import { offColor } from 'off-color'; let aliasMap; let resolvedMap; @@ -22,13 +22,13 @@ function resolveDerivedVariable(decl, variables) { switch (operation) { case "darker": { const colorString = variables[baseVariable] ?? getValueFromAlias(baseVariable); - const newColorString = new Color(colorString).darken(argument / 100).hex(); + const newColorString = new offColor(colorString).darken(argument / 100).hex(); resolvedMap.set(wholeVariable, newColorString); break; } case "lighter": { const colorString = variables[baseVariable] ?? getValueFromAlias(baseVariable); - const newColorString = new Color(colorString).lighten(argument / 100).hex(); + const newColorString = new offColor(colorString).lighten(argument / 100).hex(); resolvedMap.set(wholeVariable, newColorString); break; } diff --git a/yarn.lock b/yarn.lock index 31dde2dd..5836b2af 100644 --- a/yarn.lock +++ b/yarn.lock @@ -332,27 +332,11 @@ color-name@1.1.3: resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.3.tgz#a7d0558bd89c42f795dd42328f740831ca53bc25" integrity sha1-p9BVi9icQveV3UIyj3QIMcpTvCU= -color-name@^1.0.0, color-name@~1.1.4: +color-name@~1.1.4: version "1.1.4" resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.4.tgz#c2a09a87acbde69543de6f63fa3995c826c536a2" integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA== -color-string@^1.9.0: - version "1.9.0" - resolved "https://registry.yarnpkg.com/color-string/-/color-string-1.9.0.tgz#63b6ebd1bec11999d1df3a79a7569451ac2be8aa" - integrity sha512-9Mrz2AQLefkH1UvASKj6v6hj/7eWgjnT/cVsR8CumieLoT+g900exWeNogqtweI8dxloXN9BDQTYro1oWu/5CQ== - dependencies: - color-name "^1.0.0" - simple-swizzle "^0.2.2" - -color@^4.2.1: - version "4.2.1" - resolved "https://registry.yarnpkg.com/color/-/color-4.2.1.tgz#498aee5fce7fc982606c8875cab080ac0547c884" - integrity sha512-MFJr0uY4RvTQUKvPq7dh9grVOTYSFeXja2mBXioCGjnjJoXrAp9jJ1NQTDR73c9nwBSAQiNKloKl5zq9WB9UPw== - dependencies: - color-convert "^2.0.1" - color-string "^1.9.0" - colors@^1.3.3: version "1.4.0" resolved "https://registry.yarnpkg.com/colors/-/colors-1.4.0.tgz#c50491479d4c1bdaed2c9ced32cf7c7dc2360f78" @@ -977,11 +961,6 @@ inherits@2: resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c" integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== -is-arrayish@^0.3.1: - version "0.3.2" - resolved "https://registry.yarnpkg.com/is-arrayish/-/is-arrayish-0.3.2.tgz#4574a2ae56f7ab206896fb431eaeed066fdf8f03" - integrity sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ== - is-core-module@^2.2.0: version "2.5.0" resolved "https://registry.yarnpkg.com/is-core-module/-/is-core-module-2.5.0.tgz#f754843617c70bfd29b7bd87327400cda5c18491" @@ -1154,6 +1133,13 @@ nth-check@^2.0.0: dependencies: boolbase "^1.0.0" +off-color@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/off-color/-/off-color-2.0.0.tgz#ecf3bda52e9a78dde535db86361e048741a56631" + integrity sha512-JJ9ObbY2CzgT7F8PpdpHGNjQa7QbU8f4DkY3cCxYUq9NezYUMmL/oSofCc5MMaiUnNNBEFCc4w1unMA+R8syvw== + dependencies: + core-js "^3.6.5" + once@^1.3.0: version "1.4.0" resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1" @@ -1363,13 +1349,6 @@ shebang-regex@^3.0.0: resolved "https://registry.yarnpkg.com/shebang-regex/-/shebang-regex-3.0.0.tgz#ae16f1644d873ecad843b0307b143362d4c42172" integrity sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A== -simple-swizzle@^0.2.2: - version "0.2.2" - resolved "https://registry.yarnpkg.com/simple-swizzle/-/simple-swizzle-0.2.2.tgz#a4da6b635ffcccca33f70d17cb92592de95e557a" - integrity sha1-pNprY1/8zMoz9w0Xy5JZLeleVXo= - dependencies: - is-arrayish "^0.3.1" - slash@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/slash/-/slash-3.0.0.tgz#6539be870c165adbd5240220dbe361f1bc4d4634" From a5d46bb40cd7d0ba2451476e1d9277e779da681c Mon Sep 17 00:00:00 2001 From: RMidhunSuresh Date: Mon, 7 Mar 2022 13:10:44 +0530 Subject: [PATCH 052/238] Move over tests to Hydrogen using impunity --- package.json | 1 + postcss/css-compile-variables.js | 6 +-- postcss/test.js | 66 ++++++++++++++++++++++++++++++++ 3 files changed, 70 insertions(+), 3 deletions(-) create mode 100644 postcss/test.js diff --git a/package.json b/package.json index 041100a5..5ca5b05f 100644 --- a/package.json +++ b/package.json @@ -10,6 +10,7 @@ "lint-ts": "eslint src/ -c .ts-eslintrc.js --ext .ts", "lint-ci": "eslint src/", "test": "impunity --entry-point src/platform/web/main.js src/platform/web/Platform.js --force-esm-dirs lib/ src/ --root-dir src/", + "test:postcss": "impunity --entry-point postcss/test.js ", "start": "vite --port 3000", "build": "vite build", "build:sdk": "./scripts/sdk/build.sh" diff --git a/postcss/css-compile-variables.js b/postcss/css-compile-variables.js index e212ee69..8737067d 100644 --- a/postcss/css-compile-variables.js +++ b/postcss/css-compile-variables.js @@ -1,4 +1,4 @@ -import { offColor } from 'off-color'; +const offColor = require("off-color").offColor; let aliasMap; let resolvedMap; @@ -22,13 +22,13 @@ function resolveDerivedVariable(decl, variables) { switch (operation) { case "darker": { const colorString = variables[baseVariable] ?? getValueFromAlias(baseVariable); - const newColorString = new offColor(colorString).darken(argument / 100).hex(); + const newColorString = offColor(colorString).darken(argument / 100).hex(); resolvedMap.set(wholeVariable, newColorString); break; } case "lighter": { const colorString = variables[baseVariable] ?? getValueFromAlias(baseVariable); - const newColorString = new offColor(colorString).lighten(argument / 100).hex(); + const newColorString = offColor(colorString).lighten(argument / 100).hex(); resolvedMap.set(wholeVariable, newColorString); break; } diff --git a/postcss/test.js b/postcss/test.js new file mode 100644 index 00000000..104696aa --- /dev/null +++ b/postcss/test.js @@ -0,0 +1,66 @@ +const offColor = require("off-color").offColor; +const postcss = require("postcss"); +const plugin = require("./css-compile-variables"); + +async function run(input, output, opts = {}, assert) { + let result = await postcss([plugin(opts)]).process(input, { from: undefined, }); + assert.strictEqual( + result.css.replaceAll(/\s/g, ""), + output.replaceAll(/\s/g, "") + ); + assert.strictEqual(result.warnings().length, 0); +} + +module.exports.tests = function tests() { + return { + "derived variables are resolved": async (assert) => { + const inputCSS = `div { + background-color: var(--foo-color--lighter-50); + }`; + const transformedColor = offColor("#ff0").lighten(0.5); + const outputCSS = + inputCSS + + ` + :root { + --foo-color: #ff0; + --foo-color--lighter-50: ${transformedColor.hex()}; + } + `; + await run( + inputCSS, + outputCSS, + { variables: { "foo-color": "#ff0" } }, + assert + ); + }, + + "derived variables work with alias": async (assert) => { + const inputCSS = `div { + background: var(--icon-color--darker-20); + --my-alias: var(--icon-color--darker-20); + color: var(--my-alias--lighter-15); + }`; + const colorDarker = offColor("#fff").darken(0.2).hex(); + const aliasLighter = offColor(colorDarker).lighten(0.15).hex(); + const outputCSS = `div { + background: var(--icon-color--darker-20); + --my-alias: var(--icon-color--darker-20); + color: var(--my-alias--lighter-15); + } + :root { + --icon-color: #fff; + --icon-color--darker-20: ${colorDarker}; + --my-alias--lighter-15: ${aliasLighter}; + } + `; + await run(inputCSS, outputCSS, { variables: { "icon-color": "#fff" }, }, assert); + }, + + "derived variable throws if base not present in config": async (assert) => { + const css = `:root { + color: var(--icon-color--darker-20); + }`; + assert.rejects(async () => await postcss([plugin({ variables: {} })]).process(css, { from: undefined, })); + } + }; +}; From 41f6b6ab6bd52a55f024cb31d8f78c389e7f02df Mon Sep 17 00:00:00 2001 From: RMidhunSuresh Date: Mon, 7 Mar 2022 13:25:53 +0530 Subject: [PATCH 053/238] Use startsWith instead of regex testing --- postcss/css-compile-variables.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/postcss/css-compile-variables.js b/postcss/css-compile-variables.js index 8737067d..8d4b0fd9 100644 --- a/postcss/css-compile-variables.js +++ b/postcss/css-compile-variables.js @@ -37,9 +37,8 @@ function resolveDerivedVariable(decl, variables) { } function extractAlias(decl) { - const RE_VARIABLE_PROP = /--(.+)/; const wholeVariable = decl.value.match(RE_VARIABLE_VALUE)?.[1]; - if (RE_VARIABLE_PROP.test(decl.prop) && wholeVariable) { + if (decl.prop.startsWith("--") && wholeVariable) { aliasMap.set(decl.prop, wholeVariable); } } From a83850ebf38f3de28bb182470fd009b1fabade9c Mon Sep 17 00:00:00 2001 From: RMidhunSuresh Date: Wed, 9 Mar 2022 11:48:53 +0530 Subject: [PATCH 054/238] Use postcss value parser to find variables --- package.json | 3 +- postcss/css-compile-variables.js | 58 +++++++++++++++++++++----------- postcss/test.js | 23 +++++++++++++ yarn.lock | 5 +++ 4 files changed, 68 insertions(+), 21 deletions(-) diff --git a/package.json b/package.json index 5ca5b05f..fa9c4344 100644 --- a/package.json +++ b/package.json @@ -54,6 +54,7 @@ "another-json": "^0.2.0", "base64-arraybuffer": "^0.2.0", "dompurify": "^2.3.0", - "off-color": "^2.0.0" + "off-color": "^2.0.0", + "postcss-value-parser": "^4.2.0" } } diff --git a/postcss/css-compile-variables.js b/postcss/css-compile-variables.js index 8d4b0fd9..bd952ac8 100644 --- a/postcss/css-compile-variables.js +++ b/postcss/css-compile-variables.js @@ -1,4 +1,5 @@ const offColor = require("off-color").offColor; +const valueParser = require("postcss-value-parser"); let aliasMap; let resolvedMap; @@ -9,28 +10,45 @@ function getValueFromAlias(alias) { return resolvedMap.get(derivedVariable); // what if we haven't resolved this variable yet? } -function resolveDerivedVariable(decl, variables) { - const matches = decl.value.match(RE_VARIABLE_VALUE); - if (matches) { - const [, wholeVariable, baseVariable, operation, argument] = matches; - if (!variables[baseVariable]) { - // hmm.. baseVariable should be in config..., maybe this is an alias? - if (!aliasMap.get(`--${baseVariable}`)) { - throw new Error(`Cannot derive from ${baseVariable} because it is neither defined in config nor is it an alias!`); - } +function parseDeclarationValue(value) { + const parsed = valueParser(value); + const variables = []; + parsed.walk(node => { + if (node.type !== "function" && node.value !== "var") { + return; } - switch (operation) { - case "darker": { - const colorString = variables[baseVariable] ?? getValueFromAlias(baseVariable); - const newColorString = offColor(colorString).darken(argument / 100).hex(); - resolvedMap.set(wholeVariable, newColorString); - break; + const variable = node.nodes[0]; + variables.push(variable.value); + }); + return variables; +} + +function resolveDerivedVariable(decl, variables) { + const RE_VARIABLE_VALUE = /--(.+)--(.+)-(.+)/; + const variableCollection = parseDeclarationValue(decl.value); + for (const variable of variableCollection) { + const matches = variable.match(RE_VARIABLE_VALUE); + if (matches) { + const [wholeVariable, baseVariable, operation, argument] = matches; + if (!variables[baseVariable]) { + // hmm.. baseVariable should be in config..., maybe this is an alias? + if (!aliasMap.get(`--${baseVariable}`)) { + throw new Error(`Cannot derive from ${baseVariable} because it is neither defined in config nor is it an alias!`); + } } - case "lighter": { - const colorString = variables[baseVariable] ?? getValueFromAlias(baseVariable); - const newColorString = offColor(colorString).lighten(argument / 100).hex(); - resolvedMap.set(wholeVariable, newColorString); - break; + switch (operation) { + case "darker": { + const colorString = variables[baseVariable] ?? getValueFromAlias(baseVariable); + const newColorString = offColor(colorString).darken(argument / 100).hex(); + resolvedMap.set(wholeVariable, newColorString); + break; + } + case "lighter": { + const colorString = variables[baseVariable] ?? getValueFromAlias(baseVariable); + const newColorString = offColor(colorString).lighten(argument / 100).hex(); + resolvedMap.set(wholeVariable, newColorString); + break; + } } } } diff --git a/postcss/test.js b/postcss/test.js index 104696aa..e67016b6 100644 --- a/postcss/test.js +++ b/postcss/test.js @@ -61,6 +61,29 @@ module.exports.tests = function tests() { color: var(--icon-color--darker-20); }`; assert.rejects(async () => await postcss([plugin({ variables: {} })]).process(css, { from: undefined, })); + }, + + "multiple derived variable in single declaration is parsed correctly": async (assert) => { + const inputCSS = `div { + background-color: linear-gradient(var(--foo-color--lighter-50), var(--foo-color--darker-20)); + }`; + const transformedColor1 = offColor("#ff0").lighten(0.5); + const transformedColor2 = offColor("#ff0").darken(0.2); + const outputCSS = + inputCSS + + ` + :root { + --foo-color: #ff0; + --foo-color--lighter-50: ${transformedColor1.hex()}; + --foo-color--darker-20: ${transformedColor2.hex()}; + } + `; + await run( + inputCSS, + outputCSS, + { variables: { "foo-color": "#ff0" } }, + assert + ); } }; }; diff --git a/yarn.lock b/yarn.lock index 5836b2af..7bcefdd4 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1222,6 +1222,11 @@ postcss-flexbugs-fixes@^5.0.2: resolved "https://registry.yarnpkg.com/postcss-flexbugs-fixes/-/postcss-flexbugs-fixes-5.0.2.tgz#2028e145313074fc9abe276cb7ca14e5401eb49d" integrity sha512-18f9voByak7bTktR2QgDveglpn9DTbBWPUzSOe9g0N4WR/2eSt6Vrcbf0hmspvMI6YWGywz6B9f7jzpFNJJgnQ== +postcss-value-parser@^4.2.0: + version "4.2.0" + resolved "https://registry.yarnpkg.com/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz#723c09920836ba6d3e5af019f92bc0971c02e514" + integrity sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ== + postcss@^8.3.8: version "8.3.9" resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.3.9.tgz#98754caa06c4ee9eb59cc48bd073bb6bd3437c31" From 6d7c983e8e8f7d33f1a704fe82b5a30972431cf8 Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Wed, 9 Mar 2022 11:28:19 +0100 Subject: [PATCH 055/238] convert (Base)ObservableMap to typescript --- .../room/timeline/ReactionsViewModel.js | 2 +- src/matrix/room/members/MemberList.js | 2 +- src/observable/index.js | 4 +-- src/observable/list/SortedMapList.js | 2 +- src/observable/map/ApplyMap.js | 2 +- ...eObservableMap.js => BaseObservableMap.ts} | 30 +++++++++---------- src/observable/map/FilteredMap.js | 4 +-- src/observable/map/JoinedMap.js | 4 +-- src/observable/map/LogMap.js | 2 +- src/observable/map/MappedMap.js | 2 +- .../{ObservableMap.js => ObservableMap.ts} | 30 ++++++++++--------- 11 files changed, 42 insertions(+), 42 deletions(-) rename src/observable/map/{BaseObservableMap.js => BaseObservableMap.ts} (69%) rename src/observable/map/{ObservableMap.js => ObservableMap.ts} (90%) diff --git a/src/domain/session/room/timeline/ReactionsViewModel.js b/src/domain/session/room/timeline/ReactionsViewModel.js index fa48bec0..4f366af0 100644 --- a/src/domain/session/room/timeline/ReactionsViewModel.js +++ b/src/domain/session/room/timeline/ReactionsViewModel.js @@ -13,7 +13,7 @@ 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 {ObservableMap} from "../../../../observable/map/ObservableMap.js"; +import {ObservableMap} from "../../../../observable/map/ObservableMap"; export class ReactionsViewModel { constructor(parentTile) { diff --git a/src/matrix/room/members/MemberList.js b/src/matrix/room/members/MemberList.js index 9923fb87..f32a63d3 100644 --- a/src/matrix/room/members/MemberList.js +++ b/src/matrix/room/members/MemberList.js @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -import {ObservableMap} from "../../../observable/map/ObservableMap.js"; +import {ObservableMap} from "../../../observable/map/ObservableMap"; import {RetainedValue} from "../../../utils/RetainedValue"; export class MemberList extends RetainedValue { diff --git a/src/observable/index.js b/src/observable/index.js index 4d7f18a3..6057174b 100644 --- a/src/observable/index.js +++ b/src/observable/index.js @@ -18,14 +18,14 @@ import {SortedMapList} from "./list/SortedMapList.js"; import {FilteredMap} from "./map/FilteredMap.js"; import {MappedMap} from "./map/MappedMap.js"; import {JoinedMap} from "./map/JoinedMap.js"; -import {BaseObservableMap} from "./map/BaseObservableMap.js"; +import {BaseObservableMap} from "./map/BaseObservableMap"; // re-export "root" (of chain) collections export { ObservableArray } from "./list/ObservableArray"; export { SortedArray } from "./list/SortedArray"; export { MappedList } from "./list/MappedList"; export { AsyncMappedList } from "./list/AsyncMappedList"; export { ConcatList } from "./list/ConcatList"; -export { ObservableMap } from "./map/ObservableMap.js"; +export { ObservableMap } from "./map/ObservableMap"; // avoid circular dependency between these classes // and BaseObservableMap (as they extend it) diff --git a/src/observable/list/SortedMapList.js b/src/observable/list/SortedMapList.js index 38900380..d74dbade 100644 --- a/src/observable/list/SortedMapList.js +++ b/src/observable/list/SortedMapList.js @@ -133,7 +133,7 @@ export class SortedMapList extends BaseObservableList { } } -import {ObservableMap} from "../map/ObservableMap.js"; +import {ObservableMap} from "../map/ObservableMap"; export function tests() { return { diff --git a/src/observable/map/ApplyMap.js b/src/observable/map/ApplyMap.js index ad345595..6be7278a 100644 --- a/src/observable/map/ApplyMap.js +++ b/src/observable/map/ApplyMap.js @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -import {BaseObservableMap} from "./BaseObservableMap.js"; +import {BaseObservableMap} from "./BaseObservableMap"; export class ApplyMap extends BaseObservableMap { constructor(source, apply) { diff --git a/src/observable/map/BaseObservableMap.js b/src/observable/map/BaseObservableMap.ts similarity index 69% rename from src/observable/map/BaseObservableMap.js rename to src/observable/map/BaseObservableMap.ts index d3193931..694c017e 100644 --- a/src/observable/map/BaseObservableMap.js +++ b/src/observable/map/BaseObservableMap.ts @@ -16,7 +16,14 @@ limitations under the License. import {BaseObservable} from "../BaseObservable"; -export class BaseObservableMap extends BaseObservable { +export interface IMapObserver { + onReset(): void; + onAdd(key: K, value:V): void; + onUpdate(key: K, value: V, params: any): void; + onRemove(key: K, value: V): void +} + +export abstract class BaseObservableMap extends BaseObservable> { emitReset() { for(let h of this._handlers) { h.onReset(); @@ -24,15 +31,15 @@ export class BaseObservableMap extends BaseObservable { } // we need batch events, mostly on index based collection though? // maybe we should get started without? - emitAdd(key, value) { + emitAdd(key: K, value: V) { for(let h of this._handlers) { h.onAdd(key, value); } } - emitUpdate(key, value, ...params) { + emitUpdate(key, value, params) { for(let h of this._handlers) { - h.onUpdate(key, value, ...params); + h.onUpdate(key, value, params); } } @@ -42,16 +49,7 @@ export class BaseObservableMap extends BaseObservable { } } - [Symbol.iterator]() { - throw new Error("unimplemented"); - } - - get size() { - throw new Error("unimplemented"); - } - - // eslint-disable-next-line no-unused-vars - get(key) { - throw new Error("unimplemented"); - } + abstract [Symbol.iterator](): Iterator<[K, V]>; + abstract get size(): number; + abstract get(key: K): V | undefined; } diff --git a/src/observable/map/FilteredMap.js b/src/observable/map/FilteredMap.js index f7090502..d7e11fbe 100644 --- a/src/observable/map/FilteredMap.js +++ b/src/observable/map/FilteredMap.js @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -import {BaseObservableMap} from "./BaseObservableMap.js"; +import {BaseObservableMap} from "./BaseObservableMap"; export class FilteredMap extends BaseObservableMap { constructor(source, filter) { @@ -166,7 +166,7 @@ class FilterIterator { } } -import {ObservableMap} from "./ObservableMap.js"; +import {ObservableMap} from "./ObservableMap"; export function tests() { return { "filter preloaded list": assert => { diff --git a/src/observable/map/JoinedMap.js b/src/observable/map/JoinedMap.js index 7db04be1..d97c5677 100644 --- a/src/observable/map/JoinedMap.js +++ b/src/observable/map/JoinedMap.js @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -import {BaseObservableMap} from "./BaseObservableMap.js"; +import {BaseObservableMap} from "./BaseObservableMap"; export class JoinedMap extends BaseObservableMap { constructor(sources) { @@ -191,7 +191,7 @@ class SourceSubscriptionHandler { } -import { ObservableMap } from "./ObservableMap.js"; +import { ObservableMap } from "./ObservableMap"; export function tests() { diff --git a/src/observable/map/LogMap.js b/src/observable/map/LogMap.js index 4b8bb686..1beb4846 100644 --- a/src/observable/map/LogMap.js +++ b/src/observable/map/LogMap.js @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -import {BaseObservableMap} from "./BaseObservableMap.js"; +import {BaseObservableMap} from "./BaseObservableMap"; export class LogMap extends BaseObservableMap { constructor(source, log) { diff --git a/src/observable/map/MappedMap.js b/src/observable/map/MappedMap.js index 2a810058..a6b65c41 100644 --- a/src/observable/map/MappedMap.js +++ b/src/observable/map/MappedMap.js @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -import {BaseObservableMap} from "./BaseObservableMap.js"; +import {BaseObservableMap} from "./BaseObservableMap"; /* so a mapped value can emit updates on it's own with this._emitSpontaneousUpdate that is passed in the mapping function how should the mapped value be notified of an update though? and can it then decide to not propagate the update? diff --git a/src/observable/map/ObservableMap.js b/src/observable/map/ObservableMap.ts similarity index 90% rename from src/observable/map/ObservableMap.js rename to src/observable/map/ObservableMap.ts index 8f5a0922..0f681879 100644 --- a/src/observable/map/ObservableMap.js +++ b/src/observable/map/ObservableMap.ts @@ -14,15 +14,17 @@ See the License for the specific language governing permissions and limitations under the License. */ -import {BaseObservableMap} from "./BaseObservableMap.js"; +import {BaseObservableMap} from "./BaseObservableMap"; -export class ObservableMap extends BaseObservableMap { - constructor(initialValues) { +export class ObservableMap extends BaseObservableMap { + private readonly _values: Map; + + constructor(initialValues: Iterable<[K, V]>) { super(); this._values = new Map(initialValues); } - update(key, params) { + update(key: K, params: any): boolean { const value = this._values.get(key); if (value !== undefined) { // could be the same value, so it's already updated @@ -34,7 +36,7 @@ export class ObservableMap extends BaseObservableMap { return false; // or return existing value? } - add(key, value) { + add(key: K, value: V): boolean { if (!this._values.has(key)) { this._values.set(key, value); this.emitAdd(key, value); @@ -43,7 +45,7 @@ export class ObservableMap extends BaseObservableMap { return false; // or return existing value? } - remove(key) { + remove(key: K): boolean { const value = this._values.get(key); if (value !== undefined) { this._values.delete(key); @@ -54,39 +56,39 @@ export class ObservableMap extends BaseObservableMap { } } - set(key, value) { + set(key: K, value: V): boolean { if (this._values.has(key)) { // We set the value here because update only supports inline updates this._values.set(key, value); - return this.update(key); + return this.update(key, undefined); } else { return this.add(key, value); } } - reset() { + reset(): void { this._values.clear(); this.emitReset(); } - get(key) { + get(key: K): V | undefined { return this._values.get(key); } - get size() { + get size(): number { return this._values.size; } - [Symbol.iterator]() { + [Symbol.iterator](): Iterator<[K, V]> { return this._values.entries(); } - values() { + values(): Iterator { return this._values.values(); } - keys() { + keys(): Iterator { return this._values.keys(); } } From 21080d2110849531087500328b558b02b5f941b1 Mon Sep 17 00:00:00 2001 From: Bruno Windels <274386+bwindels@users.noreply.github.com> Date: Wed, 9 Mar 2022 11:41:26 +0100 Subject: [PATCH 056/238] fix tests --- src/observable/map/ObservableMap.ts | 44 +++++++++++++++++++---------- 1 file changed, 29 insertions(+), 15 deletions(-) diff --git a/src/observable/map/ObservableMap.ts b/src/observable/map/ObservableMap.ts index 0f681879..719ce4b7 100644 --- a/src/observable/map/ObservableMap.ts +++ b/src/observable/map/ObservableMap.ts @@ -19,12 +19,12 @@ import {BaseObservableMap} from "./BaseObservableMap"; export class ObservableMap extends BaseObservableMap { private readonly _values: Map; - constructor(initialValues: Iterable<[K, V]>) { + constructor(initialValues?: Iterable<[K, V]>) { super(); this._values = new Map(initialValues); } - update(key: K, params: any): boolean { + update(key: K, params?: any): boolean { const value = this._values.get(key); if (value !== undefined) { // could be the same value, so it's already updated @@ -107,13 +107,16 @@ export function tests() { test_add(assert) { let fired = 0; - const map = new ObservableMap(); + const map = new ObservableMap(); map.subscribe({ onAdd(key, value) { fired += 1; assert.equal(key, 1); assert.deepEqual(value, {value: 5}); - } + }, + onUpdate() {}, + onRemove() {}, + onReset() {} }); map.add(1, {value: 5}); assert.equal(map.size, 1); @@ -122,7 +125,7 @@ export function tests() { test_update(assert) { let fired = 0; - const map = new ObservableMap(); + const map = new ObservableMap(); const value = {number: 5}; map.add(1, value); map.subscribe({ @@ -131,7 +134,10 @@ export function tests() { assert.equal(key, 1); assert.deepEqual(value, {number: 6}); assert.equal(params, "test"); - } + }, + onAdd() {}, + onRemove() {}, + onReset() {} }); value.number = 6; map.update(1, "test"); @@ -140,9 +146,12 @@ export function tests() { test_update_unknown(assert) { let fired = 0; - const map = new ObservableMap(); + const map = new ObservableMap(); map.subscribe({ - onUpdate() { fired += 1; } + onUpdate() { fired += 1; }, + onAdd() {}, + onRemove() {}, + onReset() {} }); const result = map.update(1); assert.equal(fired, 0); @@ -151,7 +160,7 @@ export function tests() { test_set(assert) { let add_fired = 0, update_fired = 0; - const map = new ObservableMap(); + const map = new ObservableMap(); map.subscribe({ onAdd(key, value) { add_fired += 1; @@ -162,7 +171,9 @@ export function tests() { update_fired += 1; assert.equal(key, 1); assert.deepEqual(value, {value: 7}); - } + }, + onRemove() {}, + onReset() {} }); // Add map.set(1, {value: 5}); @@ -176,7 +187,7 @@ export function tests() { test_remove(assert) { let fired = 0; - const map = new ObservableMap(); + const map = new ObservableMap(); const value = {value: 5}; map.add(1, value); map.subscribe({ @@ -184,7 +195,10 @@ export function tests() { fired += 1; assert.equal(key, 1); assert.deepEqual(value, {value: 5}); - } + }, + onAdd() {}, + onUpdate() {}, + onReset() {} }); map.remove(1); assert.equal(map.size, 0); @@ -192,8 +206,8 @@ export function tests() { }, test_iterate(assert) { - const results = []; - const map = new ObservableMap(); + const results: any[] = []; + const map = new ObservableMap(); map.add(1, {number: 5}); map.add(2, {number: 6}); map.add(3, {number: 7}); @@ -206,7 +220,7 @@ export function tests() { assert.equal(results.find(([key]) => key === 3)[1].number, 7); }, test_size(assert) { - const map = new ObservableMap(); + const map = new ObservableMap(); map.add(1, {number: 5}); map.add(2, {number: 6}); assert.equal(map.size, 2); From 762925d4a591b58235fa3846d3022daa75071898 Mon Sep 17 00:00:00 2001 From: Bruno Windels <274386+bwindels@users.noreply.github.com> Date: Wed, 9 Mar 2022 11:44:49 +0100 Subject: [PATCH 057/238] fix type error --- src/observable/map/ObservableMap.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/observable/map/ObservableMap.ts b/src/observable/map/ObservableMap.ts index 719ce4b7..8ada1843 100644 --- a/src/observable/map/ObservableMap.ts +++ b/src/observable/map/ObservableMap.ts @@ -19,7 +19,7 @@ import {BaseObservableMap} from "./BaseObservableMap"; export class ObservableMap extends BaseObservableMap { private readonly _values: Map; - constructor(initialValues?: Iterable<[K, V]>) { + constructor(initialValues?: Iterable) { super(); this._values = new Map(initialValues); } From 6150e91c3f82ab7c772dd2eb9841fddddae32bd0 Mon Sep 17 00:00:00 2001 From: Bruno Windels <274386+bwindels@users.noreply.github.com> Date: Wed, 9 Mar 2022 11:51:11 +0100 Subject: [PATCH 058/238] fix type error again --- src/observable/map/ObservableMap.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/observable/map/ObservableMap.ts b/src/observable/map/ObservableMap.ts index 8ada1843..d604ab0a 100644 --- a/src/observable/map/ObservableMap.ts +++ b/src/observable/map/ObservableMap.ts @@ -19,7 +19,7 @@ import {BaseObservableMap} from "./BaseObservableMap"; export class ObservableMap extends BaseObservableMap { private readonly _values: Map; - constructor(initialValues?: Iterable) { + constructor(initialValues?: (readonly [K, V])[]) { super(); this._values = new Map(initialValues); } From 79f363fb9d9e4406daf65244419849cf422df1c4 Mon Sep 17 00:00:00 2001 From: RMidhunSuresh Date: Wed, 9 Mar 2022 17:20:05 +0530 Subject: [PATCH 059/238] Move code to callback and fix alias code --- postcss/color.js | 14 ++++++++++++++ postcss/css-compile-variables.js | 31 +++++++++---------------------- postcss/test.js | 28 +++++++++++++++++++++------- 3 files changed, 44 insertions(+), 29 deletions(-) create mode 100644 postcss/color.js diff --git a/postcss/color.js b/postcss/color.js new file mode 100644 index 00000000..cad91019 --- /dev/null +++ b/postcss/color.js @@ -0,0 +1,14 @@ +const offColor = require("off-color").offColor; + +module.exports.derive = function (value, operation, argument) { + switch (operation) { + case "darker": { + const newColorString = offColor(value).darken(argument / 100).hex(); + return newColorString; + } + case "lighter": { + const newColorString = offColor(value).lighten(argument / 100).hex(); + return newColorString; + } + } +} diff --git a/postcss/css-compile-variables.js b/postcss/css-compile-variables.js index bd952ac8..bc91dc06 100644 --- a/postcss/css-compile-variables.js +++ b/postcss/css-compile-variables.js @@ -1,13 +1,11 @@ -const offColor = require("off-color").offColor; const valueParser = require("postcss-value-parser"); let aliasMap; let resolvedMap; -const RE_VARIABLE_VALUE = /var\((--(.+)--(.+)-(.+))\)/; -function getValueFromAlias(alias) { +function getValueFromAlias(alias, variables) { const derivedVariable = aliasMap.get(`--${alias}`); - return resolvedMap.get(derivedVariable); // what if we haven't resolved this variable yet? + return variables[derivedVariable] ?? resolvedMap.get(`--${derivedVariable}`); // what if we haven't resolved this variable yet? } function parseDeclarationValue(value) { @@ -23,7 +21,7 @@ function parseDeclarationValue(value) { return variables; } -function resolveDerivedVariable(decl, variables) { +function resolveDerivedVariable(decl, {variables, derive}) { const RE_VARIABLE_VALUE = /--(.+)--(.+)-(.+)/; const variableCollection = parseDeclarationValue(decl.value); for (const variable of variableCollection) { @@ -36,26 +34,15 @@ function resolveDerivedVariable(decl, variables) { throw new Error(`Cannot derive from ${baseVariable} because it is neither defined in config nor is it an alias!`); } } - switch (operation) { - case "darker": { - const colorString = variables[baseVariable] ?? getValueFromAlias(baseVariable); - const newColorString = offColor(colorString).darken(argument / 100).hex(); - resolvedMap.set(wholeVariable, newColorString); - break; - } - case "lighter": { - const colorString = variables[baseVariable] ?? getValueFromAlias(baseVariable); - const newColorString = offColor(colorString).lighten(argument / 100).hex(); - resolvedMap.set(wholeVariable, newColorString); - break; - } - } + const value = variables[baseVariable] ?? getValueFromAlias(baseVariable, variables); + const derivedValue = derive(value, operation, argument); + resolvedMap.set(wholeVariable, derivedValue); } } } function extractAlias(decl) { - const wholeVariable = decl.value.match(RE_VARIABLE_VALUE)?.[1]; + const wholeVariable = decl.value.match(/var\(--(.+)\)/)?.[1]; if (decl.prop.startsWith("--") && wholeVariable) { aliasMap.set(decl.prop, wholeVariable); } @@ -82,7 +69,7 @@ function addResolvedVariablesToRootSelector(root, variables, { Rule, Declaration module.exports = (opts = {}) => { aliasMap = new Map(); resolvedMap = new Map(); - const { variables } = opts; + const {variables} = opts; return { postcssPlugin: "postcss-compile-variables", @@ -93,7 +80,7 @@ module.exports = (opts = {}) => { later. */ root.walkDecls(decl => extractAlias(decl)); - root.walkDecls(decl => resolveDerivedVariable(decl, variables)); + root.walkDecls(decl => resolveDerivedVariable(decl, opts)); addResolvedVariablesToRootSelector(root, variables, { Rule, Declaration }); }, }; diff --git a/postcss/test.js b/postcss/test.js index e67016b6..d07a6689 100644 --- a/postcss/test.js +++ b/postcss/test.js @@ -1,9 +1,10 @@ const offColor = require("off-color").offColor; const postcss = require("postcss"); const plugin = require("./css-compile-variables"); +const derive = require("./color").derive; async function run(input, output, opts = {}, assert) { - let result = await postcss([plugin(opts)]).process(input, { from: undefined, }); + let result = await postcss([plugin({ ...opts, derive })]).process(input, { from: undefined, }); assert.strictEqual( result.css.replaceAll(/\s/g, ""), output.replaceAll(/\s/g, "") @@ -78,12 +79,25 @@ module.exports.tests = function tests() { --foo-color--darker-20: ${transformedColor2.hex()}; } `; - await run( - inputCSS, - outputCSS, - { variables: { "foo-color": "#ff0" } }, - assert - ); + await run( inputCSS, outputCSS, { variables: { "foo-color": "#ff0" } }, assert); + }, + "multiple aliased-derived variable in single declaration is parsed correctly": async (assert) => { + const inputCSS = `div { + --my-alias: var(--foo-color); + background-color: linear-gradient(var(--my-alias--lighter-50), var(--my-alias--darker-20)); + }`; + const transformedColor1 = offColor("#ff0").lighten(0.5); + const transformedColor2 = offColor("#ff0").darken(0.2); + const outputCSS = + inputCSS + + ` + :root { + --foo-color: #ff0; + --my-alias--lighter-50: ${transformedColor1.hex()}; + --my-alias--darker-20: ${transformedColor2.hex()}; + } + `; + await run( inputCSS, outputCSS, { variables: { "foo-color": "#ff0" } }, assert); } }; }; From 96fa83b5084ac0fc76d3dad2e67ee32cc3b224a8 Mon Sep 17 00:00:00 2001 From: RMidhunSuresh Date: Wed, 9 Mar 2022 17:22:11 +0530 Subject: [PATCH 060/238] Add license header --- postcss/color.js | 16 ++++++++++++++++ postcss/css-compile-variables.js | 16 ++++++++++++++++ postcss/test.js | 16 ++++++++++++++++ 3 files changed, 48 insertions(+) diff --git a/postcss/color.js b/postcss/color.js index cad91019..2251a56e 100644 --- a/postcss/color.js +++ b/postcss/color.js @@ -1,3 +1,19 @@ +/* +Copyright 2021 The Matrix.org Foundation C.I.C. + +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. +*/ + const offColor = require("off-color").offColor; module.exports.derive = function (value, operation, argument) { diff --git a/postcss/css-compile-variables.js b/postcss/css-compile-variables.js index bc91dc06..195526ef 100644 --- a/postcss/css-compile-variables.js +++ b/postcss/css-compile-variables.js @@ -1,3 +1,19 @@ +/* +Copyright 2021 The Matrix.org Foundation C.I.C. + +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. +*/ + const valueParser = require("postcss-value-parser"); let aliasMap; diff --git a/postcss/test.js b/postcss/test.js index d07a6689..4ed74c67 100644 --- a/postcss/test.js +++ b/postcss/test.js @@ -1,3 +1,19 @@ +/* +Copyright 2021 The Matrix.org Foundation C.I.C. + +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. +*/ + const offColor = require("off-color").offColor; const postcss = require("postcss"); const plugin = require("./css-compile-variables"); From 63c1f2a7a39ee9e1f3d3633d47aee49743f801ba Mon Sep 17 00:00:00 2001 From: RMidhunSuresh Date: Wed, 9 Mar 2022 17:22:45 +0530 Subject: [PATCH 061/238] Add node as env to eslint --- .eslintrc.js | 1 + 1 file changed, 1 insertion(+) diff --git a/.eslintrc.js b/.eslintrc.js index cb28f4c8..1985ccc1 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -1,6 +1,7 @@ module.exports = { "env": { "browser": true, + "node": true, "es6": true }, "extends": "eslint:recommended", From 1663782954ef1de0e0bce0ba9d759f733bd95f2e Mon Sep 17 00:00:00 2001 From: RMidhunSuresh Date: Thu, 10 Mar 2022 16:05:13 +0530 Subject: [PATCH 062/238] Throw after fetching value --- postcss/css-compile-variables.js | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/postcss/css-compile-variables.js b/postcss/css-compile-variables.js index 195526ef..732ed8b3 100644 --- a/postcss/css-compile-variables.js +++ b/postcss/css-compile-variables.js @@ -44,13 +44,10 @@ function resolveDerivedVariable(decl, {variables, derive}) { const matches = variable.match(RE_VARIABLE_VALUE); if (matches) { const [wholeVariable, baseVariable, operation, argument] = matches; - if (!variables[baseVariable]) { - // hmm.. baseVariable should be in config..., maybe this is an alias? - if (!aliasMap.get(`--${baseVariable}`)) { - throw new Error(`Cannot derive from ${baseVariable} because it is neither defined in config nor is it an alias!`); - } - } const value = variables[baseVariable] ?? getValueFromAlias(baseVariable, variables); + if (!value) { + throw new Error(`Cannot derive from ${baseVariable} because it is neither defined in config nor is it an alias!`); + } const derivedValue = derive(value, operation, argument); resolvedMap.set(wholeVariable, derivedValue); } From 52101239773d598c3fdc94b6b6a778ad6d3db147 Mon Sep 17 00:00:00 2001 From: RMidhunSuresh Date: Thu, 10 Mar 2022 17:19:04 +0530 Subject: [PATCH 063/238] Document options --- postcss/css-compile-variables.js | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/postcss/css-compile-variables.js b/postcss/css-compile-variables.js index 732ed8b3..38f5c471 100644 --- a/postcss/css-compile-variables.js +++ b/postcss/css-compile-variables.js @@ -76,13 +76,21 @@ function addResolvedVariablesToRootSelector(root, variables, { Rule, Declaration root.append(newRule); } -/* * - * @type {import('postcss').PluginCreator} +/** + * @callback derive + * @param {string} value - The base value on which an operation is applied + * @param {string} operation - The operation to be applied (eg: darker, lighter...) + * @param {string} argument - The argument for this operation + */ +/** + * + * @param {Object} opts - Options for the plugin + * @param {Object} opts.variables - An object with records of the form: {base_variable_name: value} + * @param {derive} opts.derive - The callback which contains the logic for resolving derived variables */ module.exports = (opts = {}) => { aliasMap = new Map(); resolvedMap = new Map(); - const {variables} = opts; return { postcssPlugin: "postcss-compile-variables", @@ -94,7 +102,7 @@ module.exports = (opts = {}) => { */ root.walkDecls(decl => extractAlias(decl)); root.walkDecls(decl => resolveDerivedVariable(decl, opts)); - addResolvedVariablesToRootSelector(root, variables, { Rule, Declaration }); + addResolvedVariablesToRootSelector(root, opts.variables, { Rule, Declaration }); }, }; }; From f732164b5fb253ad1dfe88e05d50ffdcfba2df8a Mon Sep 17 00:00:00 2001 From: RMidhunSuresh Date: Thu, 10 Mar 2022 17:21:38 +0530 Subject: [PATCH 064/238] Formatting change --- postcss/css-compile-variables.js | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/postcss/css-compile-variables.js b/postcss/css-compile-variables.js index 38f5c471..cf2b451d 100644 --- a/postcss/css-compile-variables.js +++ b/postcss/css-compile-variables.js @@ -61,16 +61,16 @@ function extractAlias(decl) { } } -function addResolvedVariablesToRootSelector(root, variables, { Rule, Declaration }) { +function addResolvedVariablesToRootSelector(root, variables, {Rule, Declaration}) { const newRule = new Rule({ selector: ":root", source: root.source }); // Add base css variables to :root for (const [key, value] of Object.entries(variables)) { - const declaration = new Declaration({ prop: `--${key}`, value }); + const declaration = new Declaration({prop: `--${key}`, value}); newRule.append(declaration); } // Add derived css variables to :root resolvedMap.forEach((value, key) => { - const declaration = new Declaration({ prop: key, value }); + const declaration = new Declaration({prop: key, value}); newRule.append(declaration); }); root.append(newRule); @@ -94,7 +94,7 @@ module.exports = (opts = {}) => { return { postcssPlugin: "postcss-compile-variables", - Once(root, { Rule, Declaration }) { + Once(root, {Rule, Declaration}) { /* Go through the CSS file once to extract all aliases. We use the extracted alias when resolving derived variables @@ -102,7 +102,7 @@ module.exports = (opts = {}) => { */ root.walkDecls(decl => extractAlias(decl)); root.walkDecls(decl => resolveDerivedVariable(decl, opts)); - addResolvedVariablesToRootSelector(root, opts.variables, { Rule, Declaration }); + addResolvedVariablesToRootSelector(root, opts.variables, {Rule, Declaration}); }, }; }; From ff10297bf88802e35f9a11309e52ee8558348f28 Mon Sep 17 00:00:00 2001 From: RMidhunSuresh Date: Thu, 10 Mar 2022 17:22:02 +0530 Subject: [PATCH 065/238] Explicitly convert to number --- postcss/color.js | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/postcss/color.js b/postcss/color.js index 2251a56e..f61dac1e 100644 --- a/postcss/color.js +++ b/postcss/color.js @@ -17,13 +17,14 @@ limitations under the License. const offColor = require("off-color").offColor; module.exports.derive = function (value, operation, argument) { + const argumentAsNumber = parseInt(argument); switch (operation) { case "darker": { - const newColorString = offColor(value).darken(argument / 100).hex(); + const newColorString = offColor(value).darken(argumentAsNumber / 100).hex(); return newColorString; } case "lighter": { - const newColorString = offColor(value).lighten(argument / 100).hex(); + const newColorString = offColor(value).lighten(argumentAsNumber / 100).hex(); return newColorString; } } From 9f77df0bff2d1fd9d7f8216f7fca35a28ad08713 Mon Sep 17 00:00:00 2001 From: RMidhunSuresh Date: Thu, 10 Mar 2022 17:24:32 +0530 Subject: [PATCH 066/238] Match regex only if declaration is a variable --- postcss/css-compile-variables.js | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/postcss/css-compile-variables.js b/postcss/css-compile-variables.js index cf2b451d..d5e78352 100644 --- a/postcss/css-compile-variables.js +++ b/postcss/css-compile-variables.js @@ -55,9 +55,11 @@ function resolveDerivedVariable(decl, {variables, derive}) { } function extractAlias(decl) { - const wholeVariable = decl.value.match(/var\(--(.+)\)/)?.[1]; - if (decl.prop.startsWith("--") && wholeVariable) { - aliasMap.set(decl.prop, wholeVariable); + if (decl.variable) { + const wholeVariable = decl.value.match(/var\(--(.+)\)/)?.[1]; + if (wholeVariable) { + aliasMap.set(decl.prop, wholeVariable); + } } } From 6f4a7e074a4f6b32a6a716957ea1adb850bb94ae Mon Sep 17 00:00:00 2001 From: RMidhunSuresh Date: Thu, 10 Mar 2022 17:27:12 +0530 Subject: [PATCH 067/238] Change confusing doc --- postcss/css-compile-variables.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/postcss/css-compile-variables.js b/postcss/css-compile-variables.js index d5e78352..4adfc7b1 100644 --- a/postcss/css-compile-variables.js +++ b/postcss/css-compile-variables.js @@ -87,7 +87,7 @@ function addResolvedVariablesToRootSelector(root, variables, {Rule, Declaration} /** * * @param {Object} opts - Options for the plugin - * @param {Object} opts.variables - An object with records of the form: {base_variable_name: value} + * @param {Object} opts.variables - An object with of the form: {base_variable_name_1: value, base_variable_name_2: value, ...} * @param {derive} opts.derive - The callback which contains the logic for resolving derived variables */ module.exports = (opts = {}) => { From 2c068cc3ced36dd791bf0106ca827b13a13dcd31 Mon Sep 17 00:00:00 2001 From: RMidhunSuresh Date: Thu, 10 Mar 2022 17:42:12 +0530 Subject: [PATCH 068/238] typo --- postcss/css-compile-variables.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/postcss/css-compile-variables.js b/postcss/css-compile-variables.js index 4adfc7b1..023c8eb7 100644 --- a/postcss/css-compile-variables.js +++ b/postcss/css-compile-variables.js @@ -87,7 +87,7 @@ function addResolvedVariablesToRootSelector(root, variables, {Rule, Declaration} /** * * @param {Object} opts - Options for the plugin - * @param {Object} opts.variables - An object with of the form: {base_variable_name_1: value, base_variable_name_2: value, ...} + * @param {Object} opts.variables - An object of the form: {base_variable_name_1: value, base_variable_name_2: value, ...} * @param {derive} opts.derive - The callback which contains the logic for resolving derived variables */ module.exports = (opts = {}) => { From 4020ade70c7cfd15b7665aa26dc07f3657cc98dd Mon Sep 17 00:00:00 2001 From: RMidhunSuresh Date: Thu, 10 Mar 2022 17:51:25 +0530 Subject: [PATCH 069/238] Remove redundant comment --- postcss/css-compile-variables.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/postcss/css-compile-variables.js b/postcss/css-compile-variables.js index 023c8eb7..d7eca7f2 100644 --- a/postcss/css-compile-variables.js +++ b/postcss/css-compile-variables.js @@ -21,7 +21,7 @@ let resolvedMap; function getValueFromAlias(alias, variables) { const derivedVariable = aliasMap.get(`--${alias}`); - return variables[derivedVariable] ?? resolvedMap.get(`--${derivedVariable}`); // what if we haven't resolved this variable yet? + return variables[derivedVariable] ?? resolvedMap.get(`--${derivedVariable}`); } function parseDeclarationValue(value) { From bca1648df6a03370fc071c1723f504cc146e8b16 Mon Sep 17 00:00:00 2001 From: RMidhunSuresh Date: Mon, 14 Mar 2022 11:34:58 +0530 Subject: [PATCH 070/238] Move plugin to /scripts and create eslintrc --- .eslintrc.js | 1 - package.json | 2 +- scripts/.eslintrc.js | 18 ++++++++++++++++++ {postcss => scripts/postcss}/color.js | 0 .../postcss}/css-compile-variables.js | 0 {postcss => scripts/postcss}/test.js | 0 6 files changed, 19 insertions(+), 2 deletions(-) create mode 100644 scripts/.eslintrc.js rename {postcss => scripts/postcss}/color.js (100%) rename {postcss => scripts/postcss}/css-compile-variables.js (100%) rename {postcss => scripts/postcss}/test.js (100%) diff --git a/.eslintrc.js b/.eslintrc.js index 1985ccc1..cb28f4c8 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -1,7 +1,6 @@ module.exports = { "env": { "browser": true, - "node": true, "es6": true }, "extends": "eslint:recommended", diff --git a/package.json b/package.json index fa9c4344..12c73994 100644 --- a/package.json +++ b/package.json @@ -10,7 +10,7 @@ "lint-ts": "eslint src/ -c .ts-eslintrc.js --ext .ts", "lint-ci": "eslint src/", "test": "impunity --entry-point src/platform/web/main.js src/platform/web/Platform.js --force-esm-dirs lib/ src/ --root-dir src/", - "test:postcss": "impunity --entry-point postcss/test.js ", + "test:postcss": "impunity --entry-point scripts/postcss/test.js ", "start": "vite --port 3000", "build": "vite build", "build:sdk": "./scripts/sdk/build.sh" diff --git a/scripts/.eslintrc.js b/scripts/.eslintrc.js new file mode 100644 index 00000000..1cdfca84 --- /dev/null +++ b/scripts/.eslintrc.js @@ -0,0 +1,18 @@ +module.exports = { + "env": { + "node": true, + "es6": true + }, + "extends": "eslint:recommended", + "parserOptions": { + "ecmaVersion": 2020, + "sourceType": "module" + }, + "rules": { + "no-console": "off", + "no-empty": "off", + "no-prototype-builtins": "off", + "no-unused-vars": "warn" + }, +}; + diff --git a/postcss/color.js b/scripts/postcss/color.js similarity index 100% rename from postcss/color.js rename to scripts/postcss/color.js diff --git a/postcss/css-compile-variables.js b/scripts/postcss/css-compile-variables.js similarity index 100% rename from postcss/css-compile-variables.js rename to scripts/postcss/css-compile-variables.js diff --git a/postcss/test.js b/scripts/postcss/test.js similarity index 100% rename from postcss/test.js rename to scripts/postcss/test.js From 19a6d669a94172f38ab7dd50c8d8a95fada4bc5d Mon Sep 17 00:00:00 2001 From: RMidhunSuresh Date: Mon, 14 Mar 2022 23:26:37 +0530 Subject: [PATCH 071/238] Extract base variables from css --- scripts/postcss/css-compile-variables.js | 37 +++++++++--------- scripts/postcss/test.js | 48 ++++++++++++------------ 2 files changed, 43 insertions(+), 42 deletions(-) diff --git a/scripts/postcss/css-compile-variables.js b/scripts/postcss/css-compile-variables.js index d7eca7f2..ea939780 100644 --- a/scripts/postcss/css-compile-variables.js +++ b/scripts/postcss/css-compile-variables.js @@ -18,10 +18,11 @@ const valueParser = require("postcss-value-parser"); let aliasMap; let resolvedMap; +let baseVariables; -function getValueFromAlias(alias, variables) { +function getValueFromAlias(alias) { const derivedVariable = aliasMap.get(`--${alias}`); - return variables[derivedVariable] ?? resolvedMap.get(`--${derivedVariable}`); + return baseVariables.get(derivedVariable) ?? resolvedMap.get(derivedVariable); } function parseDeclarationValue(value) { @@ -37,14 +38,14 @@ function parseDeclarationValue(value) { return variables; } -function resolveDerivedVariable(decl, {variables, derive}) { +function resolveDerivedVariable(decl, derive) { const RE_VARIABLE_VALUE = /--(.+)--(.+)-(.+)/; const variableCollection = parseDeclarationValue(decl.value); for (const variable of variableCollection) { const matches = variable.match(RE_VARIABLE_VALUE); if (matches) { const [wholeVariable, baseVariable, operation, argument] = matches; - const value = variables[baseVariable] ?? getValueFromAlias(baseVariable, variables); + const value = baseVariables.get(`--${baseVariable}`) ?? getValueFromAlias(baseVariable); if (!value) { throw new Error(`Cannot derive from ${baseVariable} because it is neither defined in config nor is it an alias!`); } @@ -54,22 +55,21 @@ function resolveDerivedVariable(decl, {variables, derive}) { } } -function extractAlias(decl) { +function extract(decl) { if (decl.variable) { - const wholeVariable = decl.value.match(/var\(--(.+)\)/)?.[1]; + // see if right side is of form "var(--foo)" + const wholeVariable = decl.value.match(/var\((--.+)\)/)?.[1]; if (wholeVariable) { aliasMap.set(decl.prop, wholeVariable); + // Since this is an alias, we shouldn't store it in baseVariables + return; } + baseVariables.set(decl.prop, decl.value); } } -function addResolvedVariablesToRootSelector(root, variables, {Rule, Declaration}) { +function addResolvedVariablesToRootSelector(root, {Rule, Declaration}) { const newRule = new Rule({ selector: ":root", source: root.source }); - // Add base css variables to :root - for (const [key, value] of Object.entries(variables)) { - const declaration = new Declaration({prop: `--${key}`, value}); - newRule.append(declaration); - } // Add derived css variables to :root resolvedMap.forEach((value, key) => { const declaration = new Declaration({prop: key, value}); @@ -87,24 +87,23 @@ function addResolvedVariablesToRootSelector(root, variables, {Rule, Declaration} /** * * @param {Object} opts - Options for the plugin - * @param {Object} opts.variables - An object of the form: {base_variable_name_1: value, base_variable_name_2: value, ...} * @param {derive} opts.derive - The callback which contains the logic for resolving derived variables */ module.exports = (opts = {}) => { aliasMap = new Map(); resolvedMap = new Map(); + baseVariables = new Map(); return { postcssPlugin: "postcss-compile-variables", Once(root, {Rule, Declaration}) { /* - Go through the CSS file once to extract all aliases. - We use the extracted alias when resolving derived variables - later. + Go through the CSS file once to extract all aliases and base variables. + We use these when resolving derived variables later. */ - root.walkDecls(decl => extractAlias(decl)); - root.walkDecls(decl => resolveDerivedVariable(decl, opts)); - addResolvedVariablesToRootSelector(root, opts.variables, {Rule, Declaration}); + root.walkDecls(decl => extract(decl)); + root.walkDecls(decl => resolveDerivedVariable(decl, opts.derive)); + addResolvedVariablesToRootSelector(root, {Rule, Declaration}); }, }; }; diff --git a/scripts/postcss/test.js b/scripts/postcss/test.js index 4ed74c67..36ff9282 100644 --- a/scripts/postcss/test.js +++ b/scripts/postcss/test.js @@ -31,7 +31,11 @@ async function run(input, output, opts = {}, assert) { module.exports.tests = function tests() { return { "derived variables are resolved": async (assert) => { - const inputCSS = `div { + const inputCSS = ` + :root { + --foo-color: #ff0; + } + div { background-color: var(--foo-color--lighter-50); }`; const transformedColor = offColor("#ff0").lighten(0.5); @@ -39,38 +43,30 @@ module.exports.tests = function tests() { inputCSS + ` :root { - --foo-color: #ff0; --foo-color--lighter-50: ${transformedColor.hex()}; } `; - await run( - inputCSS, - outputCSS, - { variables: { "foo-color": "#ff0" } }, - assert - ); + await run( inputCSS, outputCSS, {}, assert); }, "derived variables work with alias": async (assert) => { - const inputCSS = `div { + const inputCSS = ` + :root { + --icon-color: #fff; + } + div { background: var(--icon-color--darker-20); --my-alias: var(--icon-color--darker-20); color: var(--my-alias--lighter-15); }`; const colorDarker = offColor("#fff").darken(0.2).hex(); const aliasLighter = offColor(colorDarker).lighten(0.15).hex(); - const outputCSS = `div { - background: var(--icon-color--darker-20); - --my-alias: var(--icon-color--darker-20); - color: var(--my-alias--lighter-15); - } - :root { - --icon-color: #fff; + const outputCSS = inputCSS + `:root { --icon-color--darker-20: ${colorDarker}; --my-alias--lighter-15: ${aliasLighter}; } `; - await run(inputCSS, outputCSS, { variables: { "icon-color": "#fff" }, }, assert); + await run(inputCSS, outputCSS, { }, assert); }, "derived variable throws if base not present in config": async (assert) => { @@ -81,7 +77,11 @@ module.exports.tests = function tests() { }, "multiple derived variable in single declaration is parsed correctly": async (assert) => { - const inputCSS = `div { + const inputCSS = ` + :root { + --foo-color: #ff0; + } + div { background-color: linear-gradient(var(--foo-color--lighter-50), var(--foo-color--darker-20)); }`; const transformedColor1 = offColor("#ff0").lighten(0.5); @@ -90,15 +90,18 @@ module.exports.tests = function tests() { inputCSS + ` :root { - --foo-color: #ff0; --foo-color--lighter-50: ${transformedColor1.hex()}; --foo-color--darker-20: ${transformedColor2.hex()}; } `; - await run( inputCSS, outputCSS, { variables: { "foo-color": "#ff0" } }, assert); + await run( inputCSS, outputCSS, { }, assert); }, "multiple aliased-derived variable in single declaration is parsed correctly": async (assert) => { - const inputCSS = `div { + const inputCSS = ` + :root { + --foo-color: #ff0; + } + div { --my-alias: var(--foo-color); background-color: linear-gradient(var(--my-alias--lighter-50), var(--my-alias--darker-20)); }`; @@ -108,12 +111,11 @@ module.exports.tests = function tests() { inputCSS + ` :root { - --foo-color: #ff0; --my-alias--lighter-50: ${transformedColor1.hex()}; --my-alias--darker-20: ${transformedColor2.hex()}; } `; - await run( inputCSS, outputCSS, { variables: { "foo-color": "#ff0" } }, assert); + await run( inputCSS, outputCSS, { }, assert); } }; }; From 5d4323cd1dcabaacdfb98bb0b44ce09cc25a739d Mon Sep 17 00:00:00 2001 From: RMidhunSuresh Date: Wed, 23 Mar 2022 17:12:14 +0530 Subject: [PATCH 072/238] Remove stray "--" from code --- scripts/postcss/css-compile-variables.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/scripts/postcss/css-compile-variables.js b/scripts/postcss/css-compile-variables.js index ea939780..23795145 100644 --- a/scripts/postcss/css-compile-variables.js +++ b/scripts/postcss/css-compile-variables.js @@ -21,7 +21,7 @@ let resolvedMap; let baseVariables; function getValueFromAlias(alias) { - const derivedVariable = aliasMap.get(`--${alias}`); + const derivedVariable = aliasMap.get(alias); return baseVariables.get(derivedVariable) ?? resolvedMap.get(derivedVariable); } @@ -39,13 +39,13 @@ function parseDeclarationValue(value) { } function resolveDerivedVariable(decl, derive) { - const RE_VARIABLE_VALUE = /--(.+)--(.+)-(.+)/; + const RE_VARIABLE_VALUE = /(--.+)--(.+)-(.+)/; const variableCollection = parseDeclarationValue(decl.value); for (const variable of variableCollection) { const matches = variable.match(RE_VARIABLE_VALUE); if (matches) { const [wholeVariable, baseVariable, operation, argument] = matches; - const value = baseVariables.get(`--${baseVariable}`) ?? getValueFromAlias(baseVariable); + const value = baseVariables.get(baseVariable) ?? getValueFromAlias(baseVariable); if (!value) { throw new Error(`Cannot derive from ${baseVariable} because it is neither defined in config nor is it an alias!`); } From 59ca8e63095d7c81e41e2e82647663a5a6e4afe8 Mon Sep 17 00:00:00 2001 From: RMidhunSuresh Date: Wed, 23 Mar 2022 17:25:12 +0530 Subject: [PATCH 073/238] Add explanation of plugin --- scripts/postcss/css-compile-variables.js | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/scripts/postcss/css-compile-variables.js b/scripts/postcss/css-compile-variables.js index 23795145..a3c3aa30 100644 --- a/scripts/postcss/css-compile-variables.js +++ b/scripts/postcss/css-compile-variables.js @@ -16,6 +16,20 @@ limitations under the License. const valueParser = require("postcss-value-parser"); +/** + * This plugin derives new css variables from a given set of base variables. + * A derived css variable has the form --base--operation-argument; meaning that the derived + * variable has a value that is generated from the base variable "base" by applying "operation" + * with given "argument". + * + * eg: given the base variable --foo-color: #40E0D0, --foo-color--darker-20 is a css variable + * derived from foo-color by making it 20% more darker. + * + * All derived variables are added to the :root section. + * + * The actual derivation is done outside the plugin in a callback. + */ + let aliasMap; let resolvedMap; let baseVariables; From 72785e7c3ebc5d0ca1c5208011c14831ab859116 Mon Sep 17 00:00:00 2001 From: RMidhunSuresh Date: Wed, 23 Mar 2022 20:39:24 +0530 Subject: [PATCH 074/238] Remove -- from everywhere --- scripts/postcss/css-compile-variables.js | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/scripts/postcss/css-compile-variables.js b/scripts/postcss/css-compile-variables.js index a3c3aa30..3ed34513 100644 --- a/scripts/postcss/css-compile-variables.js +++ b/scripts/postcss/css-compile-variables.js @@ -53,12 +53,12 @@ function parseDeclarationValue(value) { } function resolveDerivedVariable(decl, derive) { - const RE_VARIABLE_VALUE = /(--.+)--(.+)-(.+)/; + const RE_VARIABLE_VALUE = /--((.+)--(.+)-(.+))/; const variableCollection = parseDeclarationValue(decl.value); for (const variable of variableCollection) { const matches = variable.match(RE_VARIABLE_VALUE); if (matches) { - const [wholeVariable, baseVariable, operation, argument] = matches; + const [, wholeVariable, baseVariable, operation, argument] = matches; const value = baseVariables.get(baseVariable) ?? getValueFromAlias(baseVariable); if (!value) { throw new Error(`Cannot derive from ${baseVariable} because it is neither defined in config nor is it an alias!`); @@ -72,13 +72,15 @@ function resolveDerivedVariable(decl, derive) { function extract(decl) { if (decl.variable) { // see if right side is of form "var(--foo)" - const wholeVariable = decl.value.match(/var\((--.+)\)/)?.[1]; + const wholeVariable = decl.value.match(/var\(--(.+)\)/)?.[1]; + // remove -- from the prop + const prop = decl.prop.substring(2); if (wholeVariable) { - aliasMap.set(decl.prop, wholeVariable); + aliasMap.set(prop, wholeVariable); // Since this is an alias, we shouldn't store it in baseVariables return; } - baseVariables.set(decl.prop, decl.value); + baseVariables.set(prop, decl.value); } } @@ -86,7 +88,7 @@ function addResolvedVariablesToRootSelector(root, {Rule, Declaration}) { const newRule = new Rule({ selector: ":root", source: root.source }); // Add derived css variables to :root resolvedMap.forEach((value, key) => { - const declaration = new Declaration({prop: key, value}); + const declaration = new Declaration({prop: `--${key}`, value}); newRule.append(declaration); }); root.append(newRule); From e8bd1f33900851e972c06f8f0e01caa8f5b81caf Mon Sep 17 00:00:00 2001 From: RMidhunSuresh Date: Sun, 27 Mar 2022 20:06:26 +0530 Subject: [PATCH 075/238] Pass result as message --- scripts/postcss/css-compile-variables.js | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/scripts/postcss/css-compile-variables.js b/scripts/postcss/css-compile-variables.js index 3ed34513..b48fc8fa 100644 --- a/scripts/postcss/css-compile-variables.js +++ b/scripts/postcss/css-compile-variables.js @@ -112,7 +112,7 @@ module.exports = (opts = {}) => { return { postcssPlugin: "postcss-compile-variables", - Once(root, {Rule, Declaration}) { + Once(root, {Rule, Declaration, result}) { /* Go through the CSS file once to extract all aliases and base variables. We use these when resolving derived variables later. @@ -120,6 +120,13 @@ module.exports = (opts = {}) => { root.walkDecls(decl => extract(decl)); root.walkDecls(decl => resolveDerivedVariable(decl, opts.derive)); addResolvedVariablesToRootSelector(root, {Rule, Declaration}); + // Publish both the base-variables and derived-variables to the other postcss-plugins + const combinedMap = new Map([...baseVariables, ...resolvedMap]); + result.messages.push({ + type: "resolved-variable-map", + plugin: "postcss-compile-variables", + colorMap: combinedMap, + }) }, }; }; From 2015fa2d7a892777991f8326ca56d1d6e15534a1 Mon Sep 17 00:00:00 2001 From: RMidhunSuresh Date: Sun, 27 Mar 2022 20:18:42 +0530 Subject: [PATCH 076/238] Move postcss-value-parser to dev dependency --- package.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index 12c73994..d506d872 100644 --- a/package.json +++ b/package.json @@ -43,6 +43,7 @@ "node-html-parser": "^4.0.0", "postcss-css-variables": "^0.18.0", "postcss-flexbugs-fixes": "^5.0.2", + "postcss-value-parser": "^4.2.0", "regenerator-runtime": "^0.13.7", "text-encoding": "^0.7.0", "typescript": "^4.3.5", @@ -54,7 +55,6 @@ "another-json": "^0.2.0", "base64-arraybuffer": "^0.2.0", "dompurify": "^2.3.0", - "off-color": "^2.0.0", - "postcss-value-parser": "^4.2.0" + "off-color": "^2.0.0" } } From 4350d2f264927bf655793a178e37ecdff8213494 Mon Sep 17 00:00:00 2001 From: RMidhunSuresh Date: Fri, 1 Apr 2022 16:20:58 +0530 Subject: [PATCH 077/238] Don't derive variables for runtime theme --- scripts/postcss/css-compile-variables.js | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/scripts/postcss/css-compile-variables.js b/scripts/postcss/css-compile-variables.js index b48fc8fa..f7d63fab 100644 --- a/scripts/postcss/css-compile-variables.js +++ b/scripts/postcss/css-compile-variables.js @@ -113,6 +113,11 @@ module.exports = (opts = {}) => { postcssPlugin: "postcss-compile-variables", Once(root, {Rule, Declaration, result}) { + const cssFileLocation = root.source.input.from; + if (cssFileLocation.includes("type=runtime")) { + // If this is a runtime theme, don't derive variables. + return; + } /* Go through the CSS file once to extract all aliases and base variables. We use these when resolving derived variables later. From 918a3e42b1cf809af11bf6f6feb076f48700fd73 Mon Sep 17 00:00:00 2001 From: RMidhunSuresh Date: Fri, 1 Apr 2022 16:23:33 +0530 Subject: [PATCH 078/238] Populate compiled variables map --- scripts/postcss/css-compile-variables.js | 23 ++++++++++++++++++++++- 1 file changed, 22 insertions(+), 1 deletion(-) diff --git a/scripts/postcss/css-compile-variables.js b/scripts/postcss/css-compile-variables.js index f7d63fab..263fbe36 100644 --- a/scripts/postcss/css-compile-variables.js +++ b/scripts/postcss/css-compile-variables.js @@ -94,6 +94,23 @@ function addResolvedVariablesToRootSelector(root, {Rule, Declaration}) { root.append(newRule); } +function populateMapWithDerivedVariables(map, cssFileLocation) { + const location = cssFileLocation.match(/(.+)\/.+\.css/)?.[1]; + if (map.has(location)) { + /** + * This postcss plugin is going to run on all theme variants of a single theme. + * But we only really need to populate the map once since theme variants only differ + * by the values of the base-variables and we don't care about values here. + */ + return; + } + const derivedVariables = new Set([ + ...([...resolvedMap.keys()].filter(v => !aliasMap.has(v))), + ...([...aliasMap.entries()].map(([alias, variable]) => `${alias}=${variable}`)) + ]); + map.set(location, { "derived-variables": derivedVariables }); +} + /** * @callback derive * @param {string} value - The base value on which an operation is applied @@ -104,6 +121,7 @@ function addResolvedVariablesToRootSelector(root, {Rule, Declaration}) { * * @param {Object} opts - Options for the plugin * @param {derive} opts.derive - The callback which contains the logic for resolving derived variables + * @param {Map} opts.compiledVariables - A map that stores derived variables so that manifest source sections can be produced */ module.exports = (opts = {}) => { aliasMap = new Map(); @@ -125,13 +143,16 @@ module.exports = (opts = {}) => { root.walkDecls(decl => extract(decl)); root.walkDecls(decl => resolveDerivedVariable(decl, opts.derive)); addResolvedVariablesToRootSelector(root, {Rule, Declaration}); + if (opts.compiledVariables){ + populateMapWithDerivedVariables(opts.compiledVariables, cssFileLocation); + } // Publish both the base-variables and derived-variables to the other postcss-plugins const combinedMap = new Map([...baseVariables, ...resolvedMap]); result.messages.push({ type: "resolved-variable-map", plugin: "postcss-compile-variables", colorMap: combinedMap, - }) + }); }, }; }; From 859449ed60bd7ce7476d0fedac958fd1a62c3a17 Mon Sep 17 00:00:00 2001 From: RMidhunSuresh Date: Fri, 1 Apr 2022 16:41:00 +0530 Subject: [PATCH 079/238] Write test for map population --- scripts/postcss/test.js | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/scripts/postcss/test.js b/scripts/postcss/test.js index 36ff9282..d07f07e4 100644 --- a/scripts/postcss/test.js +++ b/scripts/postcss/test.js @@ -96,6 +96,7 @@ module.exports.tests = function tests() { `; await run( inputCSS, outputCSS, { }, assert); }, + "multiple aliased-derived variable in single declaration is parsed correctly": async (assert) => { const inputCSS = ` :root { @@ -116,6 +117,23 @@ module.exports.tests = function tests() { } `; await run( inputCSS, outputCSS, { }, assert); + }, + + "compiledVariables map is populated": async (assert) => { + const compiledVariables = new Map(); + const inputCSS = ` + :root { + --icon-color: #fff; + } + div { + background: var(--icon-color--darker-20); + --my-alias: var(--icon-color--darker-20); + color: var(--my-alias--lighter-15); + }`; + await postcss([plugin({ derive, compiledVariables })]).process(inputCSS, { from: "/foo/bar/test.css", }); + const actualSet = compiledVariables.get("/foo/bar")["derived-variables"]; + const expectedSet = new Set(["icon-color--darker-20", "my-alias=icon-color--darker-20", "my-alias--lighter-15"]); + assert.deepEqual(actualSet, expectedSet); } }; }; From 76789eacf154a7e172bd3572480b94a8604cdf16 Mon Sep 17 00:00:00 2001 From: RMidhunSuresh Date: Fri, 1 Apr 2022 20:43:42 +0530 Subject: [PATCH 080/238] Use array instead of Set --- scripts/postcss/css-compile-variables.js | 4 ++-- scripts/postcss/test.js | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/scripts/postcss/css-compile-variables.js b/scripts/postcss/css-compile-variables.js index 263fbe36..7152535e 100644 --- a/scripts/postcss/css-compile-variables.js +++ b/scripts/postcss/css-compile-variables.js @@ -104,10 +104,10 @@ function populateMapWithDerivedVariables(map, cssFileLocation) { */ return; } - const derivedVariables = new Set([ + const derivedVariables = [ ...([...resolvedMap.keys()].filter(v => !aliasMap.has(v))), ...([...aliasMap.entries()].map(([alias, variable]) => `${alias}=${variable}`)) - ]); + ]; map.set(location, { "derived-variables": derivedVariables }); } diff --git a/scripts/postcss/test.js b/scripts/postcss/test.js index d07f07e4..8d1412ea 100644 --- a/scripts/postcss/test.js +++ b/scripts/postcss/test.js @@ -131,9 +131,9 @@ module.exports.tests = function tests() { color: var(--my-alias--lighter-15); }`; await postcss([plugin({ derive, compiledVariables })]).process(inputCSS, { from: "/foo/bar/test.css", }); - const actualSet = compiledVariables.get("/foo/bar")["derived-variables"]; - const expectedSet = new Set(["icon-color--darker-20", "my-alias=icon-color--darker-20", "my-alias--lighter-15"]); - assert.deepEqual(actualSet, expectedSet); + const actualArray = compiledVariables.get("/foo/bar")["derived-variables"]; + const expectedArray = ["icon-color--darker-20", "my-alias=icon-color--darker-20", "my-alias--lighter-15"]; + assert.deepStrictEqual(actualArray.sort(), expectedArray.sort()); } }; }; From 454345c9b2bb9e3e965cd9e36374da95973164fb Mon Sep 17 00:00:00 2001 From: RMidhunSuresh Date: Tue, 5 Apr 2022 15:08:35 +0530 Subject: [PATCH 081/238] Always set map --- scripts/postcss/css-compile-variables.js | 8 -------- 1 file changed, 8 deletions(-) diff --git a/scripts/postcss/css-compile-variables.js b/scripts/postcss/css-compile-variables.js index 7152535e..7302f8d4 100644 --- a/scripts/postcss/css-compile-variables.js +++ b/scripts/postcss/css-compile-variables.js @@ -96,14 +96,6 @@ function addResolvedVariablesToRootSelector(root, {Rule, Declaration}) { function populateMapWithDerivedVariables(map, cssFileLocation) { const location = cssFileLocation.match(/(.+)\/.+\.css/)?.[1]; - if (map.has(location)) { - /** - * This postcss plugin is going to run on all theme variants of a single theme. - * But we only really need to populate the map once since theme variants only differ - * by the values of the base-variables and we don't care about values here. - */ - return; - } const derivedVariables = [ ...([...resolvedMap.keys()].filter(v => !aliasMap.has(v))), ...([...aliasMap.entries()].map(([alias, variable]) => `${alias}=${variable}`)) From 95d17303c34bde1ceb125055262e379febce3e4f Mon Sep 17 00:00:00 2001 From: Eric Eastwood Date: Tue, 5 Apr 2022 17:16:55 -0500 Subject: [PATCH 082/238] Update Vite which includes fixes to importing `*.js?url` with `exports` Update to Vite which includes https://github.com/vitejs/vite/pull/7098 --- package.json | 2 +- yarn.lock | 224 ++++++++++++++++++++++++++++++++++++++++----------- 2 files changed, 179 insertions(+), 47 deletions(-) diff --git a/package.json b/package.json index 17719801..9b785c35 100644 --- a/package.json +++ b/package.json @@ -46,7 +46,7 @@ "regenerator-runtime": "^0.13.7", "text-encoding": "^0.7.0", "typescript": "^4.3.5", - "vite": "todo: wait for next Vite release", + "vite": "^2.9.1", "xxhashjs": "^0.2.2" }, "dependencies": { diff --git a/yarn.lock b/yarn.lock index 7bcefdd4..71fe419e 100644 --- a/yarn.lock +++ b/yarn.lock @@ -498,66 +498,141 @@ entities@^2.0.0: version "4.2.8" resolved "https://github.com/bwindels/es6-promise.git#112f78f5829e627055b0ff56a52fecb63f6003b1" +esbuild-android-64@0.14.31: + version "0.14.31" + resolved "https://registry.yarnpkg.com/esbuild-android-64/-/esbuild-android-64-0.14.31.tgz#4b7dfbbeee62b3495ba78220b85fb590eb68d5bf" + integrity sha512-MYkuJ91w07nGmr4EouejOZK2j/f5TCnsKxY8vRr2+wpKKfHD1LTJK28VbZa+y1+AL7v1V9G98ezTUwsV3CmXNw== + esbuild-android-arm64@0.13.15: version "0.13.15" resolved "https://registry.yarnpkg.com/esbuild-android-arm64/-/esbuild-android-arm64-0.13.15.tgz#3fc3ff0bab76fe35dd237476b5d2b32bb20a3d44" integrity sha512-m602nft/XXeO8YQPUDVoHfjyRVPdPgjyyXOxZ44MK/agewFFkPa8tUo6lAzSWh5Ui5PB4KR9UIFTSBKh/RrCmg== +esbuild-android-arm64@0.14.31: + version "0.14.31" + resolved "https://registry.yarnpkg.com/esbuild-android-arm64/-/esbuild-android-arm64-0.14.31.tgz#24c3d693924e044fb0d23206c3e627502b10b930" + integrity sha512-0rkH/35s7ZVcsw6nS0IAkR0dekSbjZGWdlOAf3jV0lGoPqqw0x6/TmaV9w7DQgUERTH1ApmPlpAMU4kVkCq9Jg== + esbuild-darwin-64@0.13.15: version "0.13.15" resolved "https://registry.yarnpkg.com/esbuild-darwin-64/-/esbuild-darwin-64-0.13.15.tgz#8e9169c16baf444eacec60d09b24d11b255a8e72" integrity sha512-ihOQRGs2yyp7t5bArCwnvn2Atr6X4axqPpEdCFPVp7iUj4cVSdisgvEKdNR7yH3JDjW6aQDw40iQFoTqejqxvQ== +esbuild-darwin-64@0.14.31: + version "0.14.31" + resolved "https://registry.yarnpkg.com/esbuild-darwin-64/-/esbuild-darwin-64-0.14.31.tgz#285fbdb6dc74d4410f43dee59e6a14ebff82a9d7" + integrity sha512-kP6xPZHxtJa36Hb0jC05L3VzQSZBW2f3bpnQS20czXTRGEmM2GDiYpGdI5g2QYaw6vC4PYXjnigq8usd9g9jnQ== + esbuild-darwin-arm64@0.13.15: version "0.13.15" resolved "https://registry.yarnpkg.com/esbuild-darwin-arm64/-/esbuild-darwin-arm64-0.13.15.tgz#1b07f893b632114f805e188ddfca41b2b778229a" integrity sha512-i1FZssTVxUqNlJ6cBTj5YQj4imWy3m49RZRnHhLpefFIh0To05ow9DTrXROTE1urGTQCloFUXTX8QfGJy1P8dQ== +esbuild-darwin-arm64@0.14.31: + version "0.14.31" + resolved "https://registry.yarnpkg.com/esbuild-darwin-arm64/-/esbuild-darwin-arm64-0.14.31.tgz#b39c471a8134ce2c7811eb96fab9c500b256261c" + integrity sha512-1ZMog4hkNsdBGtDDtsftUqX6S9N52gEx4vX5aVehsSptgoBFIar1XrPiBTQty7YNH+bJasTpSVaZQgElCVvPKQ== + esbuild-freebsd-64@0.13.15: version "0.13.15" resolved "https://registry.yarnpkg.com/esbuild-freebsd-64/-/esbuild-freebsd-64-0.13.15.tgz#0b8b7eca1690c8ec94c75680c38c07269c1f4a85" integrity sha512-G3dLBXUI6lC6Z09/x+WtXBXbOYQZ0E8TDBqvn7aMaOCzryJs8LyVXKY4CPnHFXZAbSwkCbqiPuSQ1+HhrNk7EA== +esbuild-freebsd-64@0.14.31: + version "0.14.31" + resolved "https://registry.yarnpkg.com/esbuild-freebsd-64/-/esbuild-freebsd-64-0.14.31.tgz#7ca700ef60ae12154bae63094ad41b21c6ae1a23" + integrity sha512-Zo0BYj7QpVFWoUpkv6Ng0RO2eJ4zk/WDaHMO88+jr5HuYmxsOre0imgwaZVPquTuJnCvL1G48BFucJ3tFflSeQ== + esbuild-freebsd-arm64@0.13.15: version "0.13.15" resolved "https://registry.yarnpkg.com/esbuild-freebsd-arm64/-/esbuild-freebsd-arm64-0.13.15.tgz#2e1a6c696bfdcd20a99578b76350b41db1934e52" integrity sha512-KJx0fzEDf1uhNOZQStV4ujg30WlnwqUASaGSFPhznLM/bbheu9HhqZ6mJJZM32lkyfGJikw0jg7v3S0oAvtvQQ== +esbuild-freebsd-arm64@0.14.31: + version "0.14.31" + resolved "https://registry.yarnpkg.com/esbuild-freebsd-arm64/-/esbuild-freebsd-arm64-0.14.31.tgz#f793085c7184fcd08649b8d185edc5c2ce112e82" + integrity sha512-t85bS6jbRpmdjr4pdr/FY/fpx8lo1vv9S7BAs2EsXKJQhRDMIiC3QW+k2acYJoRuqirlvJcJVFQGCq/PfyC1kA== + esbuild-linux-32@0.13.15: version "0.13.15" resolved "https://registry.yarnpkg.com/esbuild-linux-32/-/esbuild-linux-32-0.13.15.tgz#6fd39f36fc66dd45b6b5f515728c7bbebc342a69" integrity sha512-ZvTBPk0YWCLMCXiFmD5EUtB30zIPvC5Itxz0mdTu/xZBbbHJftQgLWY49wEPSn2T/TxahYCRDWun5smRa0Tu+g== +esbuild-linux-32@0.14.31: + version "0.14.31" + resolved "https://registry.yarnpkg.com/esbuild-linux-32/-/esbuild-linux-32-0.14.31.tgz#cac97ec7da6fbde0c21dbe08babd0d2a034f317d" + integrity sha512-XYtOk/GodSkv+UOYVwryGpGPuFnszsMvRMKq6cIUfFfdssHuKDsU9IZveyCG44J106J39ABenQ5EetbYtVJHUw== + esbuild-linux-64@0.13.15: version "0.13.15" resolved "https://registry.yarnpkg.com/esbuild-linux-64/-/esbuild-linux-64-0.13.15.tgz#9cb8e4bcd7574e67946e4ee5f1f1e12386bb6dd3" integrity sha512-eCKzkNSLywNeQTRBxJRQ0jxRCl2YWdMB3+PkWFo2BBQYC5mISLIVIjThNtn6HUNqua1pnvgP5xX0nHbZbPj5oA== +esbuild-linux-64@0.14.31: + version "0.14.31" + resolved "https://registry.yarnpkg.com/esbuild-linux-64/-/esbuild-linux-64-0.14.31.tgz#ec94cd5228e6777d2feb3c24a1fe1cbf8817d6da" + integrity sha512-Zf9CZxAxaXWHLqCg/QZ/hs0RU0XV3IBxV+ENQzy00S4QOTnZAvSLgPciILHHrVJ0lPIlb4XzAqlLM5y6iI2LIw== + esbuild-linux-arm64@0.13.15: version "0.13.15" resolved "https://registry.yarnpkg.com/esbuild-linux-arm64/-/esbuild-linux-arm64-0.13.15.tgz#3891aa3704ec579a1b92d2a586122e5b6a2bfba1" integrity sha512-bYpuUlN6qYU9slzr/ltyLTR9YTBS7qUDymO8SV7kjeNext61OdmqFAzuVZom+OLW1HPHseBfJ/JfdSlx8oTUoA== +esbuild-linux-arm64@0.14.31: + version "0.14.31" + resolved "https://registry.yarnpkg.com/esbuild-linux-arm64/-/esbuild-linux-arm64-0.14.31.tgz#d119188fccd6384db5c703de24c46dacaee3e9e8" + integrity sha512-V/H0tv+xpQ9IOHM+o85oCKNNidIEc5CcnDWl0V+hPd2F03dqdbFkWPBGphx8rD4JSQn6UefUQ1iH7y1qIzO8Fw== + esbuild-linux-arm@0.13.15: version "0.13.15" resolved "https://registry.yarnpkg.com/esbuild-linux-arm/-/esbuild-linux-arm-0.13.15.tgz#8a00e99e6a0c6c9a6b7f334841364d8a2b4aecfe" integrity sha512-wUHttDi/ol0tD8ZgUMDH8Ef7IbDX+/UsWJOXaAyTdkT7Yy9ZBqPg8bgB/Dn3CZ9SBpNieozrPRHm0BGww7W/jA== +esbuild-linux-arm@0.14.31: + version "0.14.31" + resolved "https://registry.yarnpkg.com/esbuild-linux-arm/-/esbuild-linux-arm-0.14.31.tgz#63e10846886901e5632a591d44160f95c5d12ba7" + integrity sha512-RpiaeHPRlgCCDskxoyIsI49BhcDtZ4cl8+SLffizDm0yMNWP538SUg0ezQ2TTOPj3/svaGIbkRDwYtAon0Sjkg== + esbuild-linux-mips64le@0.13.15: version "0.13.15" resolved "https://registry.yarnpkg.com/esbuild-linux-mips64le/-/esbuild-linux-mips64le-0.13.15.tgz#36b07cc47c3d21e48db3bb1f4d9ef8f46aead4f7" integrity sha512-KlVjIG828uFPyJkO/8gKwy9RbXhCEUeFsCGOJBepUlpa7G8/SeZgncUEz/tOOUJTcWMTmFMtdd3GElGyAtbSWg== +esbuild-linux-mips64le@0.14.31: + version "0.14.31" + resolved "https://registry.yarnpkg.com/esbuild-linux-mips64le/-/esbuild-linux-mips64le-0.14.31.tgz#1cd44f72cde6489a5d6deea7c54efa6f3d6590ee" + integrity sha512-9/oBfAckInRuUg6AEgdCLLn6KJ6UOJDOLmUinTsReVSg6AfV6wxYQJq9iQM2idRogP7GUpomJ+bvCdWXpotQRQ== + esbuild-linux-ppc64le@0.13.15: version "0.13.15" resolved "https://registry.yarnpkg.com/esbuild-linux-ppc64le/-/esbuild-linux-ppc64le-0.13.15.tgz#f7e6bba40b9a11eb9dcae5b01550ea04670edad2" integrity sha512-h6gYF+OsaqEuBjeesTBtUPw0bmiDu7eAeuc2OEH9S6mV9/jPhPdhOWzdeshb0BskRZxPhxPOjqZ+/OqLcxQwEQ== +esbuild-linux-ppc64le@0.14.31: + version "0.14.31" + resolved "https://registry.yarnpkg.com/esbuild-linux-ppc64le/-/esbuild-linux-ppc64le-0.14.31.tgz#3b5ccc05e5b8ef5c494f30a61fdd27811d2bbeeb" + integrity sha512-NMcb14Pg+8q8raGkzor9/R3vQwKzgxE3694BtO2SDLBwJuL2C1dQ1ZtM1t7ZvArQBgT8RiZVxb0/3fD+qGNk7g== + +esbuild-linux-riscv64@0.14.31: + version "0.14.31" + resolved "https://registry.yarnpkg.com/esbuild-linux-riscv64/-/esbuild-linux-riscv64-0.14.31.tgz#d74ca78c8ed1d9b40bc907a9e3ef6e83fc06189c" + integrity sha512-l13yvmsVfawAnoYfcpuvml+nTlrOmtdceXYufSkXl2DOb0JKcuR6ARlAzuQCDcpo49SOJy1cCxpwlOIsUQBfzA== + +esbuild-linux-s390x@0.14.31: + version "0.14.31" + resolved "https://registry.yarnpkg.com/esbuild-linux-s390x/-/esbuild-linux-s390x-0.14.31.tgz#1bd547b8b027e323b77a838d265cb56ece2543af" + integrity sha512-GIwV9mY3koYja9MCSkKLk1P7rj+MkPV0UsGsZ575hEcIBrXeKN9jBi6X/bxDDPEN/SUAH35cJhBNrZU4x9lEfg== + esbuild-netbsd-64@0.13.15: version "0.13.15" resolved "https://registry.yarnpkg.com/esbuild-netbsd-64/-/esbuild-netbsd-64-0.13.15.tgz#a2fedc549c2b629d580a732d840712b08d440038" integrity sha512-3+yE9emwoevLMyvu+iR3rsa+Xwhie7ZEHMGDQ6dkqP/ndFzRHkobHUKTe+NCApSqG5ce2z4rFu+NX/UHnxlh3w== +esbuild-netbsd-64@0.14.31: + version "0.14.31" + resolved "https://registry.yarnpkg.com/esbuild-netbsd-64/-/esbuild-netbsd-64-0.14.31.tgz#964a45dbad4fac92aa0a15056e38a182735bd6c6" + integrity sha512-bJ+pyLvKQm+Obp5k7/Wk8e9Gdkls56F1aiI3uptoIfOIUqsZImH7pDyTrSufwqsFp62kO9LRuwXnjDwQtPyhFQ== + esbuild-node-loader@^0.6.3: version "0.6.3" resolved "https://registry.yarnpkg.com/esbuild-node-loader/-/esbuild-node-loader-0.6.3.tgz#3b90012f8bc2fcbb2ef76a659482c2c99840c5e8" @@ -570,27 +645,52 @@ esbuild-openbsd-64@0.13.15: resolved "https://registry.yarnpkg.com/esbuild-openbsd-64/-/esbuild-openbsd-64-0.13.15.tgz#b22c0e5806d3a1fbf0325872037f885306b05cd7" integrity sha512-wTfvtwYJYAFL1fSs8yHIdf5GEE4NkbtbXtjLWjM3Cw8mmQKqsg8kTiqJ9NJQe5NX/5Qlo7Xd9r1yKMMkHllp5g== +esbuild-openbsd-64@0.14.31: + version "0.14.31" + resolved "https://registry.yarnpkg.com/esbuild-openbsd-64/-/esbuild-openbsd-64-0.14.31.tgz#7d2a1d46450321b0459263d3e7072e6d3924ce46" + integrity sha512-NRAAPPca05H9j9Xab0kVXK0V6/pyZGGy8d2Y8KS0BMwWEydlD4KCJDmH8/7bWCKYLRGOOCE9/GPBJyPWHFW3sg== + esbuild-sunos-64@0.13.15: version "0.13.15" resolved "https://registry.yarnpkg.com/esbuild-sunos-64/-/esbuild-sunos-64-0.13.15.tgz#d0b6454a88375ee8d3964daeff55c85c91c7cef4" integrity sha512-lbivT9Bx3t1iWWrSnGyBP9ODriEvWDRiweAs69vI+miJoeKwHWOComSRukttbuzjZ8r1q0mQJ8Z7yUsDJ3hKdw== +esbuild-sunos-64@0.14.31: + version "0.14.31" + resolved "https://registry.yarnpkg.com/esbuild-sunos-64/-/esbuild-sunos-64-0.14.31.tgz#3b3e4363951cd1dda14a14fee6d94ca426108e0c" + integrity sha512-9uA+V8w9Eehu4ldb95lPWdgCMcMO5HH6pXmfkk5usn3JsSZxKdLKsXB4hYgP80wscZvVYXJl2G+KNxsUTfPhZw== + esbuild-windows-32@0.13.15: version "0.13.15" resolved "https://registry.yarnpkg.com/esbuild-windows-32/-/esbuild-windows-32-0.13.15.tgz#c96d0b9bbb52f3303322582ef8e4847c5ad375a7" integrity sha512-fDMEf2g3SsJ599MBr50cY5ve5lP1wyVwTe6aLJsM01KtxyKkB4UT+fc5MXQFn3RLrAIAZOG+tHC+yXObpSn7Nw== +esbuild-windows-32@0.14.31: + version "0.14.31" + resolved "https://registry.yarnpkg.com/esbuild-windows-32/-/esbuild-windows-32-0.14.31.tgz#997026a41c04535bfb7c014a0458940b49145820" + integrity sha512-VGdncQTqoxD9q3v/dk0Yugbmx2FzOkcs0OemBYc1X9KXOLQYH0uQbLJIckZdZOC3J+JKSExbYFrzYCOwWPuNyA== + esbuild-windows-64@0.13.15: version "0.13.15" resolved "https://registry.yarnpkg.com/esbuild-windows-64/-/esbuild-windows-64-0.13.15.tgz#1f79cb9b1e1bb02fb25cd414cb90d4ea2892c294" integrity sha512-9aMsPRGDWCd3bGjUIKG/ZOJPKsiztlxl/Q3C1XDswO6eNX/Jtwu4M+jb6YDH9hRSUflQWX0XKAfWzgy5Wk54JQ== +esbuild-windows-64@0.14.31: + version "0.14.31" + resolved "https://registry.yarnpkg.com/esbuild-windows-64/-/esbuild-windows-64-0.14.31.tgz#5d4b0ea686c9b60036303b3415c472f2761bdafc" + integrity sha512-v/2ye5zBqpmCzi3bLCagStbNQlnOsY7WtMrD2Q0xZxeSIXONxji15KYtVee5o7nw4lXWbQSS1BL8G6BBMvtq4A== + esbuild-windows-arm64@0.13.15: version "0.13.15" resolved "https://registry.yarnpkg.com/esbuild-windows-arm64/-/esbuild-windows-arm64-0.13.15.tgz#482173070810df22a752c686509c370c3be3b3c3" integrity sha512-zzvyCVVpbwQQATaf3IG8mu1IwGEiDxKkYUdA4FpoCHi1KtPa13jeScYDjlW0Qh+ebWzpKfR2ZwvqAQkSWNcKjA== -esbuild@^0.13.12, esbuild@^0.13.2: +esbuild-windows-arm64@0.14.31: + version "0.14.31" + resolved "https://registry.yarnpkg.com/esbuild-windows-arm64/-/esbuild-windows-arm64-0.14.31.tgz#4f3b9fc34c4a33abbd0171df6cbb657ccbdbfc67" + integrity sha512-RXeU42FJoG1sriNHg73h4S+5B7L/gw+8T7U9u8IWqSSEbY6fZvBh4uofugiU1szUDqqP00GHwZ09WgYe3lGZiw== + +esbuild@^0.13.12: version "0.13.15" resolved "https://registry.yarnpkg.com/esbuild/-/esbuild-0.13.15.tgz#db56a88166ee373f87dbb2d8798ff449e0450cdf" integrity sha512-raCxt02HBKv8RJxE8vkTSCXGIyKHdEdGfUmiYb8wnabnaEmHzyW7DCHb5tEN0xU8ryqg5xw54mcwnYkC4x3AIw== @@ -613,6 +713,32 @@ esbuild@^0.13.12, esbuild@^0.13.2: esbuild-windows-64 "0.13.15" esbuild-windows-arm64 "0.13.15" +esbuild@^0.14.27: + version "0.14.31" + resolved "https://registry.yarnpkg.com/esbuild/-/esbuild-0.14.31.tgz#f7d0db114edc615f66d84972ee9fbd2b267f4029" + integrity sha512-QA0fUM13+JZzcvg1bdrhi7wo8Lr5IRHA9ypNn2znqxGqb66dSK6pAh01TjyBOhzZGazPQJZ1K26VrCAQJ715qA== + optionalDependencies: + esbuild-android-64 "0.14.31" + esbuild-android-arm64 "0.14.31" + esbuild-darwin-64 "0.14.31" + esbuild-darwin-arm64 "0.14.31" + esbuild-freebsd-64 "0.14.31" + esbuild-freebsd-arm64 "0.14.31" + esbuild-linux-32 "0.14.31" + esbuild-linux-64 "0.14.31" + esbuild-linux-arm "0.14.31" + esbuild-linux-arm64 "0.14.31" + esbuild-linux-mips64le "0.14.31" + esbuild-linux-ppc64le "0.14.31" + esbuild-linux-riscv64 "0.14.31" + esbuild-linux-s390x "0.14.31" + esbuild-netbsd-64 "0.14.31" + esbuild-openbsd-64 "0.14.31" + esbuild-sunos-64 "0.14.31" + esbuild-windows-32 "0.14.31" + esbuild-windows-64 "0.14.31" + esbuild-windows-arm64 "0.14.31" + escape-string-regexp@^1.0.3, escape-string-regexp@^1.0.5: version "1.0.5" resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4" @@ -961,10 +1087,10 @@ inherits@2: resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c" integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== -is-core-module@^2.2.0: - version "2.5.0" - resolved "https://registry.yarnpkg.com/is-core-module/-/is-core-module-2.5.0.tgz#f754843617c70bfd29b7bd87327400cda5c18491" - integrity sha512-TXCMSDsEHMEEZ6eCA8rwRDbLu55MRGmrctljsBX/2v1d9/GzqHOxW5c5oPSgrUt2vBFXebu9rGqckXGPWOlYpg== +is-core-module@^2.8.1: + version "2.8.1" + resolved "https://registry.yarnpkg.com/is-core-module/-/is-core-module-2.8.1.tgz#f59fdfca701d5879d0a6b100a40aa1560ce27211" + integrity sha512-SdNCUs284hr40hFTFP6l0IfZ/RSrMXF3qgoRHd3/79unUTvrFO/JoXwkGm+5J/Oe3E/b5GsnG330uUNgRpu1PA== dependencies: has "^1.0.3" @@ -1108,10 +1234,10 @@ ms@2.1.2: resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.2.tgz#d09d1f357b443f493382a8eb3ccd183872ae6009" integrity sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w== -nanoid@^3.1.28: - version "3.1.28" - resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.1.28.tgz#3c01bac14cb6c5680569014cc65a2f26424c6bd4" - integrity sha512-gSu9VZ2HtmoKYe/lmyPFES5nknFrHa+/DT9muUFWFMi6Jh9E1I7bkvlQ8xxf1Kos9pi9o8lBnIOkatMhKX/YUw== +nanoid@^3.3.1: + version "3.3.2" + resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.3.2.tgz#c89622fafb4381cd221421c69ec58547a1eec557" + integrity sha512-CuHBogktKwpm5g2sRgv83jEy2ijFzBwMoYA60orPDR7ynsLijJDqgsi4RDGj3OJpy3Ieb+LYwiRmIOGyytgITA== natural-compare@^1.4.0: version "1.4.0" @@ -1188,20 +1314,20 @@ path-key@^3.1.0: resolved "https://registry.yarnpkg.com/path-key/-/path-key-3.1.1.tgz#581f6ade658cbba65a0d3380de7753295054f375" integrity sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q== -path-parse@^1.0.6: - version "1.0.6" - resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.6.tgz#d62dbb5679405d72c4737ec58600e9ddcf06d24c" - integrity sha512-GSmOT2EbHrINBf9SR7CDELwlJ8AENk3Qn7OikK4nFYAu3Ote2+JYNVvkpAEQm3/TLNEJFD/xZJjzyxg3KBWOzw== +path-parse@^1.0.7: + version "1.0.7" + resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.7.tgz#fbc114b60ca42b30d9daf5858e4bd68bbedb6735" + integrity sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw== path-type@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/path-type/-/path-type-4.0.0.tgz#84ed01c0a7ba380afe09d90a8c180dcd9d03043b" integrity sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw== -picocolors@^0.2.1: - version "0.2.1" - resolved "https://registry.yarnpkg.com/picocolors/-/picocolors-0.2.1.tgz#570670f793646851d1ba135996962abad587859f" - integrity sha512-cMlDqaLEqfSaW8Z7N5Jw+lyIW869EzT73/F5lhtY9cLGoVxSXznfgfXMO0Z5K0o0Q2TkTXq+0KFsdnSe3jDViA== +picocolors@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/picocolors/-/picocolors-1.0.0.tgz#cb5bdc74ff3f51892236eaf79d68bc44564ab81c" + integrity sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ== picomatch@^2.2.3: version "2.3.0" @@ -1227,14 +1353,14 @@ postcss-value-parser@^4.2.0: resolved "https://registry.yarnpkg.com/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz#723c09920836ba6d3e5af019f92bc0971c02e514" integrity sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ== -postcss@^8.3.8: - version "8.3.9" - resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.3.9.tgz#98754caa06c4ee9eb59cc48bd073bb6bd3437c31" - integrity sha512-f/ZFyAKh9Dnqytx5X62jgjhhzttjZS7hMsohcI7HEI5tjELX/HxCy3EFhsRxyzGvrzFF+82XPvCS8T9TFleVJw== +postcss@^8.4.12: + version "8.4.12" + resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.4.12.tgz#1e7de78733b28970fa4743f7da6f3763648b1905" + integrity sha512-lg6eITwYe9v6Hr5CncVbK70SoioNQIq81nsaG86ev5hAidQvmOeETBqs7jm43K2F5/Ley3ytDtriImV6TpNiSg== dependencies: - nanoid "^3.1.28" - picocolors "^0.2.1" - source-map-js "^0.6.2" + nanoid "^3.3.1" + picocolors "^1.0.0" + source-map-js "^1.0.2" prelude-ls@^1.2.1: version "1.2.1" @@ -1291,13 +1417,14 @@ resolve-from@^4.0.0: resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-4.0.0.tgz#4abcd852ad32dd7baabfe9b40e00a36db5f392e6" integrity sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g== -resolve@^1.20.0: - version "1.20.0" - resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.20.0.tgz#629a013fb3f70755d6f0b7935cc1c2c5378b1975" - integrity sha512-wENBPt4ySzg4ybFQW2TT1zMQucPK95HSh/nq2CFTZVOGut2+pQvSsgtda4d26YrYcr067wjbmzOG8byDPBX63A== +resolve@^1.22.0: + version "1.22.0" + resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.22.0.tgz#5e0b8c67c15df57a89bdbabe603a002f21731198" + integrity sha512-Hhtrw0nLeSrFQ7phPp4OOcVjLPIeMnRlr5mcnVuMe7M/7eBn98A3hmFRLoFo3DLZkivSYwhRUJTyPyWAk56WLw== dependencies: - is-core-module "^2.2.0" - path-parse "^1.0.6" + is-core-module "^2.8.1" + path-parse "^1.0.7" + supports-preserve-symlinks-flag "^1.0.0" reusify@^1.0.4: version "1.0.4" @@ -1311,10 +1438,10 @@ rimraf@^3.0.2: dependencies: glob "^7.1.3" -rollup@^2.57.0: - version "2.58.0" - resolved "https://registry.yarnpkg.com/rollup/-/rollup-2.58.0.tgz#a643983365e7bf7f5b7c62a8331b983b7c4c67fb" - integrity sha512-NOXpusKnaRpbS7ZVSzcEXqxcLDOagN6iFS8p45RkoiMqPHDLwJm758UF05KlMoCRbLBTZsPOIa887gZJ1AiXvw== +rollup@^2.59.0: + version "2.70.1" + resolved "https://registry.yarnpkg.com/rollup/-/rollup-2.70.1.tgz#824b1f1f879ea396db30b0fc3ae8d2fead93523e" + integrity sha512-CRYsI5EuzLbXdxC6RnYhOuRdtz4bhejPMSWjsFLfVM/7w/85n2szZv6yExqUXsBdz5KT8eoubeyDUDjhLHEslA== optionalDependencies: fsevents "~2.3.2" @@ -1368,10 +1495,10 @@ slice-ansi@^4.0.0: astral-regex "^2.0.0" is-fullwidth-code-point "^3.0.0" -source-map-js@^0.6.2: - version "0.6.2" - resolved "https://registry.yarnpkg.com/source-map-js/-/source-map-js-0.6.2.tgz#0bb5de631b41cfbda6cfba8bd05a80efdfd2385e" - integrity sha512-/3GptzWzu0+0MBQFrDKzw/DvvMTUORvgY6k6jd/VS6iCR4RDTKWH6v6WPwQoUO8667uQEf9Oe38DxAYWY5F/Ug== +source-map-js@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/source-map-js/-/source-map-js-1.0.2.tgz#adbc361d9c62df380125e7f161f71c826f1e490c" + integrity sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw== source-map@~0.6.1: version "0.6.1" @@ -1418,6 +1545,11 @@ supports-color@^7.1.0: dependencies: has-flag "^4.0.0" +supports-preserve-symlinks-flag@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz#6eda4bd344a3c94aea376d4cc31bc77311039e09" + integrity sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w== + table@^6.0.9: version "6.7.1" resolved "https://registry.yarnpkg.com/table/-/table-6.7.1.tgz#ee05592b7143831a8c94f3cee6aae4c1ccef33e2" @@ -1521,15 +1653,15 @@ v8-compile-cache@^2.0.3: resolved "https://registry.yarnpkg.com/v8-compile-cache/-/v8-compile-cache-2.3.0.tgz#2de19618c66dc247dcfb6f99338035d8245a2cee" integrity sha512-l8lCEmLcLYZh4nbunNZvQCJc5pv7+RCwa8q/LdUx8u7lsWvPDKmpodJAJNwkAhJC//dFY48KuIEmjtd4RViDrA== -vite@^2.6.14: - version "2.6.14" - resolved "https://registry.yarnpkg.com/vite/-/vite-2.6.14.tgz#35c09a15e4df823410819a2a239ab11efb186271" - integrity sha512-2HA9xGyi+EhY2MXo0+A2dRsqsAG3eFNEVIo12olkWhOmc8LfiM+eMdrXf+Ruje9gdXgvSqjLI9freec1RUM5EA== +vite@^2.9.1: + version "2.9.1" + resolved "https://registry.yarnpkg.com/vite/-/vite-2.9.1.tgz#84bce95fae210a7beb566a0af06246748066b48f" + integrity sha512-vSlsSdOYGcYEJfkQ/NeLXgnRv5zZfpAsdztkIrs7AZHV8RCMZQkwjo4DS5BnrYTqoWqLoUe1Cah4aVO4oNNqCQ== dependencies: - esbuild "^0.13.2" - postcss "^8.3.8" - resolve "^1.20.0" - rollup "^2.57.0" + esbuild "^0.14.27" + postcss "^8.4.12" + resolve "^1.22.0" + rollup "^2.59.0" optionalDependencies: fsevents "~2.3.2" From dd06d78a7218a36086dccfa7ae8553aa10005c3a Mon Sep 17 00:00:00 2001 From: Eric Eastwood Date: Tue, 5 Apr 2022 18:17:14 -0500 Subject: [PATCH 083/238] Avoid ERR_REQUIRE_ESM errors when requiring SDK --- package.json | 3 ++- scripts/sdk/base-manifest.json | 5 ++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/package.json b/package.json index 9b785c35..6de800e3 100644 --- a/package.json +++ b/package.json @@ -13,7 +13,8 @@ "test:postcss": "impunity --entry-point scripts/postcss/test.js ", "start": "vite --port 3000", "build": "vite build", - "build:sdk": "./scripts/sdk/build.sh" + "build:sdk": "./scripts/sdk/build.sh", + "watch:sdk": "./scripts/sdk/build.sh && yarn run vite build -c vite.sdk-lib-config.js --watch" }, "repository": { "type": "git", diff --git a/scripts/sdk/base-manifest.json b/scripts/sdk/base-manifest.json index fbc2b39c..da814c8d 100644 --- a/scripts/sdk/base-manifest.json +++ b/scripts/sdk/base-manifest.json @@ -2,7 +2,7 @@ "name": "hydrogen-view-sdk", "description": "Embeddable matrix client library, including view components", "version": "0.0.9", - "main": "./hydrogen.cjs.js", + "main": "./lib-build/hydrogen.cjs.js", "exports": { ".": { "import": "./lib-build/hydrogen.es.js", @@ -13,6 +13,5 @@ "./main.js": "./asset-build/assets/main.js", "./download-sandbox.html": "./asset-build/assets/download-sandbox.html", "./assets/*": "./asset-build/assets/*" - }, - "type": "module" + } } From 2401b7f453a417dc87635e43aa2339ed2517600c Mon Sep 17 00:00:00 2001 From: Eric Eastwood Date: Tue, 5 Apr 2022 19:24:27 -0500 Subject: [PATCH 084/238] Add way to test whether SDK works in ESM and CommonJS --- package.json | 1 + scripts/sdk/test/.gitignore | 3 +++ scripts/sdk/test/deps.d.ts | 2 ++ scripts/sdk/test/esm-entry.ts | 21 +++++++++++++++++++ scripts/sdk/test/index.html | 12 +++++++++++ scripts/sdk/test/package.json | 8 +++++++ scripts/sdk/test/test-sdk-in-commonjs-env.js | 13 ++++++++++++ .../test/test-sdk-in-esm-vite-build-env.js | 19 +++++++++++++++++ 8 files changed, 79 insertions(+) create mode 100644 scripts/sdk/test/.gitignore create mode 100644 scripts/sdk/test/deps.d.ts create mode 100644 scripts/sdk/test/esm-entry.ts create mode 100644 scripts/sdk/test/index.html create mode 100644 scripts/sdk/test/package.json create mode 100644 scripts/sdk/test/test-sdk-in-commonjs-env.js create mode 100644 scripts/sdk/test/test-sdk-in-esm-vite-build-env.js diff --git a/package.json b/package.json index 6de800e3..4e0b0643 100644 --- a/package.json +++ b/package.json @@ -11,6 +11,7 @@ "lint-ci": "eslint src/", "test": "impunity --entry-point src/platform/web/main.js src/platform/web/Platform.js --force-esm-dirs lib/ src/ --root-dir src/", "test:postcss": "impunity --entry-point scripts/postcss/test.js ", + "test:sdk": "cd ./scripts/sdk/test/ && yarn --no-lockfile && node test-sdk-in-esm-vite-build-env.js && node test-sdk-in-commonjs-env.js", "start": "vite --port 3000", "build": "vite build", "build:sdk": "./scripts/sdk/build.sh", diff --git a/scripts/sdk/test/.gitignore b/scripts/sdk/test/.gitignore new file mode 100644 index 00000000..cf762fe6 --- /dev/null +++ b/scripts/sdk/test/.gitignore @@ -0,0 +1,3 @@ +node_modules +dist +yarn.lock diff --git a/scripts/sdk/test/deps.d.ts b/scripts/sdk/test/deps.d.ts new file mode 100644 index 00000000..4c7d0327 --- /dev/null +++ b/scripts/sdk/test/deps.d.ts @@ -0,0 +1,2 @@ +// Keep TypeScripts from complaining about hydrogen-view-sdk not having types yet +declare module "hydrogen-view-sdk"; diff --git a/scripts/sdk/test/esm-entry.ts b/scripts/sdk/test/esm-entry.ts new file mode 100644 index 00000000..162cb6ef --- /dev/null +++ b/scripts/sdk/test/esm-entry.ts @@ -0,0 +1,21 @@ +import * as hydrogenViewSdk from "hydrogen-view-sdk"; +import downloadSandboxPath from 'hydrogen-view-sdk/download-sandbox.html?url'; +import workerPath from 'hydrogen-view-sdk/main.js?url'; +import olmWasmPath from '@matrix-org/olm/olm.wasm?url'; +import olmJsPath from '@matrix-org/olm/olm.js?url'; +import olmLegacyJsPath from '@matrix-org/olm/olm_legacy.js?url'; +const assetPaths = { + downloadSandbox: downloadSandboxPath, + worker: workerPath, + olm: { + wasm: olmWasmPath, + legacyBundle: olmLegacyJsPath, + wasmBundle: olmJsPath + } +}; +import "hydrogen-view-sdk/style.css"; + +console.log('hydrogenViewSdk', hydrogenViewSdk); +console.log('assetPaths', assetPaths); + +console.log('Entry ESM works ✅'); diff --git a/scripts/sdk/test/index.html b/scripts/sdk/test/index.html new file mode 100644 index 00000000..2ee14116 --- /dev/null +++ b/scripts/sdk/test/index.html @@ -0,0 +1,12 @@ + + + + + + Vite App + + +
+ + + diff --git a/scripts/sdk/test/package.json b/scripts/sdk/test/package.json new file mode 100644 index 00000000..a81da82c --- /dev/null +++ b/scripts/sdk/test/package.json @@ -0,0 +1,8 @@ +{ + "name": "test-sdk", + "version": "0.0.0", + "description": "", + "dependencies": { + "hydrogen-view-sdk": "link:../../../target" + } +} diff --git a/scripts/sdk/test/test-sdk-in-commonjs-env.js b/scripts/sdk/test/test-sdk-in-commonjs-env.js new file mode 100644 index 00000000..fc7245f8 --- /dev/null +++ b/scripts/sdk/test/test-sdk-in-commonjs-env.js @@ -0,0 +1,13 @@ +// Make sure the SDK can be used in a CommonJS environment. +// Usage: node scripts/sdk/test/test-sdk-in-commonjs-env.js +const hydrogenViewSdk = require('hydrogen-view-sdk'); + +// Test that the "exports" are available: +// Worker +require.resolve('hydrogen-view-sdk/main.js'); +// Styles +require.resolve('hydrogen-view-sdk/style.css'); +// Can access files in the assets/* directory +require.resolve('hydrogen-view-sdk/assets/main.js'); + +console.log('SDK works in CommonJS ✅'); diff --git a/scripts/sdk/test/test-sdk-in-esm-vite-build-env.js b/scripts/sdk/test/test-sdk-in-esm-vite-build-env.js new file mode 100644 index 00000000..6fc87da7 --- /dev/null +++ b/scripts/sdk/test/test-sdk-in-esm-vite-build-env.js @@ -0,0 +1,19 @@ +const { resolve } = require('path'); +const { build } = require('vite'); + +async function main() { + await build({ + outDir: './dist', + build: { + rollupOptions: { + input: { + main: resolve(__dirname, 'index.html') + } + } + } + }); + + console.log('SDK works in Vite build ✅'); +} + +main(); From 48d0242c80fe63073277f47ab36fc7a14dfb256b Mon Sep 17 00:00:00 2001 From: RMidhunSuresh Date: Wed, 6 Apr 2022 12:23:55 +0530 Subject: [PATCH 085/238] Also derive variables in URLs --- scripts/postcss/css-compile-variables.js | 23 ++++++++++++++++++---- scripts/postcss/test.js | 25 ++++++++++++++++++++++++ 2 files changed, 44 insertions(+), 4 deletions(-) diff --git a/scripts/postcss/css-compile-variables.js b/scripts/postcss/css-compile-variables.js index 7302f8d4..fa584caa 100644 --- a/scripts/postcss/css-compile-variables.js +++ b/scripts/postcss/css-compile-variables.js @@ -43,17 +43,32 @@ function parseDeclarationValue(value) { const parsed = valueParser(value); const variables = []; parsed.walk(node => { - if (node.type !== "function" && node.value !== "var") { + if (node.type !== "function") { return; } - const variable = node.nodes[0]; - variables.push(variable.value); + switch (node.value) { + case "var": { + const variable = node.nodes[0]; + variables.push(variable.value); + break; + } + case "url": { + const url = node.nodes[0].value; + // resolve url with some absolute url so that we get the query params without using regex + const params = new URL(url, "file://foo/bar/").searchParams; + const primary = params.get("primary"); + const secondary = params.get("secondary"); + if (primary) { variables.push(primary); } + if (secondary) { variables.push(secondary); } + break; + } + } }); return variables; } function resolveDerivedVariable(decl, derive) { - const RE_VARIABLE_VALUE = /--((.+)--(.+)-(.+))/; + const RE_VARIABLE_VALUE = /(?:--)?((.+)--(.+)-(.+))/; const variableCollection = parseDeclarationValue(decl.value); for (const variable of variableCollection) { const matches = variable.match(RE_VARIABLE_VALUE); diff --git a/scripts/postcss/test.js b/scripts/postcss/test.js index 8d1412ea..cccb3ea7 100644 --- a/scripts/postcss/test.js +++ b/scripts/postcss/test.js @@ -134,6 +134,31 @@ module.exports.tests = function tests() { const actualArray = compiledVariables.get("/foo/bar")["derived-variables"]; const expectedArray = ["icon-color--darker-20", "my-alias=icon-color--darker-20", "my-alias--lighter-15"]; assert.deepStrictEqual(actualArray.sort(), expectedArray.sort()); + }, + + "derived variable are supported in urls": async (assert) => { + const inputCSS = ` + :root { + --foo-color: #ff0; + } + div { + background-color: var(--foo-color--lighter-50); + background: url("./foo/bar/icon.svg?primary=foo-color--darker-5"); + } + a { + background: url("foo/bar/icon.svg"); + }`; + const transformedColorLighter = offColor("#ff0").lighten(0.5); + const transformedColorDarker = offColor("#ff0").darken(0.05); + const outputCSS = + inputCSS + + ` + :root { + --foo-color--lighter-50: ${transformedColorLighter.hex()}; + --foo-color--darker-5: ${transformedColorDarker.hex()}; + } + `; + await run( inputCSS, outputCSS, {}, assert); } }; }; From 1f6efb4db3faa62cdf499c77c42db2d8fd86f5f0 Mon Sep 17 00:00:00 2001 From: RMidhunSuresh Date: Fri, 25 Mar 2022 11:35:27 +0530 Subject: [PATCH 086/238] Write plugin code --- .../rollup-plugin-build-themes.js | 64 +++++++++++++++++++ 1 file changed, 64 insertions(+) create mode 100644 scripts/build-plugins/rollup-plugin-build-themes.js diff --git a/scripts/build-plugins/rollup-plugin-build-themes.js b/scripts/build-plugins/rollup-plugin-build-themes.js new file mode 100644 index 00000000..dd52ef77 --- /dev/null +++ b/scripts/build-plugins/rollup-plugin-build-themes.js @@ -0,0 +1,64 @@ +/* +Copyright 2021 The Matrix.org Foundation C.I.C. + +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. +*/ + +async function readCSSSource(location) { + const fs = require("fs").promises; + const path = require("path"); + const resolvedLocation = path.resolve(__dirname, "../../", `${location}/theme.css`); + const data = await fs.readFile(resolvedLocation); + return data; +} + +async function appendVariablesToCSS(variables, cssSource) { + return cssSource + `:root{\n${Object.entries(variables).reduce((acc, [key, value]) => acc + `--${key}: ${value};\n`, "")} }\n\n`; +} + +module.exports = function buildThemes(options) { + let manifest, variants; + + return { + name: "build-themes", + enforce: "pre", + + async buildStart() { + const { manifestLocations } = options; + for (const location of manifestLocations) { + manifest = require(`${location}/manifest.json`); + variants = manifest.values.variants; + for (const [variant] of Object.entries(variants)) { + const themeName = manifest.name; + // emit the css as built theme bundle + this.emitFile({ + type: "chunk", + id: `${location}/theme.css?variant=${variant}`, + fileName: `theme-${themeName}-${variant}.css`, + }); + } + } + }, + + async load(id) { + const result = id.match(/(.+)\/theme.css\?variant=(.+)/); + if (result) { + const [, location, variant] = result; + const cssSource = await readCSSSource(location); + const config = variants[variant]; + return await appendVariablesToCSS(config.variables, cssSource); + } + return null; + }, + } +} From 32eb95734a6ca17241fda7bf577eaf9c2641cb69 Mon Sep 17 00:00:00 2001 From: RMidhunSuresh Date: Mon, 28 Mar 2022 18:02:53 +0530 Subject: [PATCH 087/238] Add default themes to index html --- .../rollup-plugin-build-themes.js | 48 +++++++++++++++++-- 1 file changed, 45 insertions(+), 3 deletions(-) diff --git a/scripts/build-plugins/rollup-plugin-build-themes.js b/scripts/build-plugins/rollup-plugin-build-themes.js index dd52ef77..770f48b7 100644 --- a/scripts/build-plugins/rollup-plugin-build-themes.js +++ b/scripts/build-plugins/rollup-plugin-build-themes.js @@ -27,7 +27,7 @@ async function appendVariablesToCSS(variables, cssSource) { } module.exports = function buildThemes(options) { - let manifest, variants; + let manifest, variants, defaultDark, defaultLight; return { name: "build-themes", @@ -38,13 +38,23 @@ module.exports = function buildThemes(options) { for (const location of manifestLocations) { manifest = require(`${location}/manifest.json`); variants = manifest.values.variants; - for (const [variant] of Object.entries(variants)) { + for (const [variant, details] of Object.entries(variants)) { const themeName = manifest.name; + const fileName = `theme-${themeName}-${variant}.css`; + if (details.default) { + // This theme is the default for when Hydrogen launches for the first time + if (details.dark) { + defaultDark = fileName; + } + else { + defaultLight = fileName; + } + } // emit the css as built theme bundle this.emitFile({ type: "chunk", id: `${location}/theme.css?variant=${variant}`, - fileName: `theme-${themeName}-${variant}.css`, + fileName, }); } } @@ -60,5 +70,37 @@ module.exports = function buildThemes(options) { } return null; }, + + transformIndexHtml(_, ctx) { + let darkThemeLocation, lightThemeLocation; + for (const [, bundle] of Object.entries(ctx.bundle)) { + if (bundle.name === defaultDark) { + darkThemeLocation = bundle.fileName; + } + if (bundle.name === defaultLight) { + lightThemeLocation = bundle.fileName; + } + } + return [ + { + tag: "link", + attrs: { + rel: "stylesheet", + type: "text/css", + media: "(prefers-color-scheme: dark)", + href: `./${darkThemeLocation}`, + } + }, + { + tag: "link", + attrs: { + rel: "stylesheet", + type: "text/css", + media: "(prefers-color-scheme: light)", + href: `./${lightThemeLocation}`, + } + }, + ]; + } } } From 86c45b5b998685febc6f444e78b6e1da70937308 Mon Sep 17 00:00:00 2001 From: RMidhunSuresh Date: Tue, 29 Mar 2022 11:46:06 +0530 Subject: [PATCH 088/238] Emit runtime bundle --- scripts/build-plugins/rollup-plugin-build-themes.js | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/scripts/build-plugins/rollup-plugin-build-themes.js b/scripts/build-plugins/rollup-plugin-build-themes.js index 770f48b7..0b9ec4bf 100644 --- a/scripts/build-plugins/rollup-plugin-build-themes.js +++ b/scripts/build-plugins/rollup-plugin-build-themes.js @@ -38,8 +38,8 @@ module.exports = function buildThemes(options) { for (const location of manifestLocations) { manifest = require(`${location}/manifest.json`); variants = manifest.values.variants; + const themeName = manifest.name; for (const [variant, details] of Object.entries(variants)) { - const themeName = manifest.name; const fileName = `theme-${themeName}-${variant}.css`; if (details.default) { // This theme is the default for when Hydrogen launches for the first time @@ -57,6 +57,12 @@ module.exports = function buildThemes(options) { fileName, }); } + // emit the css as runtime theme bundle + this.emitFile({ + type: "chunk", + id: `${location}/theme.css`, + fileName: `theme-${themeName}-runtime.css`, + }); } }, From d5b5e10230cd8d9c5272d9ef67c7bd0f7e0285b6 Mon Sep 17 00:00:00 2001 From: RMidhunSuresh Date: Fri, 1 Apr 2022 14:27:24 +0530 Subject: [PATCH 089/238] Produce manifest.jsom --- .../rollup-plugin-build-themes.js | 59 ++++++++++++++++++- 1 file changed, 58 insertions(+), 1 deletion(-) diff --git a/scripts/build-plugins/rollup-plugin-build-themes.js b/scripts/build-plugins/rollup-plugin-build-themes.js index 0b9ec4bf..cd4dc495 100644 --- a/scripts/build-plugins/rollup-plugin-build-themes.js +++ b/scripts/build-plugins/rollup-plugin-build-themes.js @@ -26,6 +26,46 @@ async function appendVariablesToCSS(variables, cssSource) { return cssSource + `:root{\n${Object.entries(variables).reduce((acc, [key, value]) => acc + `--${key}: ${value};\n`, "")} }\n\n`; } +function parseBundle(bundle) { + const chunkMap = new Map(); + const assetMap = new Map(); + let runtimeThemeChunk; + for (const [fileName, info] of Object.entries(bundle)) { + if (!fileName.endsWith(".css")) { + continue; + } + if (info.type === "asset") { + /** + * So this is the css assetInfo that contains the asset hashed file name. + * We'll store it in a separate map indexed via fileName (unhashed) to avoid + * searching through the bundle array later. + */ + assetMap.set(info.name, info); + continue; + } + if (info.facadeModuleId?.includes("type=runtime")) { + /** + * We have a separate field in manifest.source just for the runtime theme, + * so store this separately. + */ + runtimeThemeChunk = info; + continue; + } + const location = info.facadeModuleId?.match(/(.+)\/.+\.css/)?.[1]; + if (!location) { + throw new Error("Cannot find location of css chunk!"); + } + const array = chunkMap.get(location); + if (!array) { + chunkMap.set(location, [info]); + } + else { + array.push(info); + } + } + return { chunkMap, assetMap, runtimeThemeChunk }; +} + module.exports = function buildThemes(options) { let manifest, variants, defaultDark, defaultLight; @@ -60,7 +100,7 @@ module.exports = function buildThemes(options) { // emit the css as runtime theme bundle this.emitFile({ type: "chunk", - id: `${location}/theme.css`, + id: `${location}/theme.css?type=runtime`, fileName: `theme-${themeName}-runtime.css`, }); } @@ -107,6 +147,23 @@ module.exports = function buildThemes(options) { } }, ]; + }, + + generateBundle(_, bundle) { + const { assetMap, chunkMap, runtimeThemeChunk } = parseBundle(bundle); + for (const [location, chunkArray] of chunkMap) { + const manifest = require(`${location}/manifest.json`); + manifest.source = { + "built-asset": chunkArray.map(chunk => assetMap.get(chunk.fileName).fileName), + "runtime-asset": assetMap.get(runtimeThemeChunk.fileName).fileName, + }; + const name = `theme-${manifest.name}.json`; + this.emitFile({ + type: "asset", + name, + source: JSON.stringify(manifest), + }); + } } } } From b0f082e81f555c8ba2faeefb565eb6098ea261a6 Mon Sep 17 00:00:00 2001 From: RMidhunSuresh Date: Fri, 1 Apr 2022 20:27:33 +0530 Subject: [PATCH 090/238] Add derived variables to source section --- scripts/build-plugins/rollup-plugin-build-themes.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/scripts/build-plugins/rollup-plugin-build-themes.js b/scripts/build-plugins/rollup-plugin-build-themes.js index cd4dc495..b5768e8c 100644 --- a/scripts/build-plugins/rollup-plugin-build-themes.js +++ b/scripts/build-plugins/rollup-plugin-build-themes.js @@ -153,9 +153,11 @@ module.exports = function buildThemes(options) { const { assetMap, chunkMap, runtimeThemeChunk } = parseBundle(bundle); for (const [location, chunkArray] of chunkMap) { const manifest = require(`${location}/manifest.json`); + const derivedVariables = options.compiledVariables.get(location)["derived-variables"]; manifest.source = { "built-asset": chunkArray.map(chunk => assetMap.get(chunk.fileName).fileName), "runtime-asset": assetMap.get(runtimeThemeChunk.fileName).fileName, + "derived-variables": derivedVariables, }; const name = `theme-${manifest.name}.json`; this.emitFile({ From 7f9af5b5fa0ff932231c09247a043914e733890b Mon Sep 17 00:00:00 2001 From: RMidhunSuresh Date: Sun, 3 Apr 2022 16:30:35 +0530 Subject: [PATCH 091/238] Add icon to manifest --- scripts/build-plugins/rollup-plugin-build-themes.js | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/scripts/build-plugins/rollup-plugin-build-themes.js b/scripts/build-plugins/rollup-plugin-build-themes.js index b5768e8c..2355a136 100644 --- a/scripts/build-plugins/rollup-plugin-build-themes.js +++ b/scripts/build-plugins/rollup-plugin-build-themes.js @@ -153,11 +153,14 @@ module.exports = function buildThemes(options) { const { assetMap, chunkMap, runtimeThemeChunk } = parseBundle(bundle); for (const [location, chunkArray] of chunkMap) { const manifest = require(`${location}/manifest.json`); - const derivedVariables = options.compiledVariables.get(location)["derived-variables"]; + const compiledVariables = options.compiledVariables.get(location); + const derivedVariables = compiledVariables["derived-variables"]; + const icon = compiledVariables["icon"]; manifest.source = { "built-asset": chunkArray.map(chunk => assetMap.get(chunk.fileName).fileName), "runtime-asset": assetMap.get(runtimeThemeChunk.fileName).fileName, "derived-variables": derivedVariables, + "icon": icon }; const name = `theme-${manifest.name}.json`; this.emitFile({ From f75ee86c0e30288aa5af9cf1906fe76a0ccfbd2c Mon Sep 17 00:00:00 2001 From: RMidhunSuresh Date: Tue, 5 Apr 2022 16:27:23 +0530 Subject: [PATCH 092/238] Change comment --- scripts/build-plugins/rollup-plugin-build-themes.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/build-plugins/rollup-plugin-build-themes.js b/scripts/build-plugins/rollup-plugin-build-themes.js index 2355a136..74fe4daf 100644 --- a/scripts/build-plugins/rollup-plugin-build-themes.js +++ b/scripts/build-plugins/rollup-plugin-build-themes.js @@ -82,7 +82,7 @@ module.exports = function buildThemes(options) { for (const [variant, details] of Object.entries(variants)) { const fileName = `theme-${themeName}-${variant}.css`; if (details.default) { - // This theme is the default for when Hydrogen launches for the first time + // This is one of the default variants for this theme. if (details.dark) { defaultDark = fileName; } From f897e5132c29532034d7fe3d59a1dc53e02c62a3 Mon Sep 17 00:00:00 2001 From: RMidhunSuresh Date: Mon, 14 Mar 2022 12:11:46 +0530 Subject: [PATCH 093/238] Implement url to variables plugin --- package.json | 2 +- scripts/postcss/css-url-to-variables.js | 54 +++++++++++++++++++ .../css-compile-variables.test.js} | 4 +- .../tests/css-url-to-variables.test.js | 48 +++++++++++++++++ 4 files changed, 105 insertions(+), 3 deletions(-) create mode 100644 scripts/postcss/css-url-to-variables.js rename scripts/postcss/{test.js => tests/css-compile-variables.test.js} (98%) create mode 100644 scripts/postcss/tests/css-url-to-variables.test.js diff --git a/package.json b/package.json index d506d872..af863ced 100644 --- a/package.json +++ b/package.json @@ -10,7 +10,7 @@ "lint-ts": "eslint src/ -c .ts-eslintrc.js --ext .ts", "lint-ci": "eslint src/", "test": "impunity --entry-point src/platform/web/main.js src/platform/web/Platform.js --force-esm-dirs lib/ src/ --root-dir src/", - "test:postcss": "impunity --entry-point scripts/postcss/test.js ", + "test:postcss": "impunity --entry-point scripts/postcss/tests/css-compile-variables.test.js scripts/postcss/tests/css-url-to-variables.test.js", "start": "vite --port 3000", "build": "vite build", "build:sdk": "./scripts/sdk/build.sh" diff --git a/scripts/postcss/css-url-to-variables.js b/scripts/postcss/css-url-to-variables.js new file mode 100644 index 00000000..a9370117 --- /dev/null +++ b/scripts/postcss/css-url-to-variables.js @@ -0,0 +1,54 @@ +const valueParser = require("postcss-value-parser"); +let counter = 0; +const variableMap = new Map(); +const format = "icon-url" + +function extractUrl(decl) { + const value = decl.value; + const parsed = valueParser(value); + const variables = []; + parsed.walk(node => { + if (node.type !== "function" || node.value !== "url") { + return; + } + const urlStringNode = node.nodes[0]; + const variableName = `--${format}-${counter++}`; + variableMap.set(variableName, `"${urlStringNode.value}"`); + const varNode = { + type: "function", + value: "var", + nodes: [{ type: "word", value: variableName }], + }; + //replace the url-string node with this var-node + node.nodes[0] = varNode; + }); + decl.assign({prop: decl.prop, value: parsed.toString()}) + return variables; +} + +function addResolvedVariablesToRootSelector(root, { Rule, Declaration }) { + const newRule = new Rule({ selector: ":root", source: root.source }); + // Add derived css variables to :root + variableMap.forEach((value, key) => { + const declaration = new Declaration({ prop: key, value }); + newRule.append(declaration); + }); + root.append(newRule); +} + +/* * + * @type {import('postcss').PluginCreator} + */ +module.exports = (opts = {}) => { + return { + postcssPlugin: "postcss-url-to-variable", + + Once(root, { Rule, Declaration }) { + root.walkDecls(decl => extractUrl(decl)); + addResolvedVariablesToRootSelector(root, { Rule, Declaration }); + }, + }; +}; + +module.exports.postcss = true; + diff --git a/scripts/postcss/test.js b/scripts/postcss/tests/css-compile-variables.test.js similarity index 98% rename from scripts/postcss/test.js rename to scripts/postcss/tests/css-compile-variables.test.js index cccb3ea7..945d5d4d 100644 --- a/scripts/postcss/test.js +++ b/scripts/postcss/tests/css-compile-variables.test.js @@ -16,8 +16,8 @@ limitations under the License. const offColor = require("off-color").offColor; const postcss = require("postcss"); -const plugin = require("./css-compile-variables"); -const derive = require("./color").derive; +const plugin = require("../css-compile-variables"); +const derive = require("../color").derive; async function run(input, output, opts = {}, assert) { let result = await postcss([plugin({ ...opts, derive })]).process(input, { from: undefined, }); diff --git a/scripts/postcss/tests/css-url-to-variables.test.js b/scripts/postcss/tests/css-url-to-variables.test.js new file mode 100644 index 00000000..36a38345 --- /dev/null +++ b/scripts/postcss/tests/css-url-to-variables.test.js @@ -0,0 +1,48 @@ +/* +Copyright 2021 The Matrix.org Foundation C.I.C. + +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. +*/ + +const postcss = require("postcss"); +const plugin = require("../css-url-to-variables"); + +async function run(input, output, opts = {}, assert) { + let result = await postcss([plugin(opts)]).process(input, { from: undefined, }); + assert.strictEqual( + result.css.replaceAll(/\s/g, ""), + output.replaceAll(/\s/g, "") + ); + assert.strictEqual(result.warnings().length, 0); +} + +module.exports.tests = function tests() { + return { + "url is replaced with variable": async (assert) => { + const inputCSS = `div { + background: no-repeat center/80% url("../img/image.png"); + }`; + const outputCSS = + `div { + background: no-repeat center/80% url(var(--icon-url-0)); + }`+ + ` + :root { + --icon-url-0: "../img/image.png"; + } + `; + await run( inputCSS, outputCSS, { }, assert); + }, + }; +}; + From 3ae2b4dab4d2fe1a2cba37f1832541fb938909f3 Mon Sep 17 00:00:00 2001 From: RMidhunSuresh Date: Thu, 24 Mar 2022 16:34:15 +0530 Subject: [PATCH 094/238] Use two url() in test --- scripts/postcss/tests/css-url-to-variables.test.js | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/scripts/postcss/tests/css-url-to-variables.test.js b/scripts/postcss/tests/css-url-to-variables.test.js index 36a38345..645b1775 100644 --- a/scripts/postcss/tests/css-url-to-variables.test.js +++ b/scripts/postcss/tests/css-url-to-variables.test.js @@ -31,14 +31,21 @@ module.exports.tests = function tests() { "url is replaced with variable": async (assert) => { const inputCSS = `div { background: no-repeat center/80% url("../img/image.png"); + } + button { + background: url("/home/foo/bar/cool.jpg"); }`; const outputCSS = `div { background: no-repeat center/80% url(var(--icon-url-0)); + } + button { + background: url(var(--icon-url-1)); }`+ ` :root { --icon-url-0: "../img/image.png"; + --icon-url-1: "/home/foo/bar/cool.jpg"; } `; await run( inputCSS, outputCSS, { }, assert); From cbff912476c19d9395bfd86f935bc9df80708fb4 Mon Sep 17 00:00:00 2001 From: RMidhunSuresh Date: Thu, 24 Mar 2022 16:36:01 +0530 Subject: [PATCH 095/238] Improve code quality --- scripts/postcss/css-url-to-variables.js | 35 ++++++++++++++++++------- 1 file changed, 26 insertions(+), 9 deletions(-) diff --git a/scripts/postcss/css-url-to-variables.js b/scripts/postcss/css-url-to-variables.js index a9370117..a1a47971 100644 --- a/scripts/postcss/css-url-to-variables.js +++ b/scripts/postcss/css-url-to-variables.js @@ -1,7 +1,23 @@ +/* +Copyright 2021 The Matrix.org Foundation C.I.C. + +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. +*/ + const valueParser = require("postcss-value-parser"); -let counter = 0; -const variableMap = new Map(); -const format = "icon-url" +let counter; +const urlVariables = new Map(); +const idToPrepend = "icon-url"; function extractUrl(decl) { const value = decl.value; @@ -12,14 +28,14 @@ function extractUrl(decl) { return; } const urlStringNode = node.nodes[0]; - const variableName = `--${format}-${counter++}`; - variableMap.set(variableName, `"${urlStringNode.value}"`); + const variableName = `${idToPrepend}-${counter++}`; + urlVariables.set(variableName, `"${urlStringNode.value}"`); const varNode = { type: "function", value: "var", - nodes: [{ type: "word", value: variableName }], + nodes: [{ type: "word", value: `--${variableName}` }], }; - //replace the url-string node with this var-node + // replace the url-string node with this var-node node.nodes[0] = varNode; }); decl.assign({prop: decl.prop, value: parsed.toString()}) @@ -29,8 +45,8 @@ function extractUrl(decl) { function addResolvedVariablesToRootSelector(root, { Rule, Declaration }) { const newRule = new Rule({ selector: ":root", source: root.source }); // Add derived css variables to :root - variableMap.forEach((value, key) => { - const declaration = new Declaration({ prop: key, value }); + urlVariables.forEach((value, key) => { + const declaration = new Declaration({ prop: `--${key}`, value }); newRule.append(declaration); }); root.append(newRule); @@ -40,6 +56,7 @@ function addResolvedVariablesToRootSelector(root, { Rule, Declaration }) { * @type {import('postcss').PluginCreator} */ module.exports = (opts = {}) => { + counter = 0; return { postcssPlugin: "postcss-url-to-variable", From b59d6970fcdc91359097e07cc51df1f91ca02514 Mon Sep 17 00:00:00 2001 From: RMidhunSuresh Date: Thu, 24 Mar 2022 16:52:26 +0530 Subject: [PATCH 096/238] Fix code duplication in tests --- scripts/postcss/tests/common.js | 30 +++++++++++++++++++ .../tests/css-compile-variables.test.js | 16 +++------- .../tests/css-url-to-variables.test.js | 11 +------ 3 files changed, 35 insertions(+), 22 deletions(-) create mode 100644 scripts/postcss/tests/common.js diff --git a/scripts/postcss/tests/common.js b/scripts/postcss/tests/common.js new file mode 100644 index 00000000..78ae847e --- /dev/null +++ b/scripts/postcss/tests/common.js @@ -0,0 +1,30 @@ +/* +Copyright 2021 The Matrix.org Foundation C.I.C. + +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. +*/ + +const postcss = require("postcss"); + +module.exports.createTestRunner = function (plugin) { + return async function run(input, output, opts = {}, assert) { + let result = await postcss([plugin(opts)]).process(input, { from: undefined, }); + assert.strictEqual( + result.css.replaceAll(/\s/g, ""), + output.replaceAll(/\s/g, "") + ); + assert.strictEqual(result.warnings().length, 0); + }; +} + + diff --git a/scripts/postcss/tests/css-compile-variables.test.js b/scripts/postcss/tests/css-compile-variables.test.js index 945d5d4d..e20e195f 100644 --- a/scripts/postcss/tests/css-compile-variables.test.js +++ b/scripts/postcss/tests/css-compile-variables.test.js @@ -18,15 +18,7 @@ const offColor = require("off-color").offColor; const postcss = require("postcss"); const plugin = require("../css-compile-variables"); const derive = require("../color").derive; - -async function run(input, output, opts = {}, assert) { - let result = await postcss([plugin({ ...opts, derive })]).process(input, { from: undefined, }); - assert.strictEqual( - result.css.replaceAll(/\s/g, ""), - output.replaceAll(/\s/g, "") - ); - assert.strictEqual(result.warnings().length, 0); -} +const run = require("./common").createTestRunner(plugin); module.exports.tests = function tests() { return { @@ -46,7 +38,7 @@ module.exports.tests = function tests() { --foo-color--lighter-50: ${transformedColor.hex()}; } `; - await run( inputCSS, outputCSS, {}, assert); + await run( inputCSS, outputCSS, {derive}, assert); }, "derived variables work with alias": async (assert) => { @@ -66,7 +58,7 @@ module.exports.tests = function tests() { --my-alias--lighter-15: ${aliasLighter}; } `; - await run(inputCSS, outputCSS, { }, assert); + await run(inputCSS, outputCSS, {derive}, assert); }, "derived variable throws if base not present in config": async (assert) => { @@ -94,7 +86,7 @@ module.exports.tests = function tests() { --foo-color--darker-20: ${transformedColor2.hex()}; } `; - await run( inputCSS, outputCSS, { }, assert); + await run( inputCSS, outputCSS, {derive}, assert); }, "multiple aliased-derived variable in single declaration is parsed correctly": async (assert) => { diff --git a/scripts/postcss/tests/css-url-to-variables.test.js b/scripts/postcss/tests/css-url-to-variables.test.js index 645b1775..0369995b 100644 --- a/scripts/postcss/tests/css-url-to-variables.test.js +++ b/scripts/postcss/tests/css-url-to-variables.test.js @@ -14,17 +14,8 @@ See the License for the specific language governing permissions and limitations under the License. */ -const postcss = require("postcss"); const plugin = require("../css-url-to-variables"); - -async function run(input, output, opts = {}, assert) { - let result = await postcss([plugin(opts)]).process(input, { from: undefined, }); - assert.strictEqual( - result.css.replaceAll(/\s/g, ""), - output.replaceAll(/\s/g, "") - ); - assert.strictEqual(result.warnings().length, 0); -} +const run = require("./common").createTestRunner(plugin); module.exports.tests = function tests() { return { From 97ade0659cdcad78514c5dc42deacb11170ba3ca Mon Sep 17 00:00:00 2001 From: RMidhunSuresh Date: Fri, 25 Mar 2022 11:40:21 +0530 Subject: [PATCH 097/238] Add explaining comment --- scripts/postcss/css-url-to-variables.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/scripts/postcss/css-url-to-variables.js b/scripts/postcss/css-url-to-variables.js index a1a47971..07d524c9 100644 --- a/scripts/postcss/css-url-to-variables.js +++ b/scripts/postcss/css-url-to-variables.js @@ -15,6 +15,10 @@ limitations under the License. */ const valueParser = require("postcss-value-parser"); +/** + * This plugin extracts content inside url() into css variables. + * The extracted css variables are added to the :root section. + */ let counter; const urlVariables = new Map(); const idToPrepend = "icon-url"; From 6b4bb762aa830b19e69b6160ec57169d73484cf2 Mon Sep 17 00:00:00 2001 From: RMidhunSuresh Date: Fri, 25 Mar 2022 11:40:35 +0530 Subject: [PATCH 098/238] Remove unused variable --- scripts/postcss/css-url-to-variables.js | 2 -- 1 file changed, 2 deletions(-) diff --git a/scripts/postcss/css-url-to-variables.js b/scripts/postcss/css-url-to-variables.js index 07d524c9..79814cb2 100644 --- a/scripts/postcss/css-url-to-variables.js +++ b/scripts/postcss/css-url-to-variables.js @@ -26,7 +26,6 @@ const idToPrepend = "icon-url"; function extractUrl(decl) { const value = decl.value; const parsed = valueParser(value); - const variables = []; parsed.walk(node => { if (node.type !== "function" || node.value !== "url") { return; @@ -43,7 +42,6 @@ function extractUrl(decl) { node.nodes[0] = varNode; }); decl.assign({prop: decl.prop, value: parsed.toString()}) - return variables; } function addResolvedVariablesToRootSelector(root, { Rule, Declaration }) { From 2d4ec5380e9cf578e2d0f186e0e895f50b130bf3 Mon Sep 17 00:00:00 2001 From: RMidhunSuresh Date: Fri, 25 Mar 2022 11:46:48 +0530 Subject: [PATCH 099/238] Initialize variables later --- scripts/postcss/css-url-to-variables.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/scripts/postcss/css-url-to-variables.js b/scripts/postcss/css-url-to-variables.js index 79814cb2..902e3e9d 100644 --- a/scripts/postcss/css-url-to-variables.js +++ b/scripts/postcss/css-url-to-variables.js @@ -15,12 +15,13 @@ limitations under the License. */ const valueParser = require("postcss-value-parser"); + /** * This plugin extracts content inside url() into css variables. * The extracted css variables are added to the :root section. */ let counter; -const urlVariables = new Map(); +let urlVariables; const idToPrepend = "icon-url"; function extractUrl(decl) { @@ -58,6 +59,7 @@ function addResolvedVariablesToRootSelector(root, { Rule, Declaration }) { * @type {import('postcss').PluginCreator} */ module.exports = (opts = {}) => { + urlVariables = new Map(); counter = 0; return { postcssPlugin: "postcss-url-to-variable", From f07a3ea5b5b05ca6afd9ee4df6ecbb457f60127d Mon Sep 17 00:00:00 2001 From: RMidhunSuresh Date: Fri, 25 Mar 2022 15:48:32 +0530 Subject: [PATCH 100/238] Remove css specific syntax from map --- scripts/postcss/css-url-to-variables.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/scripts/postcss/css-url-to-variables.js b/scripts/postcss/css-url-to-variables.js index 902e3e9d..dff72264 100644 --- a/scripts/postcss/css-url-to-variables.js +++ b/scripts/postcss/css-url-to-variables.js @@ -33,7 +33,7 @@ function extractUrl(decl) { } const urlStringNode = node.nodes[0]; const variableName = `${idToPrepend}-${counter++}`; - urlVariables.set(variableName, `"${urlStringNode.value}"`); + urlVariables.set(variableName, urlStringNode.value); const varNode = { type: "function", value: "var", @@ -49,7 +49,7 @@ function addResolvedVariablesToRootSelector(root, { Rule, Declaration }) { const newRule = new Rule({ selector: ":root", source: root.source }); // Add derived css variables to :root urlVariables.forEach((value, key) => { - const declaration = new Declaration({ prop: `--${key}`, value }); + const declaration = new Declaration({ prop: `--${key}`, value: `"${value}"`}); newRule.append(declaration); }); root.append(newRule); From 0a186dd11bc8fb7d14a76d7ad4a4efb753eb345b Mon Sep 17 00:00:00 2001 From: RMidhunSuresh Date: Fri, 25 Mar 2022 16:06:40 +0530 Subject: [PATCH 101/238] Fix css logic --- scripts/postcss/css-url-to-variables.js | 11 +++-------- scripts/postcss/tests/css-url-to-variables.test.js | 8 ++++---- 2 files changed, 7 insertions(+), 12 deletions(-) diff --git a/scripts/postcss/css-url-to-variables.js b/scripts/postcss/css-url-to-variables.js index dff72264..34d21bcc 100644 --- a/scripts/postcss/css-url-to-variables.js +++ b/scripts/postcss/css-url-to-variables.js @@ -34,13 +34,8 @@ function extractUrl(decl) { const urlStringNode = node.nodes[0]; const variableName = `${idToPrepend}-${counter++}`; urlVariables.set(variableName, urlStringNode.value); - const varNode = { - type: "function", - value: "var", - nodes: [{ type: "word", value: `--${variableName}` }], - }; - // replace the url-string node with this var-node - node.nodes[0] = varNode; + node.value = "var"; + node.nodes = [{ type: "word", value: `--${variableName}` }]; }); decl.assign({prop: decl.prop, value: parsed.toString()}) } @@ -49,7 +44,7 @@ function addResolvedVariablesToRootSelector(root, { Rule, Declaration }) { const newRule = new Rule({ selector: ":root", source: root.source }); // Add derived css variables to :root urlVariables.forEach((value, key) => { - const declaration = new Declaration({ prop: `--${key}`, value: `"${value}"`}); + const declaration = new Declaration({ prop: `--${key}`, value: `url("${value}")`}); newRule.append(declaration); }); root.append(newRule); diff --git a/scripts/postcss/tests/css-url-to-variables.test.js b/scripts/postcss/tests/css-url-to-variables.test.js index 0369995b..fe4d4865 100644 --- a/scripts/postcss/tests/css-url-to-variables.test.js +++ b/scripts/postcss/tests/css-url-to-variables.test.js @@ -28,15 +28,15 @@ module.exports.tests = function tests() { }`; const outputCSS = `div { - background: no-repeat center/80% url(var(--icon-url-0)); + background: no-repeat center/80% var(--icon-url-0); } button { - background: url(var(--icon-url-1)); + background: var(--icon-url-1); }`+ ` :root { - --icon-url-0: "../img/image.png"; - --icon-url-1: "/home/foo/bar/cool.jpg"; + --icon-url-0: url("../img/image.png"); + --icon-url-1: url("/home/foo/bar/cool.jpg"); } `; await run( inputCSS, outputCSS, { }, assert); From b7a47ae901c148ced1f166aba0becda5bf7db596 Mon Sep 17 00:00:00 2001 From: RMidhunSuresh Date: Fri, 25 Mar 2022 16:08:15 +0530 Subject: [PATCH 102/238] Give function better name --- scripts/postcss/css-url-to-variables.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/scripts/postcss/css-url-to-variables.js b/scripts/postcss/css-url-to-variables.js index 34d21bcc..aff8eef9 100644 --- a/scripts/postcss/css-url-to-variables.js +++ b/scripts/postcss/css-url-to-variables.js @@ -24,7 +24,7 @@ let counter; let urlVariables; const idToPrepend = "icon-url"; -function extractUrl(decl) { +function findAndReplaceUrl(decl) { const value = decl.value; const parsed = valueParser(value); parsed.walk(node => { @@ -60,7 +60,7 @@ module.exports = (opts = {}) => { postcssPlugin: "postcss-url-to-variable", Once(root, { Rule, Declaration }) { - root.walkDecls(decl => extractUrl(decl)); + root.walkDecls(decl => findAndReplaceUrl(decl)); addResolvedVariablesToRootSelector(root, { Rule, Declaration }); }, }; From 1a50effd861fdf773578f231e3ee668a01fb1744 Mon Sep 17 00:00:00 2001 From: RMidhunSuresh Date: Fri, 25 Mar 2022 18:21:03 +0530 Subject: [PATCH 103/238] Only extract into variables if file is svg --- scripts/postcss/css-url-to-variables.js | 11 ++++++++--- .../postcss/tests/css-url-to-variables.test.js | 16 +++++++++++----- 2 files changed, 19 insertions(+), 8 deletions(-) diff --git a/scripts/postcss/css-url-to-variables.js b/scripts/postcss/css-url-to-variables.js index aff8eef9..2a2937c0 100644 --- a/scripts/postcss/css-url-to-variables.js +++ b/scripts/postcss/css-url-to-variables.js @@ -31,9 +31,12 @@ function findAndReplaceUrl(decl) { if (node.type !== "function" || node.value !== "url") { return; } - const urlStringNode = node.nodes[0]; + const url = node.nodes[0].value; + if (!url.match(/\.svg\?primary=.+/)) { + return; + } const variableName = `${idToPrepend}-${counter++}`; - urlVariables.set(variableName, urlStringNode.value); + urlVariables.set(variableName, url); node.value = "var"; node.nodes = [{ type: "word", value: `--${variableName}` }]; }); @@ -61,7 +64,9 @@ module.exports = (opts = {}) => { Once(root, { Rule, Declaration }) { root.walkDecls(decl => findAndReplaceUrl(decl)); - addResolvedVariablesToRootSelector(root, { Rule, Declaration }); + if (urlVariables.size) { + addResolvedVariablesToRootSelector(root, { Rule, Declaration }); + } }, }; }; diff --git a/scripts/postcss/tests/css-url-to-variables.test.js b/scripts/postcss/tests/css-url-to-variables.test.js index fe4d4865..cc7d9cd2 100644 --- a/scripts/postcss/tests/css-url-to-variables.test.js +++ b/scripts/postcss/tests/css-url-to-variables.test.js @@ -21,10 +21,10 @@ module.exports.tests = function tests() { return { "url is replaced with variable": async (assert) => { const inputCSS = `div { - background: no-repeat center/80% url("../img/image.png"); + background: no-repeat center/80% url("../img/image.svg?primary=main-color--darker-20"); } button { - background: url("/home/foo/bar/cool.jpg"); + background: url("/home/foo/bar/cool.svg?primary=blue&secondary=green"); }`; const outputCSS = `div { @@ -35,12 +35,18 @@ module.exports.tests = function tests() { }`+ ` :root { - --icon-url-0: url("../img/image.png"); - --icon-url-1: url("/home/foo/bar/cool.jpg"); + --icon-url-0: url("../img/image.svg?primary=main-color--darker-20"); + --icon-url-1: url("/home/foo/bar/cool.svg?primary=blue&secondary=green"); } `; - await run( inputCSS, outputCSS, { }, assert); + await run(inputCSS, outputCSS, { }, assert); }, + "non svg urls without query params are not replaced": async (assert) => { + const inputCSS = `div { + background: no-repeat url("./img/foo/bar/image.png"); + }`; + await run(inputCSS, inputCSS, {}, assert); + } }; }; From cd4fce0c6ff94b53b58d820f16c8b46597535fe3 Mon Sep 17 00:00:00 2001 From: RMidhunSuresh Date: Sat, 2 Apr 2022 23:09:20 +0530 Subject: [PATCH 104/238] Populate shared map with collected icons --- scripts/postcss/css-url-to-variables.js | 18 ++++++++++++++++++ .../postcss/tests/css-url-to-variables.test.js | 16 ++++++++++++++++ 2 files changed, 34 insertions(+) diff --git a/scripts/postcss/css-url-to-variables.js b/scripts/postcss/css-url-to-variables.js index 2a2937c0..ddc65bed 100644 --- a/scripts/postcss/css-url-to-variables.js +++ b/scripts/postcss/css-url-to-variables.js @@ -53,6 +53,19 @@ function addResolvedVariablesToRootSelector(root, { Rule, Declaration }) { root.append(newRule); } +function populateMapWithDerivedVariables(map, cssFileLocation) { + const location = cssFileLocation.match(/(.+)\/.+\.css/)?.[1]; + if (map.has(location)) { + /** + * This postcss plugin is going to run on all theme variants of a single theme. + * But we only really need to populate the map once since theme variants only differ + * by the values of the base-variables and we don't care about values here. + */ + return; + } + map.set(location, { "icon": Object.fromEntries(urlVariables) }); +} + /* * * @type {import('postcss').PluginCreator} */ @@ -67,6 +80,11 @@ module.exports = (opts = {}) => { if (urlVariables.size) { addResolvedVariablesToRootSelector(root, { Rule, Declaration }); } + if (opts.compiledVariables){ + const cssFileLocation = root.source.input.from; + populateMapWithDerivedVariables(opts.compiledVariables, cssFileLocation); + } + console.log(opts.compiledVariables); }, }; }; diff --git a/scripts/postcss/tests/css-url-to-variables.test.js b/scripts/postcss/tests/css-url-to-variables.test.js index cc7d9cd2..e298599d 100644 --- a/scripts/postcss/tests/css-url-to-variables.test.js +++ b/scripts/postcss/tests/css-url-to-variables.test.js @@ -16,6 +16,7 @@ limitations under the License. const plugin = require("../css-url-to-variables"); const run = require("./common").createTestRunner(plugin); +const postcss = require("postcss"); module.exports.tests = function tests() { return { @@ -46,6 +47,21 @@ module.exports.tests = function tests() { background: no-repeat url("./img/foo/bar/image.png"); }`; await run(inputCSS, inputCSS, {}, assert); + }, + "map is populated with icons": async (assert) => { + const compiledVariables = new Map(); + const inputCSS = `div { + background: no-repeat center/80% url("../img/image.svg?primary=main-color--darker-20"); + } + button { + background: url("/home/foo/bar/cool.svg?primary=blue&secondary=green"); + }`; + const expectedObject = { + "icon-url-0": "../img/image.svg?primary=main-color--darker-20", + "icon-url-1": "/home/foo/bar/cool.svg?primary=blue&secondary=green", + }; + await postcss([plugin({compiledVariables})]).process(inputCSS, { from: "/foo/bar/test.css", }); + assert.deepEqual(expectedObject, compiledVariables.get("/foo/bar")["icon"]); } }; }; From 5e702171cebb11737a0128747bf855e3fe190b5f Mon Sep 17 00:00:00 2001 From: RMidhunSuresh Date: Sun, 3 Apr 2022 16:44:50 +0530 Subject: [PATCH 105/238] Remove console.log --- scripts/postcss/css-url-to-variables.js | 1 - 1 file changed, 1 deletion(-) diff --git a/scripts/postcss/css-url-to-variables.js b/scripts/postcss/css-url-to-variables.js index ddc65bed..6c58b093 100644 --- a/scripts/postcss/css-url-to-variables.js +++ b/scripts/postcss/css-url-to-variables.js @@ -84,7 +84,6 @@ module.exports = (opts = {}) => { const cssFileLocation = root.source.input.from; populateMapWithDerivedVariables(opts.compiledVariables, cssFileLocation); } - console.log(opts.compiledVariables); }, }; }; From 545ff2ec32f8cd654f7676fe1c7052700dbbd3af Mon Sep 17 00:00:00 2001 From: RMidhunSuresh Date: Sun, 3 Apr 2022 16:46:26 +0530 Subject: [PATCH 106/238] Add explaining comment --- scripts/postcss/css-url-to-variables.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/scripts/postcss/css-url-to-variables.js b/scripts/postcss/css-url-to-variables.js index 6c58b093..7e9f5759 100644 --- a/scripts/postcss/css-url-to-variables.js +++ b/scripts/postcss/css-url-to-variables.js @@ -17,8 +17,8 @@ limitations under the License. const valueParser = require("postcss-value-parser"); /** - * This plugin extracts content inside url() into css variables. - * The extracted css variables are added to the :root section. + * This plugin extracts content inside url() into css variables and adds the variables to the root section. + * This plugin is used in conjunction with css-url-processor plugin to colorize svg icons. */ let counter; let urlVariables; From 9a96112146a86655a04f2b3ba9b695870ce3984d Mon Sep 17 00:00:00 2001 From: RMidhunSuresh Date: Mon, 4 Apr 2022 12:02:59 +0530 Subject: [PATCH 107/238] Rename function name --- scripts/postcss/css-url-to-variables.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/scripts/postcss/css-url-to-variables.js b/scripts/postcss/css-url-to-variables.js index 7e9f5759..598ac973 100644 --- a/scripts/postcss/css-url-to-variables.js +++ b/scripts/postcss/css-url-to-variables.js @@ -53,7 +53,7 @@ function addResolvedVariablesToRootSelector(root, { Rule, Declaration }) { root.append(newRule); } -function populateMapWithDerivedVariables(map, cssFileLocation) { +function populateMapWithIcons(map, cssFileLocation) { const location = cssFileLocation.match(/(.+)\/.+\.css/)?.[1]; if (map.has(location)) { /** @@ -82,7 +82,7 @@ module.exports = (opts = {}) => { } if (opts.compiledVariables){ const cssFileLocation = root.source.input.from; - populateMapWithDerivedVariables(opts.compiledVariables, cssFileLocation); + populateMapWithIcons(opts.compiledVariables, cssFileLocation); } }, }; From 2dd655cd9ab9b4aef111061eff6ca1a3270b839b Mon Sep 17 00:00:00 2001 From: RMidhunSuresh Date: Mon, 4 Apr 2022 12:10:54 +0530 Subject: [PATCH 108/238] Check if icon is in shared var --- scripts/postcss/css-url-to-variables.js | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/scripts/postcss/css-url-to-variables.js b/scripts/postcss/css-url-to-variables.js index 598ac973..4480a2b8 100644 --- a/scripts/postcss/css-url-to-variables.js +++ b/scripts/postcss/css-url-to-variables.js @@ -55,7 +55,8 @@ function addResolvedVariablesToRootSelector(root, { Rule, Declaration }) { function populateMapWithIcons(map, cssFileLocation) { const location = cssFileLocation.match(/(.+)\/.+\.css/)?.[1]; - if (map.has(location)) { + const sharedObject = map.get(location); + if (sharedObject?.["icon"]) { /** * This postcss plugin is going to run on all theme variants of a single theme. * But we only really need to populate the map once since theme variants only differ @@ -63,7 +64,7 @@ function populateMapWithIcons(map, cssFileLocation) { */ return; } - map.set(location, { "icon": Object.fromEntries(urlVariables) }); + map.set(location, { ...sharedObject, "icon": Object.fromEntries(urlVariables) }); } /* * From 6d724e27e70cd547e5d87cde06a8165d77765ad9 Mon Sep 17 00:00:00 2001 From: RMidhunSuresh Date: Tue, 5 Apr 2022 15:26:00 +0530 Subject: [PATCH 109/238] No need to check if icons are already written --- scripts/postcss/css-url-to-variables.js | 10 +--------- scripts/postcss/tests/css-url-to-variables.test.js | 5 ++++- 2 files changed, 5 insertions(+), 10 deletions(-) diff --git a/scripts/postcss/css-url-to-variables.js b/scripts/postcss/css-url-to-variables.js index 4480a2b8..1d4666f4 100644 --- a/scripts/postcss/css-url-to-variables.js +++ b/scripts/postcss/css-url-to-variables.js @@ -56,15 +56,7 @@ function addResolvedVariablesToRootSelector(root, { Rule, Declaration }) { function populateMapWithIcons(map, cssFileLocation) { const location = cssFileLocation.match(/(.+)\/.+\.css/)?.[1]; const sharedObject = map.get(location); - if (sharedObject?.["icon"]) { - /** - * This postcss plugin is going to run on all theme variants of a single theme. - * But we only really need to populate the map once since theme variants only differ - * by the values of the base-variables and we don't care about values here. - */ - return; - } - map.set(location, { ...sharedObject, "icon": Object.fromEntries(urlVariables) }); + sharedObject["icon"] = Object.fromEntries(urlVariables); } /* * diff --git a/scripts/postcss/tests/css-url-to-variables.test.js b/scripts/postcss/tests/css-url-to-variables.test.js index e298599d..f406a38a 100644 --- a/scripts/postcss/tests/css-url-to-variables.test.js +++ b/scripts/postcss/tests/css-url-to-variables.test.js @@ -50,6 +50,7 @@ module.exports.tests = function tests() { }, "map is populated with icons": async (assert) => { const compiledVariables = new Map(); + compiledVariables.set("/foo/bar", { "derived-variables": ["background-color--darker-20", "accent-color--lighter-15"] }); const inputCSS = `div { background: no-repeat center/80% url("../img/image.svg?primary=main-color--darker-20"); } @@ -61,7 +62,9 @@ module.exports.tests = function tests() { "icon-url-1": "/home/foo/bar/cool.svg?primary=blue&secondary=green", }; await postcss([plugin({compiledVariables})]).process(inputCSS, { from: "/foo/bar/test.css", }); - assert.deepEqual(expectedObject, compiledVariables.get("/foo/bar")["icon"]); + const sharedVariable = compiledVariables.get("/foo/bar"); + assert.deepEqual(["background-color--darker-20", "accent-color--lighter-15"], sharedVariable["derived-variables"]); + assert.deepEqual(expectedObject, sharedVariable["icon"]); } }; }; From bfd73ae52a9b6cad00d7b9b4e34343495ede798d Mon Sep 17 00:00:00 2001 From: RMidhunSuresh Date: Thu, 7 Apr 2022 11:37:20 +0530 Subject: [PATCH 110/238] Pass derive function as argument --- scripts/postcss/tests/css-compile-variables.test.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/scripts/postcss/tests/css-compile-variables.test.js b/scripts/postcss/tests/css-compile-variables.test.js index e20e195f..e40751db 100644 --- a/scripts/postcss/tests/css-compile-variables.test.js +++ b/scripts/postcss/tests/css-compile-variables.test.js @@ -108,7 +108,7 @@ module.exports.tests = function tests() { --my-alias--darker-20: ${transformedColor2.hex()}; } `; - await run( inputCSS, outputCSS, { }, assert); + await run( inputCSS, outputCSS, {derive}, assert); }, "compiledVariables map is populated": async (assert) => { @@ -150,7 +150,7 @@ module.exports.tests = function tests() { --foo-color--darker-5: ${transformedColorDarker.hex()}; } `; - await run( inputCSS, outputCSS, {}, assert); + await run( inputCSS, outputCSS, {derive}, assert); } }; }; From 5d5eb93baac2d413e562f193a94304ffa6ae1c2f Mon Sep 17 00:00:00 2001 From: RMidhunSuresh Date: Fri, 25 Mar 2022 13:35:21 +0530 Subject: [PATCH 111/238] Implement plugin --- .gitignore | 3 +- scripts/postcss/css-url-processor.js | 81 ++++++++++++++++++++++++++++ scripts/postcss/svg-colorizer.js | 42 +++++++++++++++ 3 files changed, 125 insertions(+), 1 deletion(-) create mode 100644 scripts/postcss/css-url-processor.js create mode 100644 scripts/postcss/svg-colorizer.js diff --git a/.gitignore b/.gitignore index 7f6220cf..089600eb 100644 --- a/.gitignore +++ b/.gitignore @@ -7,4 +7,5 @@ bundle.js target lib *.tar.gz -.eslintcache \ No newline at end of file +.eslintcache +.tmp diff --git a/scripts/postcss/css-url-processor.js b/scripts/postcss/css-url-processor.js new file mode 100644 index 00000000..854b549f --- /dev/null +++ b/scripts/postcss/css-url-processor.js @@ -0,0 +1,81 @@ +/* +Copyright 2021 The Matrix.org Foundation C.I.C. + +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. +*/ + +const valueParser = require("postcss-value-parser"); +const resolve = require("path").resolve; + +function colorsFromURL(url, colorVariables) { + const params = new URL(`file://${url}`).searchParams; + const primary = params.get("primary"); + if (!primary) { + return null; + } + const secondary = params.get("secondary"); + const primaryColor = colorVariables[primary]; + const secondaryColor = colorVariables[secondary]; + if (!primaryColor) { + throw new Error(`Variable ${primary} not found in resolved color variables!`); + } + if (!secondaryColor) { + throw new Error(`Variable ${secondary} not found in resolved color variables!`); + } + return [primaryColor, secondaryColor]; +} + +function processURL(decl, replacer, colorVariables) { + const value = decl.value; + const parsed = valueParser(value); + parsed.walk(async node => { + if (node.type !== "function" || node.value !== "url") { + return; + } + const urlStringNode = node.nodes[0]; + const oldURL = urlStringNode.value; + const cssPath = decl.source?.input.file.replace(/[^/]*$/, ""); + const oldURLAbsolute = resolve(cssPath, oldURL); + const colors = colorsFromURL(oldURLAbsolute, colorVariables); + if (!colors) { + // If no primary color is provided via url params, then this url need not be handled. + return; + } + const newURL = replacer(oldURLAbsolute.replace(/\?.+/, ""), ...colors); + if (!newURL) { + throw new Error("Replacer failed to produce a replacement URL!"); + } + urlStringNode.value = newURL; + }); + decl.assign({prop: decl.prop, value: parsed.toString()}) +} + +/* * + * @type {import('postcss').PluginCreator} + */ +module.exports = (opts = {}) => { + return { + postcssPlugin: "postcss-url-to-variable", + + Once(root) { + /* + Go through each declaration and if it contains an URL, replace the url with the result + of running replacer(url) + */ + root.walkDecls(decl => processURL(decl, opts.replacer, opts.colors)); + }, + }; +}; + +module.exports.postcss = true; + diff --git a/scripts/postcss/svg-colorizer.js b/scripts/postcss/svg-colorizer.js new file mode 100644 index 00000000..1a659e6a --- /dev/null +++ b/scripts/postcss/svg-colorizer.js @@ -0,0 +1,42 @@ +/* +Copyright 2021 The Matrix.org Foundation C.I.C. + +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. +*/ + +const fs = require("fs"); +const path = require("path"); +/** + * Builds a new svg with the colors replaced and returns its location. + * @param {string} svgLocation The location of the input svg file + * @param {string} primaryColor Primary color for the new svg + * @param {string} secondaryColor Secondary color for the new svg + */ +module.exports.buildColorizedSVG = function (svgLocation, primaryColor, secondaryColor) { + const svgCode = fs.readFileSync(svgLocation, { encoding: "utf-8"}); + let coloredSVGCode = svgCode.replaceAll("#ff00ff", primaryColor); + coloredSVGCode = coloredSVGCode.replaceAll("#00ffff", secondaryColor); + const fileName = svgLocation.match(/.+\/(.+\.svg)/)[1]; + const outputPath = path.resolve(__dirname, "../../.tmp"); + try { + fs.mkdirSync(outputPath); + } + catch (e) { + if (e.code !== "EEXIST") { + throw e; + } + } + const outputFile = `${outputPath}/${fileName}`; + fs.writeFileSync(outputFile, coloredSVGCode); + return outputFile; +} From 8c6400ab2c16c232223c21e471213863a8622376 Mon Sep 17 00:00:00 2001 From: RMidhunSuresh Date: Fri, 25 Mar 2022 14:11:09 +0530 Subject: [PATCH 112/238] utf-8 --> utf8 --- scripts/postcss/svg-colorizer.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/postcss/svg-colorizer.js b/scripts/postcss/svg-colorizer.js index 1a659e6a..1895cfa0 100644 --- a/scripts/postcss/svg-colorizer.js +++ b/scripts/postcss/svg-colorizer.js @@ -23,7 +23,7 @@ const path = require("path"); * @param {string} secondaryColor Secondary color for the new svg */ module.exports.buildColorizedSVG = function (svgLocation, primaryColor, secondaryColor) { - const svgCode = fs.readFileSync(svgLocation, { encoding: "utf-8"}); + const svgCode = fs.readFileSync(svgLocation, { encoding: "utf8"}); let coloredSVGCode = svgCode.replaceAll("#ff00ff", primaryColor); coloredSVGCode = coloredSVGCode.replaceAll("#00ffff", secondaryColor); const fileName = svgLocation.match(/.+\/(.+\.svg)/)[1]; From 7046fcc7c74aef65256db3549cf4145c266cd077 Mon Sep 17 00:00:00 2001 From: RMidhunSuresh Date: Sun, 27 Mar 2022 20:00:43 +0530 Subject: [PATCH 113/238] Find list of resolved colors from result and also throw only if secondary color was provided --- scripts/postcss/css-url-processor.js | 25 ++++++++++++++++--------- 1 file changed, 16 insertions(+), 9 deletions(-) diff --git a/scripts/postcss/css-url-processor.js b/scripts/postcss/css-url-processor.js index 854b549f..d663ed0a 100644 --- a/scripts/postcss/css-url-processor.js +++ b/scripts/postcss/css-url-processor.js @@ -17,25 +17,25 @@ limitations under the License. const valueParser = require("postcss-value-parser"); const resolve = require("path").resolve; -function colorsFromURL(url, colorVariables) { +function colorsFromURL(url, colorMap) { const params = new URL(`file://${url}`).searchParams; const primary = params.get("primary"); if (!primary) { return null; } const secondary = params.get("secondary"); - const primaryColor = colorVariables[primary]; - const secondaryColor = colorVariables[secondary]; + const primaryColor = colorMap.get(primary); + const secondaryColor = colorMap.get(secondary); if (!primaryColor) { throw new Error(`Variable ${primary} not found in resolved color variables!`); } - if (!secondaryColor) { + if (secondary && !secondaryColor) { throw new Error(`Variable ${secondary} not found in resolved color variables!`); } return [primaryColor, secondaryColor]; } -function processURL(decl, replacer, colorVariables) { +function processURL(decl, replacer, colorMap) { const value = decl.value; const parsed = valueParser(value); parsed.walk(async node => { @@ -46,7 +46,7 @@ function processURL(decl, replacer, colorVariables) { const oldURL = urlStringNode.value; const cssPath = decl.source?.input.file.replace(/[^/]*$/, ""); const oldURLAbsolute = resolve(cssPath, oldURL); - const colors = colorsFromURL(oldURLAbsolute, colorVariables); + const colors = colorsFromURL(oldURLAbsolute, colorMap); if (!colors) { // If no primary color is provided via url params, then this url need not be handled. return; @@ -67,15 +67,22 @@ module.exports = (opts = {}) => { return { postcssPlugin: "postcss-url-to-variable", - Once(root) { + Once(root, {result}) { + /* + postcss-compile-variables should have sent the list of resolved colours down via results + */ + const {colorMap} = result.messages.find(m => m.type === "resolved-variable-map"); + if (!colorMap) { + throw new Error("Postcss results do not contain resolved colors!"); + } /* Go through each declaration and if it contains an URL, replace the url with the result of running replacer(url) */ - root.walkDecls(decl => processURL(decl, opts.replacer, opts.colors)); + root.walkDecls(decl => processURL(decl, opts.replacer, colorMap)); + console.log("result", colorMap); }, }; }; module.exports.postcss = true; - From f2b4f2e069096a10cc487ff55eb43c4323e4d8c4 Mon Sep 17 00:00:00 2001 From: RMidhunSuresh Date: Sun, 27 Mar 2022 20:35:10 +0530 Subject: [PATCH 114/238] Remove console.log --- scripts/postcss/css-url-processor.js | 1 - 1 file changed, 1 deletion(-) diff --git a/scripts/postcss/css-url-processor.js b/scripts/postcss/css-url-processor.js index d663ed0a..1a0e4fdf 100644 --- a/scripts/postcss/css-url-processor.js +++ b/scripts/postcss/css-url-processor.js @@ -80,7 +80,6 @@ module.exports = (opts = {}) => { of running replacer(url) */ root.walkDecls(decl => processURL(decl, opts.replacer, colorMap)); - console.log("result", colorMap); }, }; }; From c0fb8a2c77bd0d446b796c0632210730b915ffb3 Mon Sep 17 00:00:00 2001 From: RMidhunSuresh Date: Wed, 6 Apr 2022 11:02:09 +0530 Subject: [PATCH 115/238] Throw error if no replacements were made --- scripts/postcss/svg-colorizer.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/scripts/postcss/svg-colorizer.js b/scripts/postcss/svg-colorizer.js index 1895cfa0..7d527ddb 100644 --- a/scripts/postcss/svg-colorizer.js +++ b/scripts/postcss/svg-colorizer.js @@ -26,6 +26,9 @@ module.exports.buildColorizedSVG = function (svgLocation, primaryColor, secondar const svgCode = fs.readFileSync(svgLocation, { encoding: "utf8"}); let coloredSVGCode = svgCode.replaceAll("#ff00ff", primaryColor); coloredSVGCode = coloredSVGCode.replaceAll("#00ffff", secondaryColor); + if (svgCode === coloredSVGCode) { + throw new Error("svg-colorizer made no color replacements! The input svg should only contain colors #ff00ff (primary, case-sensitive) and #00ffff (secondary, case-sensitive)."); + } const fileName = svgLocation.match(/.+\/(.+\.svg)/)[1]; const outputPath = path.resolve(__dirname, "../../.tmp"); try { From 9755062563fb4697812ee5a55b99181615ecf3bf Mon Sep 17 00:00:00 2001 From: Bruno Windels <274386+bwindels@users.noreply.github.com> Date: Thu, 7 Apr 2022 10:33:12 +0200 Subject: [PATCH 116/238] fix error thrown during request when response code is not used --- src/matrix/net/RequestScheduler.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/matrix/net/RequestScheduler.ts b/src/matrix/net/RequestScheduler.ts index dc5c501b..c6e546a1 100644 --- a/src/matrix/net/RequestScheduler.ts +++ b/src/matrix/net/RequestScheduler.ts @@ -27,8 +27,8 @@ class Request implements IHomeServerRequest { public readonly args: any[]; private responseResolve: (result: any) => void; public responseReject: (error: Error) => void; - private responseCodeResolve: (result: any) => void; - private responseCodeReject: (result: any) => void; + private responseCodeResolve?: (result: any) => void; + private responseCodeReject?: (result: any) => void; private _requestResult?: IHomeServerRequest; private readonly _responsePromise: Promise; private _responseCodePromise: Promise; @@ -73,7 +73,7 @@ class Request implements IHomeServerRequest { const response = await this._requestResult?.response(); this.responseResolve(response); const responseCode = await this._requestResult?.responseCode(); - this.responseCodeResolve(responseCode); + this.responseCodeResolve?.(responseCode); } get requestResult() { From 6aa79cf6e22d6c465fc2c9a1644d33c38ebed948 Mon Sep 17 00:00:00 2001 From: Bruno Windels <274386+bwindels@users.noreply.github.com> Date: Thu, 7 Apr 2022 17:19:43 +0200 Subject: [PATCH 117/238] allow to inject custom tile view creator fn into timeline view --- src/lib.ts | 2 ++ src/platform/web/ui/session/room/RoomView.js | 3 ++- .../web/ui/session/room/TimelineView.ts | 23 ++++++++++++------- src/platform/web/ui/session/room/common.ts | 12 ++++------ 4 files changed, 24 insertions(+), 16 deletions(-) diff --git a/src/lib.ts b/src/lib.ts index a0ada84f..2634b0c0 100644 --- a/src/lib.ts +++ b/src/lib.ts @@ -27,6 +27,8 @@ export {RoomViewModel} from "./domain/session/room/RoomViewModel.js"; export {RoomView} from "./platform/web/ui/session/room/RoomView.js"; export {TimelineViewModel} from "./domain/session/room/timeline/TimelineViewModel.js"; export {TimelineView} from "./platform/web/ui/session/room/TimelineView"; +export {viewClassForEntry} from "./platform/web/ui/session/room/common"; +export type {TileViewConstructor, ViewClassForEntryFn} from "./platform/web/ui/session/room/TimelineView"; export {Navigation} from "./domain/navigation/Navigation.js"; export {ComposerViewModel} from "./domain/session/room/ComposerViewModel.js"; export {MessageComposer} from "./platform/web/ui/session/room/MessageComposer.js"; diff --git a/src/platform/web/ui/session/room/RoomView.js b/src/platform/web/ui/session/room/RoomView.js index c172766a..2190f1f1 100644 --- a/src/platform/web/ui/session/room/RoomView.js +++ b/src/platform/web/ui/session/room/RoomView.js @@ -23,6 +23,7 @@ import {TimelineLoadingView} from "./TimelineLoadingView.js"; import {MessageComposer} from "./MessageComposer.js"; import {RoomArchivedView} from "./RoomArchivedView.js"; import {AvatarView} from "../../AvatarView.js"; +import {viewClassForEntry} from "./common"; export class RoomView extends TemplateView { constructor(options) { @@ -54,7 +55,7 @@ export class RoomView extends TemplateView { t.div({className: "RoomView_error"}, vm => vm.error), t.mapView(vm => vm.timelineViewModel, timelineViewModel => { return timelineViewModel ? - new TimelineView(timelineViewModel) : + new TimelineView(timelineViewModel, viewClassForEntry) : new TimelineLoadingView(vm); // vm is just needed for i18n }), t.view(bottomView), diff --git a/src/platform/web/ui/session/room/TimelineView.ts b/src/platform/web/ui/session/room/TimelineView.ts index 936b8c7c..91ff59d5 100644 --- a/src/platform/web/ui/session/room/TimelineView.ts +++ b/src/platform/web/ui/session/room/TimelineView.ts @@ -14,9 +14,8 @@ See the License for the specific language governing permissions and limitations under the License. */ -import type {TileView} from "./common"; -import {viewClassForEntry} from "./common"; import {ListView} from "../../general/ListView"; +import type {IView} from "../../general/types"; import {TemplateView, Builder} from "../../general/TemplateView"; import {IObservableValue} from "../../general/BaseUpdateView"; import {MissingAttachmentView} from "./timeline/MissingAttachmentView.js"; @@ -25,6 +24,12 @@ import {RedactedView} from "./timeline/RedactedView.js"; import {SimpleTile} from "../../../../../domain/session/room/timeline/tiles/SimpleTile.js"; import {BaseObservableList as ObservableList} from "../../../../../observable/list/BaseObservableList"; +export interface TileView extends IView { + readonly value: SimpleTile; +} +export type TileViewConstructor = new (tile: SimpleTile) => TileView; +export type ViewClassForEntryFn = (tile: SimpleTile) => TileViewConstructor; + //import {TimelineViewModel} from "../../../../../domain/session/room/timeline/TimelineViewModel.js"; export interface TimelineViewModel extends IObservableValue { showJumpDown: boolean; @@ -55,13 +60,17 @@ export class TimelineView extends TemplateView { private tilesView?: TilesListView; private resizeObserver?: ResizeObserver; + constructor(vm: TimelineViewModel, private readonly viewClassForEntry: ViewClassForEntryFn) { + super(vm); + } + render(t: Builder, vm: TimelineViewModel) { // assume this view will be mounted in the parent DOM straight away requestAnimationFrame(() => { // do initial scroll positioning this.restoreScrollPosition(); }); - this.tilesView = new TilesListView(vm.tiles, () => this.restoreScrollPosition()); + this.tilesView = new TilesListView(vm.tiles, () => this.restoreScrollPosition(), this.viewClassForEntry); const root = t.div({className: "Timeline"}, [ t.div({ className: "Timeline_scroller bottom-aligned-scroll", @@ -174,16 +183,14 @@ class TilesListView extends ListView { private onChanged: () => void; - constructor(tiles: ObservableList, onChanged: () => void) { + constructor(tiles: ObservableList, onChanged: () => void, private readonly viewClassForEntry: ViewClassForEntryFn) { const options = { list: tiles, onItemClick: (tileView, evt) => tileView.onClick(evt), }; super(options, entry => { const View = viewClassForEntry(entry); - if (View) { - return new View(entry); - } + return new View(entry); }); this.onChanged = onChanged; } @@ -195,7 +202,7 @@ class TilesListView extends ListView { onUpdate(index: number, value: SimpleTile, param: any) { if (param === "shape") { - const ExpectedClass = viewClassForEntry(value); + const ExpectedClass = this.viewClassForEntry(value); const child = this.getChildInstanceByIndex(index); if (!ExpectedClass || !(child instanceof ExpectedClass)) { // shape was updated, so we need to recreate the tile view, diff --git a/src/platform/web/ui/session/room/common.ts b/src/platform/web/ui/session/room/common.ts index 5048211a..87997cc4 100644 --- a/src/platform/web/ui/session/room/common.ts +++ b/src/platform/web/ui/session/room/common.ts @@ -24,14 +24,10 @@ import {AnnouncementView} from "./timeline/AnnouncementView.js"; import {RedactedView} from "./timeline/RedactedView.js"; import {SimpleTile} from "../../../../../domain/session/room/timeline/tiles/SimpleTile.js"; import {GapView} from "./timeline/GapView.js"; +import type {TileViewConstructor, ViewClassForEntryFn} from "./TimelineView"; -export type TileView = GapView | AnnouncementView | TextMessageView | - ImageView | VideoView | FileView | LocationView | MissingAttachmentView | RedactedView; - -// TODO: this is what works for a ctor but doesn't actually check we constrain the returned ctors to the types above -type TileViewConstructor = (this: TileView, SimpleTile) => void; -export function viewClassForEntry(entry: SimpleTile): TileViewConstructor | undefined { - switch (entry.shape) { +export function viewClassForEntry(vm: SimpleTile): TileViewConstructor { + switch (vm.shape) { case "gap": return GapView; case "announcement": @@ -51,5 +47,7 @@ export function viewClassForEntry(entry: SimpleTile): TileViewConstructor | unde return MissingAttachmentView; case "redacted": return RedactedView; + default: + throw new Error(`Tiles of shape "${vm.shape}" are not supported, check the tilesCreator function in the view model`); } } From 220f35ae039bc9392391c5228dfb43fb4f803a9f Mon Sep 17 00:00:00 2001 From: Bruno Windels <274386+bwindels@users.noreply.github.com> Date: Fri, 8 Apr 2022 11:52:21 +0200 Subject: [PATCH 118/238] fix typescript error --- src/platform/web/ui/session/room/TimelineView.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/platform/web/ui/session/room/TimelineView.ts b/src/platform/web/ui/session/room/TimelineView.ts index 91ff59d5..0c893847 100644 --- a/src/platform/web/ui/session/room/TimelineView.ts +++ b/src/platform/web/ui/session/room/TimelineView.ts @@ -26,6 +26,7 @@ import {BaseObservableList as ObservableList} from "../../../../../observable/li export interface TileView extends IView { readonly value: SimpleTile; + onClick(event: UIEvent); } export type TileViewConstructor = new (tile: SimpleTile) => TileView; export type ViewClassForEntryFn = (tile: SimpleTile) => TileViewConstructor; @@ -184,11 +185,10 @@ class TilesListView extends ListView { private onChanged: () => void; constructor(tiles: ObservableList, onChanged: () => void, private readonly viewClassForEntry: ViewClassForEntryFn) { - const options = { + super({ list: tiles, onItemClick: (tileView, evt) => tileView.onClick(evt), - }; - super(options, entry => { + }, entry => { const View = viewClassForEntry(entry); return new View(entry); }); From 5445db2a42719d809f65d49f74ecb923dbfdad0d Mon Sep 17 00:00:00 2001 From: Bruno Windels <274386+bwindels@users.noreply.github.com> Date: Fri, 8 Apr 2022 12:52:30 +0200 Subject: [PATCH 119/238] allow injecting the tilesCreator from the Root/Session/RoomViewModel this changes the API slightly to be more future-proof, as we'll expose it in the SDK now. The function now returns a SimpleTile constructor, rather than an instance. This allows us to test if an entry would render in the timeline without creating a tile, which is something we might want in the matrix layer later on. The function is now called tileClassForEntry, analogue to what we do in TimelineView. --- src/domain/session/room/RoomViewModel.js | 20 ++-- .../room/timeline/ReactionsViewModel.js | 2 +- .../session/room/timeline/TilesCollection.js | 31 ++++-- .../room/timeline/TimelineViewModel.js | 4 +- .../room/timeline/tiles/BaseMediaTile.js | 4 +- .../room/timeline/tiles/BaseMessageTile.js | 22 +++-- .../room/timeline/tiles/BaseTextTile.js | 4 +- .../room/timeline/tiles/EncryptedEventTile.js | 4 +- .../session/room/timeline/tiles/FileTile.js | 4 +- .../session/room/timeline/tiles/GapTile.js | 10 +- .../session/room/timeline/tiles/ImageTile.js | 4 +- .../room/timeline/tiles/RoomMemberTile.js | 14 +-- .../session/room/timeline/tiles/SimpleTile.js | 4 +- .../session/room/timeline/tiles/index.ts | 94 +++++++++++++++++++ .../session/room/timeline/tilesCreator.js | 81 ---------------- src/platform/web/main.js | 3 + src/platform/web/ui/session/room/common.ts | 2 +- 17 files changed, 174 insertions(+), 133 deletions(-) create mode 100644 src/domain/session/room/timeline/tiles/index.ts delete mode 100644 src/domain/session/room/timeline/tilesCreator.js diff --git a/src/domain/session/room/RoomViewModel.js b/src/domain/session/room/RoomViewModel.js index 71060728..9c3f468e 100644 --- a/src/domain/session/room/RoomViewModel.js +++ b/src/domain/session/room/RoomViewModel.js @@ -18,17 +18,17 @@ limitations under the License. import {TimelineViewModel} from "./timeline/TimelineViewModel.js"; import {ComposerViewModel} from "./ComposerViewModel.js" import {avatarInitials, getIdentifierColorNumber, getAvatarHttpUrl} from "../../avatar"; -import {tilesCreator} from "./timeline/tilesCreator.js"; import {ViewModel} from "../../ViewModel"; import {imageToInfo} from "../common.js"; export class RoomViewModel extends ViewModel { constructor(options) { super(options); - const {room} = options; + const {room, tileClassForEntry} = options; this._room = room; this._timelineVM = null; - this._tilesCreator = null; + this._tileClassForEntry = tileClassForEntry; + this._tileOptions = undefined; this._onRoomChange = this._onRoomChange.bind(this); this._timelineError = null; this._sendError = null; @@ -46,12 +46,13 @@ export class RoomViewModel extends ViewModel { this._room.on("change", this._onRoomChange); try { const timeline = await this._room.openTimeline(); - this._tilesCreator = tilesCreator(this.childOptions({ + this._tileOptions = this.childOptions({ roomVM: this, timeline, - })); + tileClassForEntry: this._tileClassForEntry, + }); this._timelineVM = this.track(new TimelineViewModel(this.childOptions({ - tilesCreator: this._tilesCreator, + tileOptions: this._tileOptions, timeline, }))); this.emitChange("timelineViewModel"); @@ -161,7 +162,12 @@ export class RoomViewModel extends ViewModel { } _createTile(entry) { - return this._tilesCreator(entry); + if (this._tileOptions) { + const Tile = this._tileOptions.tileClassForEntry(entry); + if (Tile) { + return new Tile(entry, this._tileOptions); + } + } } async _sendMessage(message, replyingTo) { diff --git a/src/domain/session/room/timeline/ReactionsViewModel.js b/src/domain/session/room/timeline/ReactionsViewModel.js index 4f366af0..1977b6f4 100644 --- a/src/domain/session/room/timeline/ReactionsViewModel.js +++ b/src/domain/session/room/timeline/ReactionsViewModel.js @@ -222,7 +222,7 @@ export function tests() { }; const tiles = new MappedList(timeline.entries, entry => { if (entry.eventType === "m.room.message") { - return new BaseMessageTile({entry, roomVM: {room}, timeline, platform: {logger}}); + return new BaseMessageTile(entry, {roomVM: {room}, timeline, platform: {logger}}); } return null; }, (tile, params, entry) => tile?.updateEntry(entry, params, function () {})); diff --git a/src/domain/session/room/timeline/TilesCollection.js b/src/domain/session/room/timeline/TilesCollection.js index 33ae4472..75af5b09 100644 --- a/src/domain/session/room/timeline/TilesCollection.js +++ b/src/domain/session/room/timeline/TilesCollection.js @@ -18,20 +18,27 @@ import {BaseObservableList} from "../../../../observable/list/BaseObservableList import {sortedIndex} from "../../../../utils/sortedIndex"; // maps 1..n entries to 0..1 tile. Entries are what is stored in the timeline, either an event or fragmentboundary -// for now, tileCreator should be stable in whether it returns a tile or not. +// for now, tileClassForEntry should be stable in whether it returns a tile or not. // e.g. the decision to create a tile or not should be based on properties // not updated later on (e.g. event type) // also see big comment in onUpdate export class TilesCollection extends BaseObservableList { - constructor(entries, tileCreator) { + constructor(entries, tileOptions) { super(); this._entries = entries; this._tiles = null; this._entrySubscription = null; - this._tileCreator = tileCreator; + this._tileOptions = tileOptions; this._emitSpontanousUpdate = this._emitSpontanousUpdate.bind(this); } + _createTile(entry) { + const Tile = this._tileOptions.tileClassForEntry(entry); + if (Tile) { + return new Tile(entry, this._tileOptions); + } + } + _emitSpontanousUpdate(tile, params) { const entry = tile.lowerEntry; const tileIdx = this._findTileIdx(entry); @@ -48,7 +55,7 @@ export class TilesCollection extends BaseObservableList { let currentTile = null; for (let entry of this._entries) { if (!currentTile || !currentTile.tryIncludeEntry(entry)) { - currentTile = this._tileCreator(entry); + currentTile = this._createTile(entry); if (currentTile) { this._tiles.push(currentTile); } @@ -121,7 +128,7 @@ export class TilesCollection extends BaseObservableList { return; } - const newTile = this._tileCreator(entry); + const newTile = this._createTile(entry); if (newTile) { if (prevTile) { prevTile.updateNextSibling(newTile); @@ -150,9 +157,9 @@ export class TilesCollection extends BaseObservableList { const tileIdx = this._findTileIdx(entry); const tile = this._findTileAtIdx(entry, tileIdx); if (tile) { - const action = tile.updateEntry(entry, params, this._tileCreator); + const action = tile.updateEntry(entry, params); if (action.shouldReplace) { - const newTile = this._tileCreator(entry); + const newTile = this._createTile(entry); if (newTile) { this._replaceTile(tileIdx, tile, newTile, action.updateParams); newTile.setUpdateEmit(this._emitSpontanousUpdate); @@ -303,7 +310,10 @@ export function tests() { } } const entries = new ObservableArray([{n: 5}, {n: 10}]); - const tiles = new TilesCollection(entries, entry => new UpdateOnSiblingTile(entry)); + const tileOptions = { + tileClassForEntry: entry => UpdateOnSiblingTile, + }; + const tiles = new TilesCollection(entries, tileOptions); let receivedAdd = false; tiles.subscribe({ onAdd(idx, tile) { @@ -326,7 +336,10 @@ export function tests() { } } const entries = new ObservableArray([{n: 5}, {n: 10}, {n: 15}]); - const tiles = new TilesCollection(entries, entry => new UpdateOnSiblingTile(entry)); + const tileOptions = { + tileClassForEntry: entry => UpdateOnSiblingTile, + }; + const tiles = new TilesCollection(entries, tileOptions); const events = []; tiles.subscribe({ onUpdate(idx, tile) { diff --git a/src/domain/session/room/timeline/TimelineViewModel.js b/src/domain/session/room/timeline/TimelineViewModel.js index 2408146d..cf36fce4 100644 --- a/src/domain/session/room/timeline/TimelineViewModel.js +++ b/src/domain/session/room/timeline/TimelineViewModel.js @@ -37,9 +37,9 @@ import {ViewModel} from "../../../ViewModel"; export class TimelineViewModel extends ViewModel { constructor(options) { super(options); - const {timeline, tilesCreator} = options; + const {timeline, tileOptions} = options; this._timeline = this.track(timeline); - this._tiles = new TilesCollection(timeline.entries, tilesCreator); + this._tiles = new TilesCollection(timeline.entries, tileOptions); this._startTile = null; this._endTile = null; this._topLoadingPromise = null; diff --git a/src/domain/session/room/timeline/tiles/BaseMediaTile.js b/src/domain/session/room/timeline/tiles/BaseMediaTile.js index a927d766..0ba5b9a9 100644 --- a/src/domain/session/room/timeline/tiles/BaseMediaTile.js +++ b/src/domain/session/room/timeline/tiles/BaseMediaTile.js @@ -21,8 +21,8 @@ const MAX_HEIGHT = 300; const MAX_WIDTH = 400; export class BaseMediaTile extends BaseMessageTile { - constructor(options) { - super(options); + constructor(entry, options) { + super(entry, options); this._decryptedThumbnail = null; this._decryptedFile = null; this._isVisible = false; diff --git a/src/domain/session/room/timeline/tiles/BaseMessageTile.js b/src/domain/session/room/timeline/tiles/BaseMessageTile.js index 3385a587..03cc16ba 100644 --- a/src/domain/session/room/timeline/tiles/BaseMessageTile.js +++ b/src/domain/session/room/timeline/tiles/BaseMessageTile.js @@ -19,8 +19,8 @@ import {ReactionsViewModel} from "../ReactionsViewModel.js"; import {getIdentifierColorNumber, avatarInitials, getAvatarHttpUrl} from "../../../../avatar"; export class BaseMessageTile extends SimpleTile { - constructor(options) { - super(options); + constructor(entry, options) { + super(entry, options); this._date = this._entry.timestamp ? new Date(this._entry.timestamp) : null; this._isContinuation = false; this._reactions = null; @@ -28,7 +28,7 @@ export class BaseMessageTile extends SimpleTile { if (this._entry.annotations || this._entry.pendingAnnotations) { this._updateReactions(); } - this._updateReplyTileIfNeeded(options.tilesCreator, undefined); + this._updateReplyTileIfNeeded(undefined); } notifyVisible() { @@ -122,23 +122,27 @@ export class BaseMessageTile extends SimpleTile { } } - updateEntry(entry, param, tilesCreator) { - const action = super.updateEntry(entry, param, tilesCreator); + updateEntry(entry, param) { + const action = super.updateEntry(entry, param); if (action.shouldUpdate) { this._updateReactions(); } - this._updateReplyTileIfNeeded(tilesCreator, param); + this._updateReplyTileIfNeeded(param); return action; } - _updateReplyTileIfNeeded(tilesCreator, param) { + _updateReplyTileIfNeeded(param) { const replyEntry = this._entry.contextEntry; if (replyEntry) { // this is an update to contextEntry used for replyPreview - const action = this._replyTile?.updateEntry(replyEntry, param, tilesCreator); + const action = this._replyTile?.updateEntry(replyEntry, param); if (action?.shouldReplace || !this._replyTile) { this.disposeTracked(this._replyTile); - this._replyTile = tilesCreator(replyEntry); + const tileClassForEntry = this._options.tileClassForEntry; + const ReplyTile = tileClassForEntry(replyEntry); + if (ReplyTile) { + this._replyTile = new ReplyTile(replyEntry, this._options); + } } if(action?.shouldUpdate) { this._replyTile?.emitChange(); diff --git a/src/domain/session/room/timeline/tiles/BaseTextTile.js b/src/domain/session/room/timeline/tiles/BaseTextTile.js index 164443e3..8e78c95f 100644 --- a/src/domain/session/room/timeline/tiles/BaseTextTile.js +++ b/src/domain/session/room/timeline/tiles/BaseTextTile.js @@ -21,8 +21,8 @@ import {createEnum} from "../../../../../utils/enum"; export const BodyFormat = createEnum("Plain", "Html"); export class BaseTextTile extends BaseMessageTile { - constructor(options) { - super(options); + constructor(entry, options) { + super(entry, options); this._messageBody = null; this._format = null } diff --git a/src/domain/session/room/timeline/tiles/EncryptedEventTile.js b/src/domain/session/room/timeline/tiles/EncryptedEventTile.js index 50f507eb..b96e2d85 100644 --- a/src/domain/session/room/timeline/tiles/EncryptedEventTile.js +++ b/src/domain/session/room/timeline/tiles/EncryptedEventTile.js @@ -18,8 +18,8 @@ import {BaseTextTile} from "./BaseTextTile.js"; import {UpdateAction} from "../UpdateAction.js"; export class EncryptedEventTile extends BaseTextTile { - updateEntry(entry, params, tilesCreator) { - const parentResult = super.updateEntry(entry, params, tilesCreator); + updateEntry(entry, params) { + const parentResult = super.updateEntry(entry, params); // event got decrypted, recreate the tile and replace this one with it if (entry.eventType !== "m.room.encrypted") { // the "shape" parameter trigger tile recreation in TimelineView diff --git a/src/domain/session/room/timeline/tiles/FileTile.js b/src/domain/session/room/timeline/tiles/FileTile.js index 1007d28c..3f7b539b 100644 --- a/src/domain/session/room/timeline/tiles/FileTile.js +++ b/src/domain/session/room/timeline/tiles/FileTile.js @@ -20,8 +20,8 @@ import {formatSize} from "../../../../../utils/formatSize"; import {SendStatus} from "../../../../../matrix/room/sending/PendingEvent.js"; export class FileTile extends BaseMessageTile { - constructor(options) { - super(options); + constructor(entry, options) { + super(entry, options); this._downloadError = null; this._downloading = false; } diff --git a/src/domain/session/room/timeline/tiles/GapTile.js b/src/domain/session/room/timeline/tiles/GapTile.js index df0cedd9..6caa4b9b 100644 --- a/src/domain/session/room/timeline/tiles/GapTile.js +++ b/src/domain/session/room/timeline/tiles/GapTile.js @@ -18,8 +18,8 @@ import {SimpleTile} from "./SimpleTile.js"; import {UpdateAction} from "../UpdateAction.js"; export class GapTile extends SimpleTile { - constructor(options) { - super(options); + constructor(entry, options) { + super(entry, options); this._loading = false; this._error = null; this._isAtTop = true; @@ -81,8 +81,8 @@ export class GapTile extends SimpleTile { this._siblingChanged = true; } - updateEntry(entry, params, tilesCreator) { - super.updateEntry(entry, params, tilesCreator); + updateEntry(entry, params) { + super.updateEntry(entry, params); if (!entry.isGap) { return UpdateAction.Remove(); } else { @@ -125,7 +125,7 @@ export function tests() { tile.updateEntry(newEntry); } }; - const tile = new GapTile({entry: new FragmentBoundaryEntry(fragment, true), roomVM: {room}}); + const tile = new GapTile(new FragmentBoundaryEntry(fragment, true), {roomVM: {room}}); await tile.fill(); await tile.fill(); await tile.fill(); diff --git a/src/domain/session/room/timeline/tiles/ImageTile.js b/src/domain/session/room/timeline/tiles/ImageTile.js index eae2b926..dd959b28 100644 --- a/src/domain/session/room/timeline/tiles/ImageTile.js +++ b/src/domain/session/room/timeline/tiles/ImageTile.js @@ -18,8 +18,8 @@ limitations under the License. import {BaseMediaTile} from "./BaseMediaTile.js"; export class ImageTile extends BaseMediaTile { - constructor(options) { - super(options); + constructor(entry, options) { + super(entry, options); this._lightboxUrl = this.urlCreator.urlForSegments([ // ensure the right room is active if in grid view this.navigation.segment("room", this._room.id), diff --git a/src/domain/session/room/timeline/tiles/RoomMemberTile.js b/src/domain/session/room/timeline/tiles/RoomMemberTile.js index ce41f031..ca9cd9b7 100644 --- a/src/domain/session/room/timeline/tiles/RoomMemberTile.js +++ b/src/domain/session/room/timeline/tiles/RoomMemberTile.js @@ -66,23 +66,25 @@ export class RoomMemberTile extends SimpleTile { export function tests() { return { "user removes display name": (assert) => { - const tile = new RoomMemberTile({ - entry: { + const tile = new RoomMemberTile( + { prevContent: {displayname: "foo", membership: "join"}, content: {membership: "join"}, stateKey: "foo@bar.com", }, - }); + {} + ); assert.strictEqual(tile.announcement, "foo@bar.com removed their name (foo)"); }, "user without display name sets a new display name": (assert) => { - const tile = new RoomMemberTile({ - entry: { + const tile = new RoomMemberTile( + { prevContent: {membership: "join"}, content: {displayname: "foo", membership: "join" }, stateKey: "foo@bar.com", }, - }); + {} + ); assert.strictEqual(tile.announcement, "foo@bar.com changed their name to foo"); }, }; diff --git a/src/domain/session/room/timeline/tiles/SimpleTile.js b/src/domain/session/room/timeline/tiles/SimpleTile.js index af2b0e12..b8a7121e 100644 --- a/src/domain/session/room/timeline/tiles/SimpleTile.js +++ b/src/domain/session/room/timeline/tiles/SimpleTile.js @@ -19,9 +19,9 @@ import {ViewModel} from "../../../../ViewModel"; import {SendStatus} from "../../../../../matrix/room/sending/PendingEvent.js"; export class SimpleTile extends ViewModel { - constructor(options) { + constructor(entry, options) { super(options); - this._entry = options.entry; + this._entry = entry; } // view model props for all subclasses // hmmm, could also do instanceof ... ? diff --git a/src/domain/session/room/timeline/tiles/index.ts b/src/domain/session/room/timeline/tiles/index.ts new file mode 100644 index 00000000..242bea2f --- /dev/null +++ b/src/domain/session/room/timeline/tiles/index.ts @@ -0,0 +1,94 @@ +/* +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 {GapTile} from "./GapTile.js"; +import {TextTile} from "./TextTile.js"; +import {RedactedTile} from "./RedactedTile.js"; +import {ImageTile} from "./ImageTile.js"; +import {VideoTile} from "./VideoTile.js"; +import {FileTile} from "./FileTile.js"; +import {LocationTile} from "./LocationTile.js"; +import {RoomNameTile} from "./RoomNameTile.js"; +import {RoomMemberTile} from "./RoomMemberTile.js"; +import {EncryptedEventTile} from "./EncryptedEventTile.js"; +import {EncryptionEnabledTile} from "./EncryptionEnabledTile.js"; +import {MissingAttachmentTile} from "./MissingAttachmentTile.js"; + +import type {SimpleTile} from "./SimpleTile.js"; +import type {Room} from "../../../../../matrix/room/Room"; +import type {Timeline} from "../../../../../matrix/room/timeline/Timeline"; +import type {FragmentBoundaryEntry} from "../../../../../matrix/room/timeline/entries/FragmentBoundaryEntry"; +import type {EventEntry} from "../../../../../matrix/room/timeline/entries/EventEntry"; +import type {PendingEventEntry} from "../../../../../matrix/room/timeline/entries/PendingEventEntry"; +import type {Options as ViewModelOptions} from "../../../../ViewModel"; + +export type TimelineEntry = FragmentBoundaryEntry | EventEntry | PendingEventEntry; +export type TileClassForEntryFn = (entry: TimelineEntry) => TileConstructor | undefined; +export type Options = ViewModelOptions & { + room: Room, + timeline: Timeline + tileClassForEntry: TileClassForEntryFn; +}; +export type TileConstructor = new (entry: TimelineEntry, options: Options) => SimpleTile; + +export function tileClassForEntry(entry: TimelineEntry): TileConstructor | undefined { + if (entry.isGap) { + return GapTile; + } else if (entry.isPending && entry.pendingEvent.isMissingAttachments) { + return MissingAttachmentTile; + } else if (entry.eventType) { + switch (entry.eventType) { + case "m.room.message": { + if (entry.isRedacted) { + return RedactedTile; + } + const content = entry.content; + const msgtype = content && content.msgtype; + switch (msgtype) { + case "m.text": + case "m.notice": + case "m.emote": + return TextTile; + case "m.image": + return ImageTile; + case "m.video": + return VideoTile; + case "m.file": + return FileTile; + case "m.location": + return LocationTile; + default: + // unknown msgtype not rendered + return undefined; + } + } + case "m.room.name": + return RoomNameTile; + case "m.room.member": + return RoomMemberTile; + case "m.room.encrypted": + if (entry.isRedacted) { + return RedactedTile; + } + return EncryptedEventTile; + case "m.room.encryption": + return EncryptionEnabledTile; + default: + // unknown type not rendered + return undefined; + } + } +} diff --git a/src/domain/session/room/timeline/tilesCreator.js b/src/domain/session/room/timeline/tilesCreator.js deleted file mode 100644 index dc9a850e..00000000 --- a/src/domain/session/room/timeline/tilesCreator.js +++ /dev/null @@ -1,81 +0,0 @@ -/* -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 {GapTile} from "./tiles/GapTile.js"; -import {TextTile} from "./tiles/TextTile.js"; -import {RedactedTile} from "./tiles/RedactedTile.js"; -import {ImageTile} from "./tiles/ImageTile.js"; -import {VideoTile} from "./tiles/VideoTile.js"; -import {FileTile} from "./tiles/FileTile.js"; -import {LocationTile} from "./tiles/LocationTile.js"; -import {RoomNameTile} from "./tiles/RoomNameTile.js"; -import {RoomMemberTile} from "./tiles/RoomMemberTile.js"; -import {EncryptedEventTile} from "./tiles/EncryptedEventTile.js"; -import {EncryptionEnabledTile} from "./tiles/EncryptionEnabledTile.js"; -import {MissingAttachmentTile} from "./tiles/MissingAttachmentTile.js"; - -export function tilesCreator(baseOptions) { - const tilesCreator = function tilesCreator(entry, emitUpdate) { - const options = Object.assign({entry, emitUpdate, tilesCreator}, baseOptions); - if (entry.isGap) { - return new GapTile(options); - } else if (entry.isPending && entry.pendingEvent.isMissingAttachments) { - return new MissingAttachmentTile(options); - } else if (entry.eventType) { - switch (entry.eventType) { - case "m.room.message": { - if (entry.isRedacted) { - return new RedactedTile(options); - } - const content = entry.content; - const msgtype = content && content.msgtype; - switch (msgtype) { - case "m.text": - case "m.notice": - case "m.emote": - return new TextTile(options); - case "m.image": - return new ImageTile(options); - case "m.video": - return new VideoTile(options); - case "m.file": - return new FileTile(options); - case "m.location": - return new LocationTile(options); - default: - // unknown msgtype not rendered - return null; - } - } - case "m.room.name": - return new RoomNameTile(options); - case "m.room.member": - return new RoomMemberTile(options); - case "m.room.encrypted": - if (entry.isRedacted) { - return new RedactedTile(options); - } - return new EncryptedEventTile(options); - case "m.room.encryption": - return new EncryptionEnabledTile(options); - default: - // unknown type not rendered - return null; - } - } - }; - return tilesCreator; -} diff --git a/src/platform/web/main.js b/src/platform/web/main.js index 1729c17c..9e1ca85e 100644 --- a/src/platform/web/main.js +++ b/src/platform/web/main.js @@ -18,6 +18,7 @@ limitations under the License. // import {RecordRequester, ReplayRequester} from "./matrix/net/request/replay"; import {RootViewModel} from "../../domain/RootViewModel.js"; import {createNavigation, createRouter} from "../../domain/navigation/index.js"; +import {tileClassForEntry} from "../../domain/session/room/timeline/tiles/index"; // Don't use a default export here, as we use multiple entries during legacy build, // which does not support default exports, // see https://github.com/rollup/plugins/tree/master/packages/multi-entry @@ -42,6 +43,8 @@ export async function main(platform) { // so we call it that in the view models urlCreator: urlRouter, navigation, + // which tiles are supported by the timeline + tileClassForEntry }); await vm.load(); platform.createAndMountRootView(vm); diff --git a/src/platform/web/ui/session/room/common.ts b/src/platform/web/ui/session/room/common.ts index 87997cc4..201f14d0 100644 --- a/src/platform/web/ui/session/room/common.ts +++ b/src/platform/web/ui/session/room/common.ts @@ -48,6 +48,6 @@ export function viewClassForEntry(vm: SimpleTile): TileViewConstructor { case "redacted": return RedactedView; default: - throw new Error(`Tiles of shape "${vm.shape}" are not supported, check the tilesCreator function in the view model`); + throw new Error(`Tiles of shape "${vm.shape}" are not supported, check the tileClassForEntry function in the view model`); } } From a913671f0c93a1760dee5d12669b3cc13737a9d4 Mon Sep 17 00:00:00 2001 From: Bruno Windels <274386+bwindels@users.noreply.github.com> Date: Fri, 8 Apr 2022 14:19:34 +0200 Subject: [PATCH 120/238] make tileClassForEntry optional, as otherwise it is a breaking change --- src/domain/session/room/RoomViewModel.js | 5 ++++- src/platform/web/main.js | 3 --- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/domain/session/room/RoomViewModel.js b/src/domain/session/room/RoomViewModel.js index 9c3f468e..66042ae5 100644 --- a/src/domain/session/room/RoomViewModel.js +++ b/src/domain/session/room/RoomViewModel.js @@ -20,6 +20,9 @@ import {ComposerViewModel} from "./ComposerViewModel.js" import {avatarInitials, getIdentifierColorNumber, getAvatarHttpUrl} from "../../avatar"; import {ViewModel} from "../../ViewModel"; import {imageToInfo} from "../common.js"; +// TODO: remove fallback so default isn't included in bundle for SDK users that have their custom tileClassForEntry +// this is a breaking SDK change though to make this option mandatory +import {tileClassForEntry as defaultTileClassForEntry} from "./timeline/tiles/index"; export class RoomViewModel extends ViewModel { constructor(options) { @@ -27,7 +30,7 @@ export class RoomViewModel extends ViewModel { const {room, tileClassForEntry} = options; this._room = room; this._timelineVM = null; - this._tileClassForEntry = tileClassForEntry; + this._tileClassForEntry = tileClassForEntry ?? defaultTileClassForEntry; this._tileOptions = undefined; this._onRoomChange = this._onRoomChange.bind(this); this._timelineError = null; diff --git a/src/platform/web/main.js b/src/platform/web/main.js index 9e1ca85e..1729c17c 100644 --- a/src/platform/web/main.js +++ b/src/platform/web/main.js @@ -18,7 +18,6 @@ limitations under the License. // import {RecordRequester, ReplayRequester} from "./matrix/net/request/replay"; import {RootViewModel} from "../../domain/RootViewModel.js"; import {createNavigation, createRouter} from "../../domain/navigation/index.js"; -import {tileClassForEntry} from "../../domain/session/room/timeline/tiles/index"; // Don't use a default export here, as we use multiple entries during legacy build, // which does not support default exports, // see https://github.com/rollup/plugins/tree/master/packages/multi-entry @@ -43,8 +42,6 @@ export async function main(platform) { // so we call it that in the view models urlCreator: urlRouter, navigation, - // which tiles are supported by the timeline - tileClassForEntry }); await vm.load(); platform.createAndMountRootView(vm); From ac4bb8ca159fb8d353b78307df28c98fa24edcb3 Mon Sep 17 00:00:00 2001 From: Bruno Windels <274386+bwindels@users.noreply.github.com> Date: Fri, 8 Apr 2022 14:27:08 +0200 Subject: [PATCH 121/238] export tile view & view models from SDK --- src/lib.ts | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/src/lib.ts b/src/lib.ts index 2634b0c0..7f4e5316 100644 --- a/src/lib.ts +++ b/src/lib.ts @@ -26,9 +26,41 @@ export {SessionView} from "./platform/web/ui/session/SessionView.js"; export {RoomViewModel} from "./domain/session/room/RoomViewModel.js"; export {RoomView} from "./platform/web/ui/session/room/RoomView.js"; export {TimelineViewModel} from "./domain/session/room/timeline/TimelineViewModel.js"; +export {tileClassForEntry} from "./domain/session/room/timeline/tiles/index"; +export type {TimelineEntry, TileClassForEntryFn, Options, TileConstructor} from "./domain/session/room/timeline/tiles/index"; +// export timeline tile view models +export {GapTile} from "./domain/session/room/timeline/tiles/GapTile.js"; +export {TextTile} from "./domain/session/room/timeline/tiles/TextTile.js"; +export {RedactedTile} from "./domain/session/room/timeline/tiles/RedactedTile.js"; +export {ImageTile} from "./domain/session/room/timeline/tiles/ImageTile.js"; +export {VideoTile} from "./domain/session/room/timeline/tiles/VideoTile.js"; +export {FileTile} from "./domain/session/room/timeline/tiles/FileTile.js"; +export {LocationTile} from "./domain/session/room/timeline/tiles/LocationTile.js"; +export {RoomNameTile} from "./domain/session/room/timeline/tiles/RoomNameTile.js"; +export {RoomMemberTile} from "./domain/session/room/timeline/tiles/RoomMemberTile.js"; +export {EncryptedEventTile} from "./domain/session/room/timeline/tiles/EncryptedEventTile.js"; +export {EncryptionEnabledTile} from "./domain/session/room/timeline/tiles/EncryptionEnabledTile.js"; +export {MissingAttachmentTile} from "./domain/session/room/timeline/tiles/MissingAttachmentTile.js"; +export {SimpleTile} from "./domain/session/room/timeline/tiles/SimpleTile.js"; + export {TimelineView} from "./platform/web/ui/session/room/TimelineView"; export {viewClassForEntry} from "./platform/web/ui/session/room/common"; export type {TileViewConstructor, ViewClassForEntryFn} from "./platform/web/ui/session/room/TimelineView"; +// export timeline tile views +export {AnnouncementView} from "./platform/web/ui/session/room/timeline/AnnouncementView.js"; +export {BaseMediaView} from "./platform/web/ui/session/room/timeline/BaseMediaView.js"; +export {BaseMessageView} from "./platform/web/ui/session/room/timeline/BaseMessageView.js"; +export {FileView} from "./platform/web/ui/session/room/timeline/FileView.js"; +export {GapView} from "./platform/web/ui/session/room/timeline/GapView.js"; +export {ImageView} from "./platform/web/ui/session/room/timeline/ImageView.js"; +export {LocationView} from "./platform/web/ui/session/room/timeline/LocationView.js"; +export {MissingAttachmentView} from "./platform/web/ui/session/room/timeline/MissingAttachmentView.js"; +export {ReactionsView} from "./platform/web/ui/session/room/timeline/ReactionsView.js"; +export {RedactedView} from "./platform/web/ui/session/room/timeline/RedactedView.js"; +export {ReplyPreviewView} from "./platform/web/ui/session/room/timeline/ReplyPreviewView.js"; +export {TextMessageView} from "./platform/web/ui/session/room/timeline/TextMessageView.js"; +export {VideoView} from "./platform/web/ui/session/room/timeline/VideoView.js"; + export {Navigation} from "./domain/navigation/Navigation.js"; export {ComposerViewModel} from "./domain/session/room/ComposerViewModel.js"; export {MessageComposer} from "./platform/web/ui/session/room/MessageComposer.js"; From cda96a35eebe2158d4d9627281fd00ebedc66715 Mon Sep 17 00:00:00 2001 From: Bruno Windels <274386+bwindels@users.noreply.github.com> Date: Fri, 8 Apr 2022 15:01:06 +0200 Subject: [PATCH 122/238] rename viewClassForEntry to viewClassForTile --- src/lib.ts | 2 +- src/platform/web/ui/session/room/MessageComposer.js | 4 ++-- src/platform/web/ui/session/room/RoomView.js | 4 ++-- src/platform/web/ui/session/room/TimelineView.ts | 10 +++++----- src/platform/web/ui/session/room/common.ts | 2 +- .../web/ui/session/room/timeline/ReplyPreviewView.js | 4 ++-- 6 files changed, 13 insertions(+), 13 deletions(-) diff --git a/src/lib.ts b/src/lib.ts index 7f4e5316..90bf597c 100644 --- a/src/lib.ts +++ b/src/lib.ts @@ -44,7 +44,7 @@ export {MissingAttachmentTile} from "./domain/session/room/timeline/tiles/Missin export {SimpleTile} from "./domain/session/room/timeline/tiles/SimpleTile.js"; export {TimelineView} from "./platform/web/ui/session/room/TimelineView"; -export {viewClassForEntry} from "./platform/web/ui/session/room/common"; +export {viewClassForTile} from "./platform/web/ui/session/room/common"; export type {TileViewConstructor, ViewClassForEntryFn} from "./platform/web/ui/session/room/TimelineView"; // export timeline tile views export {AnnouncementView} from "./platform/web/ui/session/room/timeline/AnnouncementView.js"; diff --git a/src/platform/web/ui/session/room/MessageComposer.js b/src/platform/web/ui/session/room/MessageComposer.js index 9c67fa9f..7f822d37 100644 --- a/src/platform/web/ui/session/room/MessageComposer.js +++ b/src/platform/web/ui/session/room/MessageComposer.js @@ -17,7 +17,7 @@ limitations under the License. import {TemplateView} from "../../general/TemplateView"; import {Popup} from "../../general/Popup.js"; import {Menu} from "../../general/Menu.js"; -import {viewClassForEntry} from "./common" +import {viewClassForTile} from "./common" export class MessageComposer extends TemplateView { constructor(viewModel) { @@ -45,7 +45,7 @@ export class MessageComposer extends TemplateView { this._focusInput = () => this._input.focus(); this.value.on("focus", this._focusInput); const replyPreview = t.map(vm => vm.replyViewModel, (rvm, t) => { - const View = rvm && viewClassForEntry(rvm); + const View = rvm && viewClassForTile(rvm); if (!View) { return null; } return t.div({ className: "MessageComposer_replyPreview" diff --git a/src/platform/web/ui/session/room/RoomView.js b/src/platform/web/ui/session/room/RoomView.js index 2190f1f1..961be704 100644 --- a/src/platform/web/ui/session/room/RoomView.js +++ b/src/platform/web/ui/session/room/RoomView.js @@ -23,7 +23,7 @@ import {TimelineLoadingView} from "./TimelineLoadingView.js"; import {MessageComposer} from "./MessageComposer.js"; import {RoomArchivedView} from "./RoomArchivedView.js"; import {AvatarView} from "../../AvatarView.js"; -import {viewClassForEntry} from "./common"; +import {viewClassForTile} from "./common"; export class RoomView extends TemplateView { constructor(options) { @@ -55,7 +55,7 @@ export class RoomView extends TemplateView { t.div({className: "RoomView_error"}, vm => vm.error), t.mapView(vm => vm.timelineViewModel, timelineViewModel => { return timelineViewModel ? - new TimelineView(timelineViewModel, viewClassForEntry) : + new TimelineView(timelineViewModel, viewClassForTile) : new TimelineLoadingView(vm); // vm is just needed for i18n }), t.view(bottomView), diff --git a/src/platform/web/ui/session/room/TimelineView.ts b/src/platform/web/ui/session/room/TimelineView.ts index 0c893847..6dd52466 100644 --- a/src/platform/web/ui/session/room/TimelineView.ts +++ b/src/platform/web/ui/session/room/TimelineView.ts @@ -61,7 +61,7 @@ export class TimelineView extends TemplateView { private tilesView?: TilesListView; private resizeObserver?: ResizeObserver; - constructor(vm: TimelineViewModel, private readonly viewClassForEntry: ViewClassForEntryFn) { + constructor(vm: TimelineViewModel, private readonly viewClassForTile: ViewClassForEntryFn) { super(vm); } @@ -71,7 +71,7 @@ export class TimelineView extends TemplateView { // do initial scroll positioning this.restoreScrollPosition(); }); - this.tilesView = new TilesListView(vm.tiles, () => this.restoreScrollPosition(), this.viewClassForEntry); + this.tilesView = new TilesListView(vm.tiles, () => this.restoreScrollPosition(), this.viewClassForTile); const root = t.div({className: "Timeline"}, [ t.div({ className: "Timeline_scroller bottom-aligned-scroll", @@ -184,12 +184,12 @@ class TilesListView extends ListView { private onChanged: () => void; - constructor(tiles: ObservableList, onChanged: () => void, private readonly viewClassForEntry: ViewClassForEntryFn) { + constructor(tiles: ObservableList, onChanged: () => void, private readonly viewClassForTile: ViewClassForEntryFn) { super({ list: tiles, onItemClick: (tileView, evt) => tileView.onClick(evt), }, entry => { - const View = viewClassForEntry(entry); + const View = viewClassForTile(entry); return new View(entry); }); this.onChanged = onChanged; @@ -202,7 +202,7 @@ class TilesListView extends ListView { onUpdate(index: number, value: SimpleTile, param: any) { if (param === "shape") { - const ExpectedClass = this.viewClassForEntry(value); + const ExpectedClass = this.viewClassForTile(value); const child = this.getChildInstanceByIndex(index); if (!ExpectedClass || !(child instanceof ExpectedClass)) { // shape was updated, so we need to recreate the tile view, diff --git a/src/platform/web/ui/session/room/common.ts b/src/platform/web/ui/session/room/common.ts index 201f14d0..7b62630f 100644 --- a/src/platform/web/ui/session/room/common.ts +++ b/src/platform/web/ui/session/room/common.ts @@ -26,7 +26,7 @@ import {SimpleTile} from "../../../../../domain/session/room/timeline/tiles/Simp import {GapView} from "./timeline/GapView.js"; import type {TileViewConstructor, ViewClassForEntryFn} from "./TimelineView"; -export function viewClassForEntry(vm: SimpleTile): TileViewConstructor { +export function viewClassForTile(vm: SimpleTile): TileViewConstructor { switch (vm.shape) { case "gap": return GapView; diff --git a/src/platform/web/ui/session/room/timeline/ReplyPreviewView.js b/src/platform/web/ui/session/room/timeline/ReplyPreviewView.js index 3c52fc71..bddcc8fe 100644 --- a/src/platform/web/ui/session/room/timeline/ReplyPreviewView.js +++ b/src/platform/web/ui/session/room/timeline/ReplyPreviewView.js @@ -16,11 +16,11 @@ limitations under the License. import {renderStaticAvatar} from "../../../avatar"; import {TemplateView} from "../../../general/TemplateView"; -import {viewClassForEntry} from "../common"; +import {viewClassForTile} from "../common"; export class ReplyPreviewView extends TemplateView { render(t, vm) { - const viewClass = viewClassForEntry(vm); + const viewClass = viewClassForTile(vm); if (!viewClass) { throw new Error(`Shape ${vm.shape} is unrecognized.`) } From 57f50cc4160a7e35b4cb2df1c9299166fff6cf53 Mon Sep 17 00:00:00 2001 From: Bruno Windels <274386+bwindels@users.noreply.github.com> Date: Fri, 8 Apr 2022 15:01:27 +0200 Subject: [PATCH 123/238] fix lint warnings --- src/domain/session/room/timeline/TilesCollection.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/domain/session/room/timeline/TilesCollection.js b/src/domain/session/room/timeline/TilesCollection.js index 75af5b09..173b0cf6 100644 --- a/src/domain/session/room/timeline/TilesCollection.js +++ b/src/domain/session/room/timeline/TilesCollection.js @@ -311,7 +311,7 @@ export function tests() { } const entries = new ObservableArray([{n: 5}, {n: 10}]); const tileOptions = { - tileClassForEntry: entry => UpdateOnSiblingTile, + tileClassForEntry: () => UpdateOnSiblingTile, }; const tiles = new TilesCollection(entries, tileOptions); let receivedAdd = false; @@ -337,7 +337,7 @@ export function tests() { } const entries = new ObservableArray([{n: 5}, {n: 10}, {n: 15}]); const tileOptions = { - tileClassForEntry: entry => UpdateOnSiblingTile, + tileClassForEntry: () => UpdateOnSiblingTile, }; const tiles = new TilesCollection(entries, tileOptions); const events = []; From 1f0cb542c88e53831424155f282b01315aef88e4 Mon Sep 17 00:00:00 2001 From: Bruno Windels <274386+bwindels@users.noreply.github.com> Date: Fri, 8 Apr 2022 15:02:07 +0200 Subject: [PATCH 124/238] pass viewClassForTile to tile views, so they can create reply view with correct subtile --- src/platform/web/ui/session/room/TimelineView.ts | 12 ++++++++---- .../web/ui/session/room/timeline/BaseMessageView.js | 3 ++- .../web/ui/session/room/timeline/ReplyPreviewView.js | 11 +++++++---- .../web/ui/session/room/timeline/TextMessageView.js | 2 +- 4 files changed, 18 insertions(+), 10 deletions(-) diff --git a/src/platform/web/ui/session/room/TimelineView.ts b/src/platform/web/ui/session/room/TimelineView.ts index 6dd52466..5a04991f 100644 --- a/src/platform/web/ui/session/room/TimelineView.ts +++ b/src/platform/web/ui/session/room/TimelineView.ts @@ -28,7 +28,11 @@ export interface TileView extends IView { readonly value: SimpleTile; onClick(event: UIEvent); } -export type TileViewConstructor = new (tile: SimpleTile) => TileView; +export type TileViewConstructor = new ( + tile: SimpleTile, + viewClassForTile: ViewClassForEntryFn, + renderFlags?: { reply?: boolean, interactive?: boolean } +) => TileView; export type ViewClassForEntryFn = (tile: SimpleTile) => TileViewConstructor; //import {TimelineViewModel} from "../../../../../domain/session/room/timeline/TimelineViewModel.js"; @@ -188,9 +192,9 @@ class TilesListView extends ListView { super({ list: tiles, onItemClick: (tileView, evt) => tileView.onClick(evt), - }, entry => { - const View = viewClassForTile(entry); - return new View(entry); + }, tile => { + const TileView = viewClassForTile(tile); + return new TileView(tile, viewClassForTile); }); this.onChanged = onChanged; } diff --git a/src/platform/web/ui/session/room/timeline/BaseMessageView.js b/src/platform/web/ui/session/room/timeline/BaseMessageView.js index 7356cd2b..74b96ecf 100644 --- a/src/platform/web/ui/session/room/timeline/BaseMessageView.js +++ b/src/platform/web/ui/session/room/timeline/BaseMessageView.js @@ -24,10 +24,11 @@ import {Menu} from "../../../general/Menu.js"; import {ReactionsView} from "./ReactionsView.js"; export class BaseMessageView extends TemplateView { - constructor(value, renderFlags, tagName = "li") { + constructor(value, viewClassForTile, renderFlags, tagName = "li") { super(value); 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; } diff --git a/src/platform/web/ui/session/room/timeline/ReplyPreviewView.js b/src/platform/web/ui/session/room/timeline/ReplyPreviewView.js index bddcc8fe..219e4357 100644 --- a/src/platform/web/ui/session/room/timeline/ReplyPreviewView.js +++ b/src/platform/web/ui/session/room/timeline/ReplyPreviewView.js @@ -16,15 +16,18 @@ limitations under the License. import {renderStaticAvatar} from "../../../avatar"; import {TemplateView} from "../../../general/TemplateView"; -import {viewClassForTile} from "../common"; export class ReplyPreviewView extends TemplateView { + constructor(vm, viewClassForTile) { + super(vm); + this._viewClassForTile = viewClassForTile; + } render(t, vm) { - const viewClass = viewClassForTile(vm); - if (!viewClass) { + const TileView = this._viewClassForTile(vm); + if (!TileView) { throw new Error(`Shape ${vm.shape} is unrecognized.`) } - const view = new viewClass(vm, { reply: true, interactive: false }); + const view = new TileView(vm, this._viewClassForTile, { reply: true, interactive: false }); return t.div( { className: "ReplyPreviewView" }, t.blockquote([ diff --git a/src/platform/web/ui/session/room/timeline/TextMessageView.js b/src/platform/web/ui/session/room/timeline/TextMessageView.js index c0c0cfb0..8d6cb4dc 100644 --- a/src/platform/web/ui/session/room/timeline/TextMessageView.js +++ b/src/platform/web/ui/session/room/timeline/TextMessageView.js @@ -35,7 +35,7 @@ export class TextMessageView extends BaseMessageView { return new ReplyPreviewError(); } else if (replyTile) { - return new ReplyPreviewView(replyTile); + return new ReplyPreviewView(replyTile, this._viewClassForTile); } else { return null; From 1fea14dd10a1b830cc9be35d0077c478b2cb5a5e Mon Sep 17 00:00:00 2001 From: Bruno Windels <274386+bwindels@users.noreply.github.com> Date: Fri, 8 Apr 2022 15:04:38 +0200 Subject: [PATCH 125/238] ensure other parameters don't get passed to TemplateView parent ctors --- .../web/ui/session/room/timeline/AnnouncementView.js | 5 +++++ src/platform/web/ui/session/room/timeline/GapView.js | 5 +++++ 2 files changed, 10 insertions(+) diff --git a/src/platform/web/ui/session/room/timeline/AnnouncementView.js b/src/platform/web/ui/session/room/timeline/AnnouncementView.js index 268bf0fa..5ae92daa 100644 --- a/src/platform/web/ui/session/room/timeline/AnnouncementView.js +++ b/src/platform/web/ui/session/room/timeline/AnnouncementView.js @@ -17,6 +17,11 @@ limitations under the License. import {TemplateView} from "../../../general/TemplateView"; export class AnnouncementView extends TemplateView { + // ignore other arguments + constructor(vm) { + super(vm); + } + render(t) { return t.li({className: "AnnouncementView"}, t.div(vm => vm.announcement)); } diff --git a/src/platform/web/ui/session/room/timeline/GapView.js b/src/platform/web/ui/session/room/timeline/GapView.js index 2d3bd6e8..db6cda59 100644 --- a/src/platform/web/ui/session/room/timeline/GapView.js +++ b/src/platform/web/ui/session/room/timeline/GapView.js @@ -18,6 +18,11 @@ import {TemplateView} from "../../../general/TemplateView"; import {spinner} from "../../../common.js"; export class GapView extends TemplateView { + // ignore other argument + constructor(vm) { + super(vm); + } + render(t) { const className = { GapView: true, From d21d10e4f25afa82c7ff41658d6fc7f79e8b42fa Mon Sep 17 00:00:00 2001 From: Bruno Windels <274386+bwindels@users.noreply.github.com> Date: Fri, 8 Apr 2022 15:15:21 +0200 Subject: [PATCH 126/238] pass in viewClassForTile from SessionView so you can also use custom tiles when using the grid view --- src/platform/web/ui/session/RoomGridView.js | 7 ++++++- src/platform/web/ui/session/SessionView.js | 5 +++-- src/platform/web/ui/session/room/RoomView.js | 8 ++++---- 3 files changed, 13 insertions(+), 7 deletions(-) diff --git a/src/platform/web/ui/session/RoomGridView.js b/src/platform/web/ui/session/RoomGridView.js index 79fc3d21..65289bea 100644 --- a/src/platform/web/ui/session/RoomGridView.js +++ b/src/platform/web/ui/session/RoomGridView.js @@ -21,6 +21,11 @@ import {TemplateView} from "../general/TemplateView"; import {StaticView} from "../general/StaticView.js"; export class RoomGridView extends TemplateView { + constructor(vm, viewClassForTile) { + super(vm); + this._viewClassForTile = viewClassForTile; + } + render(t, vm) { const children = []; for (let i = 0; i < (vm.height * vm.width); i+=1) { @@ -39,7 +44,7 @@ export class RoomGridView extends TemplateView { } else if (roomVM.kind === "invite") { return new InviteView(roomVM); } else { - return new RoomView(roomVM); + return new RoomView(roomVM, this._viewClassForTile); } } else { return new StaticView(t => t.div({className: "room-placeholder"}, [ diff --git a/src/platform/web/ui/session/SessionView.js b/src/platform/web/ui/session/SessionView.js index e7cc406a..ef63b29b 100644 --- a/src/platform/web/ui/session/SessionView.js +++ b/src/platform/web/ui/session/SessionView.js @@ -28,6 +28,7 @@ import {RoomGridView} from "./RoomGridView.js"; import {SettingsView} from "./settings/SettingsView.js"; import {CreateRoomView} from "./CreateRoomView.js"; import {RightPanelView} from "./rightpanel/RightPanelView.js"; +import {viewClassForTile} from "./room/common"; export class SessionView extends TemplateView { render(t, vm) { @@ -42,7 +43,7 @@ export class SessionView extends TemplateView { t.view(new LeftPanelView(vm.leftPanelViewModel)), t.mapView(vm => vm.activeMiddleViewModel, () => { if (vm.roomGridViewModel) { - return new RoomGridView(vm.roomGridViewModel); + return new RoomGridView(vm.roomGridViewModel, viewClassForTile); } else if (vm.settingsViewModel) { return new SettingsView(vm.settingsViewModel); } else if (vm.createRoomViewModel) { @@ -51,7 +52,7 @@ export class SessionView extends TemplateView { if (vm.currentRoomViewModel.kind === "invite") { return new InviteView(vm.currentRoomViewModel); } else if (vm.currentRoomViewModel.kind === "room") { - return new RoomView(vm.currentRoomViewModel); + return new RoomView(vm.currentRoomViewModel, viewClassForTile); } else if (vm.currentRoomViewModel.kind === "roomBeingCreated") { return new RoomBeingCreatedView(vm.currentRoomViewModel); } else { diff --git a/src/platform/web/ui/session/room/RoomView.js b/src/platform/web/ui/session/room/RoomView.js index 961be704..0bc85e83 100644 --- a/src/platform/web/ui/session/room/RoomView.js +++ b/src/platform/web/ui/session/room/RoomView.js @@ -23,11 +23,11 @@ import {TimelineLoadingView} from "./TimelineLoadingView.js"; import {MessageComposer} from "./MessageComposer.js"; import {RoomArchivedView} from "./RoomArchivedView.js"; import {AvatarView} from "../../AvatarView.js"; -import {viewClassForTile} from "./common"; export class RoomView extends TemplateView { - constructor(options) { - super(options); + constructor(vm, viewClassForTile) { + super(vm); + this._viewClassForTile = viewClassForTile; this._optionsPopup = null; } @@ -55,7 +55,7 @@ export class RoomView extends TemplateView { t.div({className: "RoomView_error"}, vm => vm.error), t.mapView(vm => vm.timelineViewModel, timelineViewModel => { return timelineViewModel ? - new TimelineView(timelineViewModel, viewClassForTile) : + new TimelineView(timelineViewModel, this._viewClassForTile) : new TimelineLoadingView(vm); // vm is just needed for i18n }), t.view(bottomView), From cf780ce259bdcf7a6c74b759bc513a0f3f45e8c3 Mon Sep 17 00:00:00 2001 From: Bruno Windels <274386+bwindels@users.noreply.github.com> Date: Fri, 8 Apr 2022 15:16:22 +0200 Subject: [PATCH 127/238] also apply custom tiles in reply preview in composer --- src/platform/web/ui/session/room/MessageComposer.js | 12 ++++++------ src/platform/web/ui/session/room/RoomView.js | 2 +- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/platform/web/ui/session/room/MessageComposer.js b/src/platform/web/ui/session/room/MessageComposer.js index 7f822d37..6ce8148f 100644 --- a/src/platform/web/ui/session/room/MessageComposer.js +++ b/src/platform/web/ui/session/room/MessageComposer.js @@ -17,11 +17,11 @@ limitations under the License. import {TemplateView} from "../../general/TemplateView"; import {Popup} from "../../general/Popup.js"; import {Menu} from "../../general/Menu.js"; -import {viewClassForTile} from "./common" export class MessageComposer extends TemplateView { - constructor(viewModel) { + constructor(viewModel, viewClassForTile) { super(viewModel); + this._viewClassForTile = viewClassForTile; this._input = null; this._attachmentPopup = null; this._focusInput = null; @@ -45,8 +45,8 @@ export class MessageComposer extends TemplateView { this._focusInput = () => this._input.focus(); this.value.on("focus", this._focusInput); const replyPreview = t.map(vm => vm.replyViewModel, (rvm, t) => { - const View = rvm && viewClassForTile(rvm); - if (!View) { return null; } + const TileView = rvm && this._viewClassForTile(rvm); + if (!TileView) { return null; } return t.div({ className: "MessageComposer_replyPreview" }, [ @@ -55,8 +55,8 @@ export class MessageComposer extends TemplateView { className: "cancel", onClick: () => this._clearReplyingTo() }, "Close"), - t.view(new View(rvm, { interactive: false }, "div")) - ]) + t.view(new TileView(rvm, this._viewClassForTile, { interactive: false }, "div")) + ]); }); const input = t.div({className: "MessageComposer_input"}, [ this._input, diff --git a/src/platform/web/ui/session/room/RoomView.js b/src/platform/web/ui/session/room/RoomView.js index 0bc85e83..76e26eab 100644 --- a/src/platform/web/ui/session/room/RoomView.js +++ b/src/platform/web/ui/session/room/RoomView.js @@ -34,7 +34,7 @@ export class RoomView extends TemplateView { render(t, vm) { let bottomView; if (vm.composerViewModel.kind === "composer") { - bottomView = new MessageComposer(vm.composerViewModel); + bottomView = new MessageComposer(vm.composerViewModel, this._viewClassForTile); } else if (vm.composerViewModel.kind === "archived") { bottomView = new RoomArchivedView(vm.composerViewModel); } From a6b6fef6d2cd73a8141556308ad496a1ee7006bf Mon Sep 17 00:00:00 2001 From: Bruno Windels <274386+bwindels@users.noreply.github.com> Date: Fri, 8 Apr 2022 17:48:20 +0200 Subject: [PATCH 128/238] sdk release 0.0.10 --- scripts/sdk/base-manifest.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/sdk/base-manifest.json b/scripts/sdk/base-manifest.json index ba0e1f4f..7730bbac 100644 --- a/scripts/sdk/base-manifest.json +++ b/scripts/sdk/base-manifest.json @@ -1,7 +1,7 @@ { "name": "hydrogen-view-sdk", "description": "Embeddable matrix client library, including view components", - "version": "0.0.9", + "version": "0.0.10", "main": "./hydrogen.es.js", "type": "module" } From ff98ef44655f4c0c7dbf74c0537b289e0979ef36 Mon Sep 17 00:00:00 2001 From: RMidhunSuresh Date: Sun, 10 Apr 2022 14:49:19 +0530 Subject: [PATCH 129/238] Support theming in dev server --- .../rollup-plugin-build-themes.js | 198 ++++++++++++------ 1 file changed, 137 insertions(+), 61 deletions(-) diff --git a/scripts/build-plugins/rollup-plugin-build-themes.js b/scripts/build-plugins/rollup-plugin-build-themes.js index 74fe4daf..29fd91e1 100644 --- a/scripts/build-plugins/rollup-plugin-build-themes.js +++ b/scripts/build-plugins/rollup-plugin-build-themes.js @@ -13,17 +13,45 @@ 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. */ +const path = require('path'); async function readCSSSource(location) { const fs = require("fs").promises; const path = require("path"); - const resolvedLocation = path.resolve(__dirname, "../../", `${location}/theme.css`); + const resolvedLocation = path.resolve(__dirname, "../../", `${location}/theme.css`); const data = await fs.readFile(resolvedLocation); return data; } -async function appendVariablesToCSS(variables, cssSource) { - return cssSource + `:root{\n${Object.entries(variables).reduce((acc, [key, value]) => acc + `--${key}: ${value};\n`, "")} }\n\n`; +function getRootSectionWithVariables(variables) { + return `:root{\n${Object.entries(variables).reduce((acc, [key, value]) => acc + `--${key}: ${value};\n`, "")} }\n\n`; +} + +function appendVariablesToCSS(variables, cssSource) { + return cssSource + getRootSectionWithVariables(variables); +} + +function findLocationFromThemeName(name, locations) { + const themeLocation = locations.find(location => { + const manifest = require(`${location}/manifest.json`); + if (manifest.name === name) { + return true; + } + }); + if (!themeLocation) { + throw new Error(`Cannot find location from theme name "${name}"`); + } + return themeLocation; +} + +function findManifestFromThemeName(name, locations) { + for (const location of locations) { + const manifest = require(`${location}/manifest.json`); + if (manifest.name === name) { + return manifest; + } + } + throw new Error(`Cannot find manifest from theme name "${name}"`); } function parseBundle(bundle) { @@ -68,11 +96,20 @@ function parseBundle(bundle) { module.exports = function buildThemes(options) { let manifest, variants, defaultDark, defaultLight; + let isDevelopment = false; + const virtualModuleId = '@theme/' + const resolvedVirtualModuleId = '\0' + virtualModuleId; return { name: "build-themes", enforce: "pre", + configResolved(config) { + if (config.command === "serve") { + isDevelopment = true; + } + }, + async buildStart() { const { manifestLocations } = options; for (const location of manifestLocations) { @@ -106,69 +143,108 @@ module.exports = function buildThemes(options) { } }, + resolveId(id) { + if (id.startsWith(virtualModuleId)) { + return isDevelopment? '\0' + id: false; + } + }, + async load(id) { - const result = id.match(/(.+)\/theme.css\?variant=(.+)/); - if (result) { - const [, location, variant] = result; - const cssSource = await readCSSSource(location); - const config = variants[variant]; - return await appendVariablesToCSS(config.variables, cssSource); - } - return null; - }, - - transformIndexHtml(_, ctx) { - let darkThemeLocation, lightThemeLocation; - for (const [, bundle] of Object.entries(ctx.bundle)) { - if (bundle.name === defaultDark) { - darkThemeLocation = bundle.fileName; - } - if (bundle.name === defaultLight) { - lightThemeLocation = bundle.fileName; + if (isDevelopment) { + if (id.startsWith(resolvedVirtualModuleId)) { + let [theme, variant, file] = id.substr(resolvedVirtualModuleId.length).split("/"); + if (theme === "default") { + theme = "Element"; + } + if (!variant || variant === "default") { + variant = "light"; + } + if (!file) { + file = "index.js"; + } + switch (file) { + case "index.js": { + const location = findLocationFromThemeName(theme, options.manifestLocations); + return `import "${path.resolve(`${location}/theme.css`)}";` + + `import "@theme/${theme}/${variant}/variables.css"`; + } + case "variables.css": { + const manifest = findManifestFromThemeName(theme, options.manifestLocations); + const variables = manifest.values.variants[variant].variables; + const css = getRootSectionWithVariables(variables); + return css; + } + } } } - return [ - { - tag: "link", - attrs: { - rel: "stylesheet", - type: "text/css", - media: "(prefers-color-scheme: dark)", - href: `./${darkThemeLocation}`, - } - }, - { - tag: "link", - attrs: { - rel: "stylesheet", - type: "text/css", - media: "(prefers-color-scheme: light)", - href: `./${lightThemeLocation}`, - } - }, - ]; - }, + else { + const result = id.match(/(.+)\/theme.css\?variant=(.+)/); + if (result) { + const [, location, variant] = result; + const cssSource = await readCSSSource(location); + const config = variants[variant]; + return await appendVariablesToCSS(config.variables, cssSource); + } + return null; + } +}, - generateBundle(_, bundle) { - const { assetMap, chunkMap, runtimeThemeChunk } = parseBundle(bundle); - for (const [location, chunkArray] of chunkMap) { - const manifest = require(`${location}/manifest.json`); - const compiledVariables = options.compiledVariables.get(location); - const derivedVariables = compiledVariables["derived-variables"]; - const icon = compiledVariables["icon"]; - manifest.source = { - "built-asset": chunkArray.map(chunk => assetMap.get(chunk.fileName).fileName), - "runtime-asset": assetMap.get(runtimeThemeChunk.fileName).fileName, - "derived-variables": derivedVariables, - "icon": icon - }; - const name = `theme-${manifest.name}.json`; - this.emitFile({ - type: "asset", - name, - source: JSON.stringify(manifest), - }); + transformIndexHtml(_, ctx) { + if (isDevelopment) { + // Don't add default stylesheets to index.html on dev + return; + } + let darkThemeLocation, lightThemeLocation; + for (const [, bundle] of Object.entries(ctx.bundle)) { + if (bundle.name === defaultDark) { + darkThemeLocation = bundle.fileName; + } + if (bundle.name === defaultLight) { + lightThemeLocation = bundle.fileName; } } + return [ + { + tag: "link", + attrs: { + rel: "stylesheet", + type: "text/css", + media: "(prefers-color-scheme: dark)", + href: `./${darkThemeLocation}`, + } + }, + { + tag: "link", + attrs: { + rel: "stylesheet", + type: "text/css", + media: "(prefers-color-scheme: light)", + href: `./${lightThemeLocation}`, + } + }, + ]; +}, + +generateBundle(_, bundle) { + const { assetMap, chunkMap, runtimeThemeChunk } = parseBundle(bundle); + for (const [location, chunkArray] of chunkMap) { + const manifest = require(`${location}/manifest.json`); + const compiledVariables = options.compiledVariables.get(location); + const derivedVariables = compiledVariables["derived-variables"]; + const icon = compiledVariables["icon"]; + manifest.source = { + "built-asset": chunkArray.map(chunk => assetMap.get(chunk.fileName).fileName), + "runtime-asset": assetMap.get(runtimeThemeChunk.fileName).fileName, + "derived-variables": derivedVariables, + "icon": icon + }; + const name = `theme-${manifest.name}.json`; + this.emitFile({ + type: "asset", + name, + source: JSON.stringify(manifest), + }); + } +} } } From 0a95eb09405816c0cbd73883eb8b06e8c5d48e9f Mon Sep 17 00:00:00 2001 From: RMidhunSuresh Date: Sun, 10 Apr 2022 14:52:26 +0530 Subject: [PATCH 130/238] Fix formatting --- .../rollup-plugin-build-themes.js | 110 +++++++++--------- 1 file changed, 55 insertions(+), 55 deletions(-) diff --git a/scripts/build-plugins/rollup-plugin-build-themes.js b/scripts/build-plugins/rollup-plugin-build-themes.js index 29fd91e1..eb063c72 100644 --- a/scripts/build-plugins/rollup-plugin-build-themes.js +++ b/scripts/build-plugins/rollup-plugin-build-themes.js @@ -187,64 +187,64 @@ module.exports = function buildThemes(options) { } return null; } -}, + }, - transformIndexHtml(_, ctx) { - if (isDevelopment) { - // Don't add default stylesheets to index.html on dev - return; - } - let darkThemeLocation, lightThemeLocation; - for (const [, bundle] of Object.entries(ctx.bundle)) { - if (bundle.name === defaultDark) { - darkThemeLocation = bundle.fileName; - } - if (bundle.name === defaultLight) { - lightThemeLocation = bundle.fileName; - } - } - return [ - { - tag: "link", - attrs: { - rel: "stylesheet", - type: "text/css", - media: "(prefers-color-scheme: dark)", - href: `./${darkThemeLocation}`, + transformIndexHtml(_, ctx) { + if (isDevelopment) { + // Don't add default stylesheets to index.html on dev + return; + } + let darkThemeLocation, lightThemeLocation; + for (const [, bundle] of Object.entries(ctx.bundle)) { + if (bundle.name === defaultDark) { + darkThemeLocation = bundle.fileName; } - }, - { - tag: "link", - attrs: { - rel: "stylesheet", - type: "text/css", - media: "(prefers-color-scheme: light)", - href: `./${lightThemeLocation}`, + if (bundle.name === defaultLight) { + lightThemeLocation = bundle.fileName; } - }, - ]; -}, + } + return [ + { + tag: "link", + attrs: { + rel: "stylesheet", + type: "text/css", + media: "(prefers-color-scheme: dark)", + href: `./${darkThemeLocation}`, + } + }, + { + tag: "link", + attrs: { + rel: "stylesheet", + type: "text/css", + media: "(prefers-color-scheme: light)", + href: `./${lightThemeLocation}`, + } + }, + ]; + }, -generateBundle(_, bundle) { - const { assetMap, chunkMap, runtimeThemeChunk } = parseBundle(bundle); - for (const [location, chunkArray] of chunkMap) { - const manifest = require(`${location}/manifest.json`); - const compiledVariables = options.compiledVariables.get(location); - const derivedVariables = compiledVariables["derived-variables"]; - const icon = compiledVariables["icon"]; - manifest.source = { - "built-asset": chunkArray.map(chunk => assetMap.get(chunk.fileName).fileName), - "runtime-asset": assetMap.get(runtimeThemeChunk.fileName).fileName, - "derived-variables": derivedVariables, - "icon": icon - }; - const name = `theme-${manifest.name}.json`; - this.emitFile({ - type: "asset", - name, - source: JSON.stringify(manifest), - }); - } -} + generateBundle(_, bundle) { + const { assetMap, chunkMap, runtimeThemeChunk } = parseBundle(bundle); + for (const [location, chunkArray] of chunkMap) { + const manifest = require(`${location}/manifest.json`); + const compiledVariables = options.compiledVariables.get(location); + const derivedVariables = compiledVariables["derived-variables"]; + const icon = compiledVariables["icon"]; + manifest.source = { + "built-asset": chunkArray.map(chunk => assetMap.get(chunk.fileName).fileName), + "runtime-asset": assetMap.get(runtimeThemeChunk.fileName).fileName, + "derived-variables": derivedVariables, + "icon": icon + }; + const name = `theme-${manifest.name}.json`; + this.emitFile({ + type: "asset", + name, + source: JSON.stringify(manifest), + }); + } + }, } } From 49535807bf1b60c3e8d4dbabacd6108c80e98d04 Mon Sep 17 00:00:00 2001 From: RMidhunSuresh Date: Sun, 10 Apr 2022 14:59:08 +0530 Subject: [PATCH 131/238] Do not run plugin on runtime theme --- scripts/postcss/css-url-processor.js | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/scripts/postcss/css-url-processor.js b/scripts/postcss/css-url-processor.js index 1a0e4fdf..c050a898 100644 --- a/scripts/postcss/css-url-processor.js +++ b/scripts/postcss/css-url-processor.js @@ -68,6 +68,11 @@ module.exports = (opts = {}) => { postcssPlugin: "postcss-url-to-variable", Once(root, {result}) { + const cssFileLocation = root.source.input.from; + if (cssFileLocation.includes("type=runtime")) { + // If this is a runtime theme, don't process urls. + return; + } /* postcss-compile-variables should have sent the list of resolved colours down via results */ From 6456d4ef7627ec51f1a8e3bdce618e0e9ffc21d3 Mon Sep 17 00:00:00 2001 From: RMidhunSuresh Date: Sun, 10 Apr 2022 14:59:42 +0530 Subject: [PATCH 132/238] Cache cssPath --- scripts/postcss/css-url-processor.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/scripts/postcss/css-url-processor.js b/scripts/postcss/css-url-processor.js index c050a898..3ae7c60d 100644 --- a/scripts/postcss/css-url-processor.js +++ b/scripts/postcss/css-url-processor.js @@ -16,6 +16,7 @@ limitations under the License. const valueParser = require("postcss-value-parser"); const resolve = require("path").resolve; +let cssPath; function colorsFromURL(url, colorMap) { const params = new URL(`file://${url}`).searchParams; @@ -44,7 +45,6 @@ function processURL(decl, replacer, colorMap) { } const urlStringNode = node.nodes[0]; const oldURL = urlStringNode.value; - const cssPath = decl.source?.input.file.replace(/[^/]*$/, ""); const oldURLAbsolute = resolve(cssPath, oldURL); const colors = colorsFromURL(oldURLAbsolute, colorMap); if (!colors) { @@ -84,6 +84,7 @@ module.exports = (opts = {}) => { Go through each declaration and if it contains an URL, replace the url with the result of running replacer(url) */ + cssPath = root.source?.input.file.replace(/[^/]*$/, ""); root.walkDecls(decl => processURL(decl, opts.replacer, colorMap)); }, }; From 36782fb4feb83d31f57567f12eb175325608ee7f Mon Sep 17 00:00:00 2001 From: RMidhunSuresh Date: Tue, 12 Apr 2022 19:44:29 +0530 Subject: [PATCH 133/238] Use unique filenames Otherwise newly produced svgs will replace other svgs produced earlier in the build. --- scripts/postcss/svg-colorizer.js | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/scripts/postcss/svg-colorizer.js b/scripts/postcss/svg-colorizer.js index 7d527ddb..a3110693 100644 --- a/scripts/postcss/svg-colorizer.js +++ b/scripts/postcss/svg-colorizer.js @@ -16,6 +16,7 @@ limitations under the License. const fs = require("fs"); const path = require("path"); +const {randomUUID} = require('crypto'); /** * Builds a new svg with the colors replaced and returns its location. * @param {string} svgLocation The location of the input svg file @@ -30,6 +31,8 @@ module.exports.buildColorizedSVG = function (svgLocation, primaryColor, secondar throw new Error("svg-colorizer made no color replacements! The input svg should only contain colors #ff00ff (primary, case-sensitive) and #00ffff (secondary, case-sensitive)."); } const fileName = svgLocation.match(/.+\/(.+\.svg)/)[1]; + // give unique names so that this svg does not replace other versions of the same svg + const outputName = `${fileName.substring(0, fileName.length - 4)}-${randomUUID()}.svg`; const outputPath = path.resolve(__dirname, "../../.tmp"); try { fs.mkdirSync(outputPath); @@ -39,7 +42,7 @@ module.exports.buildColorizedSVG = function (svgLocation, primaryColor, secondar throw e; } } - const outputFile = `${outputPath}/${fileName}`; + const outputFile = `${outputPath}/${outputName}`; fs.writeFileSync(outputFile, coloredSVGCode); return outputFile; } From 25a8521efcd7751db4ae8b01c60a21b5f5bf70ea Mon Sep 17 00:00:00 2001 From: RMidhunSuresh Date: Tue, 12 Apr 2022 20:15:14 +0530 Subject: [PATCH 134/238] Use hash instead of UUID --- scripts/postcss/svg-colorizer.js | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/scripts/postcss/svg-colorizer.js b/scripts/postcss/svg-colorizer.js index a3110693..95355ea8 100644 --- a/scripts/postcss/svg-colorizer.js +++ b/scripts/postcss/svg-colorizer.js @@ -16,7 +16,14 @@ limitations under the License. const fs = require("fs"); const path = require("path"); -const {randomUUID} = require('crypto'); +const xxhash = require('xxhashjs'); + +function createHash(content) { + const hasher = new xxhash.h32(0); + hasher.update(content); + return hasher.digest(); +} + /** * Builds a new svg with the colors replaced and returns its location. * @param {string} svgLocation The location of the input svg file @@ -31,8 +38,7 @@ module.exports.buildColorizedSVG = function (svgLocation, primaryColor, secondar throw new Error("svg-colorizer made no color replacements! The input svg should only contain colors #ff00ff (primary, case-sensitive) and #00ffff (secondary, case-sensitive)."); } const fileName = svgLocation.match(/.+\/(.+\.svg)/)[1]; - // give unique names so that this svg does not replace other versions of the same svg - const outputName = `${fileName.substring(0, fileName.length - 4)}-${randomUUID()}.svg`; + const outputName = `${fileName.substring(0, fileName.length - 4)}-${createHash(coloredSVGCode)}.svg`; const outputPath = path.resolve(__dirname, "../../.tmp"); try { fs.mkdirSync(outputPath); From 743bd0db1c6496e359561630230d62fd64305046 Mon Sep 17 00:00:00 2001 From: RMidhunSuresh Date: Tue, 12 Apr 2022 20:39:04 +0530 Subject: [PATCH 135/238] Support dark mode and remove dev script tag --- .../rollup-plugin-build-themes.js | 25 +++++++++++++++---- 1 file changed, 20 insertions(+), 5 deletions(-) diff --git a/scripts/build-plugins/rollup-plugin-build-themes.js b/scripts/build-plugins/rollup-plugin-build-themes.js index eb063c72..14d306e8 100644 --- a/scripts/build-plugins/rollup-plugin-build-themes.js +++ b/scripts/build-plugins/rollup-plugin-build-themes.js @@ -111,6 +111,7 @@ module.exports = function buildThemes(options) { }, async buildStart() { + if (isDevelopment) { return; } const { manifestLocations } = options; for (const location of manifestLocations) { manifest = require(`${location}/manifest.json`); @@ -130,7 +131,7 @@ module.exports = function buildThemes(options) { // emit the css as built theme bundle this.emitFile({ type: "chunk", - id: `${location}/theme.css?variant=${variant}`, + id: `${location}/theme.css?variant=${variant}${details.dark? "&dark=true": ""}`, fileName, }); } @@ -145,7 +146,7 @@ module.exports = function buildThemes(options) { resolveId(id) { if (id.startsWith(virtualModuleId)) { - return isDevelopment? '\0' + id: false; + return '\0' + id; } }, @@ -165,7 +166,9 @@ module.exports = function buildThemes(options) { switch (file) { case "index.js": { const location = findLocationFromThemeName(theme, options.manifestLocations); - return `import "${path.resolve(`${location}/theme.css`)}";` + + const manifest = findManifestFromThemeName(theme, options.manifestLocations); + const isDark = manifest.values.variants[variant].dark; + return `import "${path.resolve(`${location}/theme.css`)}${isDark? "?dark=true": ""}";` + `import "@theme/${theme}/${variant}/variables.css"`; } case "variables.css": { @@ -178,7 +181,7 @@ module.exports = function buildThemes(options) { } } else { - const result = id.match(/(.+)\/theme.css\?variant=(.+)/); + const result = id.match(/(.+)\/theme.css\?variant=([^&]+)/); if (result) { const [, location, variant] = result; const cssSource = await readCSSSource(location); @@ -189,6 +192,18 @@ module.exports = function buildThemes(options) { } }, + transform(code, id) { + if (isDevelopment) { + return; + } + // Removes develop-only script tag; this cannot be done in transformIndexHtml hook. + const devScriptTag = / From 6cd3c8ee2bfab6498f260eb5e91cacb16c11688a Mon Sep 17 00:00:00 2001 From: RMidhunSuresh Date: Wed, 20 Apr 2022 12:25:23 +0530 Subject: [PATCH 164/238] Read config from URL --- src/platform/web/Platform.js | 19 +++++++++++++++++-- src/platform/web/index.html | 7 +++---- src/platform/web/main.js | 1 + vite.common-config.js | 1 + 4 files changed, 22 insertions(+), 6 deletions(-) diff --git a/src/platform/web/Platform.js b/src/platform/web/Platform.js index 1c630aed..92608d75 100644 --- a/src/platform/web/Platform.js +++ b/src/platform/web/Platform.js @@ -126,10 +126,11 @@ function adaptUIOnVisualViewportResize(container) { } export class Platform { - constructor({ container, assetPaths, config, options = null, cryptoExtras = null }) { + constructor({ container, assetPaths, config, configURL, options = null, cryptoExtras = null }) { this._container = container; this._assetPaths = assetPaths; this._config = config; + this._configURL = configURL; this.settingsStorage = new SettingsStorage("hydrogen_setting_v1_"); this.clock = new Clock(); this.encoding = new Encoding(); @@ -142,7 +143,7 @@ export class Platform { this._serviceWorkerHandler = new ServiceWorkerHandler(); this._serviceWorkerHandler.registerAndStart(assetPaths.serviceWorker); } - this.notificationService = new NotificationService(this._serviceWorkerHandler, config.push); + this.notificationService = null; // Only try to use crypto when olm is provided if(this._assetPaths.olm) { this.crypto = new Crypto(cryptoExtras); @@ -165,6 +166,20 @@ export class Platform { this._workerPromise = undefined; } + async init() { + if (!this._config) { + if (!this._configURL) { + throw new Error("Neither config nor configURL was provided!"); + } + const {body}= await this.request(this._configURL, {method: "GET", format: "json"}).response(); + this._config = body; + } + this._notificationService = new NotificationService( + this._serviceWorkerHandler, + this._config.push + ); + } + _createLogger(isDevelopment) { // Make sure that loginToken does not end up in the logs const transformer = (item) => { diff --git a/src/platform/web/index.html b/src/platform/web/index.html index ed0e8eb0..16418699 100644 --- a/src/platform/web/index.html +++ b/src/platform/web/index.html @@ -17,7 +17,7 @@ diff --git a/src/platform/web/main.js b/src/platform/web/main.js index 1729c17c..edc2cf14 100644 --- a/src/platform/web/main.js +++ b/src/platform/web/main.js @@ -32,6 +32,7 @@ export async function main(platform) { // const recorder = new RecordRequester(createFetchRequest(clock.createTimeout)); // const request = recorder.request; // window.getBrawlFetchLog = () => recorder.log(); + await platform.init(); const navigation = createNavigation(); platform.setNavigation(navigation); const urlRouter = createRouter({navigation, history: platform.history}); diff --git a/vite.common-config.js b/vite.common-config.js index f5a90154..8a82a9da 100644 --- a/vite.common-config.js +++ b/vite.common-config.js @@ -31,6 +31,7 @@ const commonOptions = { assetsInlineLimit: 0, polyfillModulePreload: false, }, + assetsInclude: ['**/config.json'], define: { DEFINE_VERSION: JSON.stringify(version), DEFINE_GLOBAL_HASH: JSON.stringify(null), From 5f8a171c2c706a884321c16f9798874fc1bf364b Mon Sep 17 00:00:00 2001 From: Eric Eastwood Date: Wed, 20 Apr 2022 11:55:48 -0500 Subject: [PATCH 165/238] Fix asset build throwing and swallowing errors (#721) - Fix `svg-colorizer` throwing errors with Windows file paths - Fix `css-url-parser` swallowing errors because it was `async` - Fail SDK build script (`yarn build:sdk`, `build.sh`) overall when some commands are failing --- scripts/postcss/css-url-processor.js | 2 +- scripts/postcss/svg-colorizer.js | 2 +- scripts/sdk/build.sh | 4 ++++ 3 files changed, 6 insertions(+), 2 deletions(-) diff --git a/scripts/postcss/css-url-processor.js b/scripts/postcss/css-url-processor.js index 3ae7c60d..f58818f1 100644 --- a/scripts/postcss/css-url-processor.js +++ b/scripts/postcss/css-url-processor.js @@ -39,7 +39,7 @@ function colorsFromURL(url, colorMap) { function processURL(decl, replacer, colorMap) { const value = decl.value; const parsed = valueParser(value); - parsed.walk(async node => { + parsed.walk(node => { if (node.type !== "function" || node.value !== "url") { return; } diff --git a/scripts/postcss/svg-colorizer.js b/scripts/postcss/svg-colorizer.js index 95355ea8..06b7b14b 100644 --- a/scripts/postcss/svg-colorizer.js +++ b/scripts/postcss/svg-colorizer.js @@ -37,7 +37,7 @@ module.exports.buildColorizedSVG = function (svgLocation, primaryColor, secondar if (svgCode === coloredSVGCode) { throw new Error("svg-colorizer made no color replacements! The input svg should only contain colors #ff00ff (primary, case-sensitive) and #00ffff (secondary, case-sensitive)."); } - const fileName = svgLocation.match(/.+\/(.+\.svg)/)[1]; + const fileName = svgLocation.match(/.+[/\\](.+\.svg)/)[1]; const outputName = `${fileName.substring(0, fileName.length - 4)}-${createHash(coloredSVGCode)}.svg`; const outputPath = path.resolve(__dirname, "../../.tmp"); try { diff --git a/scripts/sdk/build.sh b/scripts/sdk/build.sh index 2ac4be3a..ae3a794e 100755 --- a/scripts/sdk/build.sh +++ b/scripts/sdk/build.sh @@ -1,4 +1,8 @@ #!/bin/bash +# Exit whenever one of the commands fail with a non-zero exit code +set -e +set -o pipefail + rm -rf target yarn run vite build -c vite.sdk-assets-config.js yarn run vite build -c vite.sdk-lib-config.js From f1e07b684213edd8be1651898f526dc3d568d64d Mon Sep 17 00:00:00 2001 From: Eric Eastwood Date: Wed, 20 Apr 2022 11:59:49 -0500 Subject: [PATCH 166/238] Explain what is being deleted by the strange syntax See https://github.com/vector-im/hydrogen-web/pull/693#discussion_r815284713 --- scripts/sdk/build.sh | 1 + 1 file changed, 1 insertion(+) diff --git a/scripts/sdk/build.sh b/scripts/sdk/build.sh index b0040b61..5adc69c5 100755 --- a/scripts/sdk/build.sh +++ b/scripts/sdk/build.sh @@ -17,6 +17,7 @@ mkdir target/paths cp doc/SDK.md target/README.md pushd target pushd asset-build/assets +# Remove all `*.wasm` and `*.js` files except for `main.js` rm !(main).js *.wasm popd rm index.html From ce289baba61e40b711fecca10066c0be73542f44 Mon Sep 17 00:00:00 2001 From: Eric Eastwood Date: Wed, 20 Apr 2022 17:32:12 -0500 Subject: [PATCH 167/238] Remove extra space --- scripts/sdk/build.sh | 1 - 1 file changed, 1 deletion(-) diff --git a/scripts/sdk/build.sh b/scripts/sdk/build.sh index 5adc69c5..6a7bc93f 100755 --- a/scripts/sdk/build.sh +++ b/scripts/sdk/build.sh @@ -5,7 +5,6 @@ set -o pipefail # Enable extended globs so we can use the `!(filename)` glob syntax shopt -s extglob - rm -rf target/* yarn run vite build -c vite.sdk-assets-config.js yarn run vite build -c vite.sdk-lib-config.js From 468b7e15954df04bff84a99252fc6ced16aa5816 Mon Sep 17 00:00:00 2001 From: RMidhunSuresh Date: Thu, 21 Apr 2022 12:52:42 +0530 Subject: [PATCH 168/238] Cache config.json --- src/platform/web/Platform.js | 2 +- src/platform/web/sw.js | 35 ++++++++++++++++++++++++++++++++++- vite.config.js | 5 +++++ 3 files changed, 40 insertions(+), 2 deletions(-) diff --git a/src/platform/web/Platform.js b/src/platform/web/Platform.js index 92608d75..7d5126dc 100644 --- a/src/platform/web/Platform.js +++ b/src/platform/web/Platform.js @@ -171,7 +171,7 @@ export class Platform { if (!this._configURL) { throw new Error("Neither config nor configURL was provided!"); } - const {body}= await this.request(this._configURL, {method: "GET", format: "json"}).response(); + const {body}= await this.request(this._configURL, {method: "GET", format: "json", cache: true}).response(); this._config = body; } this._notificationService = new NotificationService( diff --git a/src/platform/web/sw.js b/src/platform/web/sw.js index c5f69438..e57634fc 100644 --- a/src/platform/web/sw.js +++ b/src/platform/web/sw.js @@ -75,7 +75,14 @@ self.addEventListener('fetch', (event) => { This has to do with xhr not being supported in service workers. */ if (event.request.method === "GET") { - event.respondWith(handleRequest(event.request)); + if (event.request.url.includes("config.json")) { + /** + * Use a different strategy for this file. + */ + event.respondWith(handleConfigRequest(event.request)); + } else { + event.respondWith(handleRequest(event.request)); + } } }); @@ -119,6 +126,32 @@ async function handleRequest(request) { } } +async function handleConfigRequest(request) { + const url = new URL(request.url); + // rewrite / to /index.html so it hits the cache + if (url.origin === baseURL.origin && url.pathname === baseURL.pathname) { + request = new Request(new URL("index.html", baseURL.href)); + } + let response = await readCache(request); + if (response) { + fetchAndUpdateConfig(request); + return response; + } + response = await fetchAndUpdateConfig(request); + return response; +} + +async function fetchAndUpdateConfig(request) { + const response = await fetch(request, { + signal: pendingFetchAbortController.signal, + headers: { + "Cache-Control": "no-cache", + }, + }); + updateCache(request, response.clone()); + return response; +} + async function updateCache(request, response) { // don't write error responses to the cache if (response.status >= 400) { diff --git a/vite.config.js b/vite.config.js index 4dd35af2..87e3d063 100644 --- a/vite.config.js +++ b/vite.config.js @@ -14,6 +14,11 @@ export default defineConfig(({mode}) => { outDir: "../../../target", minify: true, sourcemap: true, + rollupOptions: { + output: { + assetFileNames: (asset) => asset.name.includes("config.json") ? "assets/[name][extname]": "assets/[name].[hash][extname]", + }, + }, }, plugins: [ themeBuilder({ From 1cdc76f5a464871a50ca909a1eb4d126e5632703 Mon Sep 17 00:00:00 2001 From: RMidhunSuresh Date: Thu, 21 Apr 2022 14:14:38 +0530 Subject: [PATCH 169/238] Use undefine instead of null --- src/platform/web/Platform.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/platform/web/Platform.js b/src/platform/web/Platform.js index 7d5126dc..1c999598 100644 --- a/src/platform/web/Platform.js +++ b/src/platform/web/Platform.js @@ -143,7 +143,7 @@ export class Platform { this._serviceWorkerHandler = new ServiceWorkerHandler(); this._serviceWorkerHandler.registerAndStart(assetPaths.serviceWorker); } - this.notificationService = null; + this.notificationService = undefined; // Only try to use crypto when olm is provided if(this._assetPaths.olm) { this.crypto = new Crypto(cryptoExtras); From 4f239445816df107fb446ba3b46345a20e77cbb4 Mon Sep 17 00:00:00 2001 From: RMidhunSuresh Date: Thu, 21 Apr 2022 14:17:47 +0530 Subject: [PATCH 170/238] Use named param in Legacy Platform --- src/platform/web/LegacyPlatform.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/platform/web/LegacyPlatform.js b/src/platform/web/LegacyPlatform.js index 85632bf2..b8a6d7e7 100644 --- a/src/platform/web/LegacyPlatform.js +++ b/src/platform/web/LegacyPlatform.js @@ -19,6 +19,6 @@ import {hkdf} from "../../utils/crypto/hkdf"; import {Platform as ModernPlatform} from "./Platform.js"; -export function Platform(container, assetPaths, config, options = null) { - return new ModernPlatform(container, assetPaths, config, options, {aesjs, hkdf}); +export function Platform({ container, assetPaths, config, configURL, options = null }) { + return new ModernPlatform({ container, assetPaths, config, configURL, options, cryptoExtras: { aesjs, hkdf }}); } From b6e55ef59c263901421f8e53db23f301a91f0633 Mon Sep 17 00:00:00 2001 From: RMidhunSuresh Date: Thu, 21 Apr 2022 14:46:55 +0530 Subject: [PATCH 171/238] Remove comment --- src/platform/web/sw.js | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/platform/web/sw.js b/src/platform/web/sw.js index e57634fc..aaf56c4b 100644 --- a/src/platform/web/sw.js +++ b/src/platform/web/sw.js @@ -76,9 +76,6 @@ self.addEventListener('fetch', (event) => { */ if (event.request.method === "GET") { if (event.request.url.includes("config.json")) { - /** - * Use a different strategy for this file. - */ event.respondWith(handleConfigRequest(event.request)); } else { event.respondWith(handleRequest(event.request)); From 826835e518666d3fd7d09f4e944c98ee3650d369 Mon Sep 17 00:00:00 2001 From: RMidhunSuresh Date: Fri, 22 Apr 2022 12:07:53 +0530 Subject: [PATCH 172/238] No need to rewrite to index.html --- src/platform/web/sw.js | 5 ----- 1 file changed, 5 deletions(-) diff --git a/src/platform/web/sw.js b/src/platform/web/sw.js index aaf56c4b..91ae85c5 100644 --- a/src/platform/web/sw.js +++ b/src/platform/web/sw.js @@ -124,11 +124,6 @@ async function handleRequest(request) { } async function handleConfigRequest(request) { - const url = new URL(request.url); - // rewrite / to /index.html so it hits the cache - if (url.origin === baseURL.origin && url.pathname === baseURL.pathname) { - request = new Request(new URL("index.html", baseURL.href)); - } let response = await readCache(request); if (response) { fetchAndUpdateConfig(request); From c6691cf1cbd6fe69b617f445d1ed6a3fe297c216 Mon Sep 17 00:00:00 2001 From: RMidhunSuresh Date: Fri, 22 Apr 2022 12:10:25 +0530 Subject: [PATCH 173/238] Simplify code --- src/platform/web/sw.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/platform/web/sw.js b/src/platform/web/sw.js index 91ae85c5..2d43d1ee 100644 --- a/src/platform/web/sw.js +++ b/src/platform/web/sw.js @@ -125,12 +125,12 @@ async function handleRequest(request) { async function handleConfigRequest(request) { let response = await readCache(request); + const networkResponsePromise = fetchAndUpdateConfig(request); if (response) { - fetchAndUpdateConfig(request); return response; + } else { + return await networkResponsePromise; } - response = await fetchAndUpdateConfig(request); - return response; } async function fetchAndUpdateConfig(request) { From 5a94a2feba344029699705dd15f33891266b7b6c Mon Sep 17 00:00:00 2001 From: RMidhunSuresh Date: Fri, 22 Apr 2022 12:22:30 +0530 Subject: [PATCH 174/238] Move handleConfigRequest inside handleRequest --- src/platform/web/sw.js | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/platform/web/sw.js b/src/platform/web/sw.js index 2d43d1ee..a9a92979 100644 --- a/src/platform/web/sw.js +++ b/src/platform/web/sw.js @@ -75,11 +75,7 @@ self.addEventListener('fetch', (event) => { This has to do with xhr not being supported in service workers. */ if (event.request.method === "GET") { - if (event.request.url.includes("config.json")) { - event.respondWith(handleConfigRequest(event.request)); - } else { - event.respondWith(handleRequest(event.request)); - } + event.respondWith(handleRequest(event.request)); } }); @@ -96,8 +92,12 @@ function isCacheableThumbnail(url) { const baseURL = new URL(self.registration.scope); let pendingFetchAbortController = new AbortController(); + async function handleRequest(request) { try { + if (request.url.includes("config.json")) { + return handleConfigRequest(request); + } const url = new URL(request.url); // rewrite / to /index.html so it hits the cache if (url.origin === baseURL.origin && url.pathname === baseURL.pathname) { From 7a33c2e00d69f09979a3f668bff6e130441f5623 Mon Sep 17 00:00:00 2001 From: RMidhunSuresh Date: Fri, 22 Apr 2022 12:26:29 +0530 Subject: [PATCH 175/238] await --- src/platform/web/sw.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/platform/web/sw.js b/src/platform/web/sw.js index a9a92979..49789804 100644 --- a/src/platform/web/sw.js +++ b/src/platform/web/sw.js @@ -96,7 +96,7 @@ let pendingFetchAbortController = new AbortController(); async function handleRequest(request) { try { if (request.url.includes("config.json")) { - return handleConfigRequest(request); + return await handleConfigRequest(request); } const url = new URL(request.url); // rewrite / to /index.html so it hits the cache From d8da1287804921bd785be8383b51827913b2c1d6 Mon Sep 17 00:00:00 2001 From: RMidhunSuresh Date: Fri, 22 Apr 2022 14:34:16 +0530 Subject: [PATCH 176/238] remove await --- src/platform/web/sw.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/platform/web/sw.js b/src/platform/web/sw.js index 49789804..a9a92979 100644 --- a/src/platform/web/sw.js +++ b/src/platform/web/sw.js @@ -96,7 +96,7 @@ let pendingFetchAbortController = new AbortController(); async function handleRequest(request) { try { if (request.url.includes("config.json")) { - return await handleConfigRequest(request); + return handleConfigRequest(request); } const url = new URL(request.url); // rewrite / to /index.html so it hits the cache From 6c57c96cb9a7a3b77f4fd1c92723bfbbad59b860 Mon Sep 17 00:00:00 2001 From: Bruno Windels <274386+bwindels@users.noreply.github.com> Date: Mon, 25 Apr 2022 12:07:28 +0200 Subject: [PATCH 177/238] add typing for text bindings in template view --- src/platform/web/ui/general/TemplateView.ts | 24 +++++++++++++-------- 1 file changed, 15 insertions(+), 9 deletions(-) diff --git a/src/platform/web/ui/general/TemplateView.ts b/src/platform/web/ui/general/TemplateView.ts index ce593f75..a0e2079a 100644 --- a/src/platform/web/ui/general/TemplateView.ts +++ b/src/platform/web/ui/general/TemplateView.ts @@ -15,7 +15,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -import { setAttribute, text, isChildren, classNames, TAG_NAMES, HTML_NS, ClassNames, Child} from "./html"; +import { setAttribute, text, isChildren, classNames, TAG_NAMES, HTML_NS, ClassNames, Child as NonBoundChild} from "./html"; import {mountView} from "./utils"; import {BaseUpdateView, IObservableValue} from "./BaseUpdateView"; import {IMountArgs, ViewNode, IView} from "./types"; @@ -30,12 +30,15 @@ function objHasFns(obj: ClassNames): obj is { [className: string]: bool } export type RenderFn = (t: Builder, vm: T) => ViewNode; +type TextBinding = (T) => string | number | boolean | undefined | null; +type Child = NonBoundChild | TextBinding; +type Children = Child | Child[]; type EventHandler = ((event: Event) => void); type AttributeStaticValue = string | boolean; type AttributeBinding = (value: T) => AttributeStaticValue; export type AttrValue = AttributeStaticValue | AttributeBinding | EventHandler | ClassNames; export type Attributes = { [attribute: string]: AttrValue }; -type ElementFn = (attributes?: Attributes | Child | Child[], children?: Child | Child[]) => Element; +type ElementFn = (attributes?: Attributes | Children, children?: Children) => Element; export type Builder = TemplateBuilder & { [tagName in typeof TAG_NAMES[string][number]]: ElementFn }; /** @@ -195,15 +198,15 @@ export class TemplateBuilder { this._addAttributeBinding(node, "className", value => classNames(obj, value)); } - _addTextBinding(fn: (value: T) => string): Text { - const initialValue = fn(this._value); + _addTextBinding(fn: (value: T) => ReturnType>): Text { + const initialValue = fn(this._value)+""; const node = text(initialValue); let prevValue = initialValue; const binding = () => { - const newValue = fn(this._value); + const newValue = fn(this._value)+""; if (prevValue !== newValue) { prevValue = newValue; - node.textContent = newValue+""; + node.textContent = newValue; } }; @@ -242,7 +245,7 @@ export class TemplateBuilder { } } - _setNodeChildren(node: Element, children: Child | Child[]): void{ + _setNodeChildren(node: Element, children: Children): void{ if (!Array.isArray(children)) { children = [children]; } @@ -276,14 +279,17 @@ export class TemplateBuilder { return node; } - el(name: string, attributes?: Attributes | Child | Child[], children?: Child | Child[]): ViewNode { + el(name: string, attributes?: Attributes | Children, children?: Children): ViewNode { return this.elNS(HTML_NS, name, attributes, children); } - elNS(ns: string, name: string, attributes?: Attributes | Child | Child[], children?: Child | Child[]): ViewNode { + elNS(ns: string, name: string, attributesOrChildren?: Attributes | Children, children?: Children): ViewNode { + let attributes: Attributes | undefined; if (attributes !== undefined && isChildren(attributes)) { children = attributes; attributes = undefined; + } else { + attributes = attributesOrChildren as Attributes; } const node = document.createElementNS(ns, name); From ab893f63b5534cd40df096ef70f6a04456ec3f6d Mon Sep 17 00:00:00 2001 From: Bruno Windels <274386+bwindels@users.noreply.github.com> Date: Mon, 25 Apr 2022 12:40:25 +0200 Subject: [PATCH 178/238] remove unneeded assignment --- src/platform/web/ui/general/TemplateView.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/platform/web/ui/general/TemplateView.ts b/src/platform/web/ui/general/TemplateView.ts index a0e2079a..7f7f4c13 100644 --- a/src/platform/web/ui/general/TemplateView.ts +++ b/src/platform/web/ui/general/TemplateView.ts @@ -287,7 +287,6 @@ export class TemplateBuilder { let attributes: Attributes | undefined; if (attributes !== undefined && isChildren(attributes)) { children = attributes; - attributes = undefined; } else { attributes = attributesOrChildren as Attributes; } From bec8cea583af5d6a2a2b57ae70831a27d87ea3ac Mon Sep 17 00:00:00 2001 From: Bruno Windels <274386+bwindels@users.noreply.github.com> Date: Mon, 25 Apr 2022 14:17:07 +0200 Subject: [PATCH 179/238] fix for breaking in #725 --- src/platform/web/ui/general/TemplateView.ts | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/platform/web/ui/general/TemplateView.ts b/src/platform/web/ui/general/TemplateView.ts index 7f7f4c13..d6e3dd3f 100644 --- a/src/platform/web/ui/general/TemplateView.ts +++ b/src/platform/web/ui/general/TemplateView.ts @@ -285,10 +285,12 @@ export class TemplateBuilder { elNS(ns: string, name: string, attributesOrChildren?: Attributes | Children, children?: Children): ViewNode { let attributes: Attributes | undefined; - if (attributes !== undefined && isChildren(attributes)) { - children = attributes; - } else { - attributes = attributesOrChildren as Attributes; + if (attributesOrChildren) { + if (isChildren(attributesOrChildren)) { + children = attributesOrChildren as Children; + } else { + attributes = attributesOrChildren as Attributes; + } } const node = document.createElementNS(ns, name); From fa34315210c4ca9c072c5f096058d8076feb1ba9 Mon Sep 17 00:00:00 2001 From: Bruno Windels <274386+bwindels@users.noreply.github.com> Date: Mon, 25 Apr 2022 16:44:31 +0200 Subject: [PATCH 180/238] undo refactoring typo from #723 --- src/platform/web/Platform.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/platform/web/Platform.js b/src/platform/web/Platform.js index 1c999598..7d66301d 100644 --- a/src/platform/web/Platform.js +++ b/src/platform/web/Platform.js @@ -174,7 +174,7 @@ export class Platform { const {body}= await this.request(this._configURL, {method: "GET", format: "json", cache: true}).response(); this._config = body; } - this._notificationService = new NotificationService( + this.notificationService = new NotificationService( this._serviceWorkerHandler, this._config.push ); From 049a477008f380e26d2a66b25dbedbfbfdd6eba5 Mon Sep 17 00:00:00 2001 From: RMidhunSuresh Date: Wed, 27 Apr 2022 12:27:19 +0530 Subject: [PATCH 181/238] Pass flowSelector from Client.startRegistration --- src/matrix/Client.js | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/matrix/Client.js b/src/matrix/Client.js index b24c1ec9..21175a7f 100644 --- a/src/matrix/Client.js +++ b/src/matrix/Client.js @@ -132,14 +132,15 @@ export class Client { }); } - async startRegistration(homeserver, username, password, initialDeviceDisplayName) { + async startRegistration(homeserver, username, password, initialDeviceDisplayName, flowSelector) { const request = this._platform.request; const hsApi = new HomeServerApi({homeserver, request}); const registration = new Registration(hsApi, { username, password, initialDeviceDisplayName, - }); + }, + flowSelector); return registration; } From c07a42292cb5ef7829d938f860afbb1175717f28 Mon Sep 17 00:00:00 2001 From: RMidhunSuresh Date: Wed, 27 Apr 2022 12:28:48 +0530 Subject: [PATCH 182/238] Include Platform change in sdk docs --- doc/SDK.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/SDK.md b/doc/SDK.md index 54e37cca..cd81f15b 100644 --- a/doc/SDK.md +++ b/doc/SDK.md @@ -53,7 +53,7 @@ import "hydrogen-view-sdk/theme-element-light.css"; async function main() { const app = document.querySelector('#app')! const config = {}; - const platform = new Platform(app, assetPaths, config, { development: import.meta.env.DEV }); + const platform = new Platform({container: app, assetPaths, config, options: { development: import.meta.env.DEV }}); const navigation = createNavigation(); platform.setNavigation(navigation); const urlRouter = createRouter({ From 83664a1b13d266eb0b6d49f09a37f28753b293dc Mon Sep 17 00:00:00 2001 From: RMidhunSuresh Date: Wed, 27 Apr 2022 12:38:12 +0530 Subject: [PATCH 183/238] viewClassForTile is needed for TimelineView --- doc/SDK.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/doc/SDK.md b/doc/SDK.md index cd81f15b..3f5bdb09 100644 --- a/doc/SDK.md +++ b/doc/SDK.md @@ -31,7 +31,8 @@ import { createNavigation, createRouter, RoomViewModel, - TimelineView + TimelineView, + viewClassForTile } from "hydrogen-view-sdk"; import downloadSandboxPath from 'hydrogen-view-sdk/download-sandbox.html?url'; import workerPath from 'hydrogen-view-sdk/main.js?url'; @@ -88,7 +89,7 @@ async function main() { navigation, }); await vm.load(); - const view = new TimelineView(vm.timelineViewModel); + const view = new TimelineView(vm.timelineViewModel, viewClassForTile); app.appendChild(view.mount()); } } From d053d4388fb6d72612749cc6a864272242e1b6fa Mon Sep 17 00:00:00 2001 From: Eric Eastwood Date: Thu, 5 May 2022 14:58:43 -0500 Subject: [PATCH 184/238] Update Vite to avoid flakey errors in our PostCSS plugins Fix https://github.com/vector-im/hydrogen-web/issues/722 Updating Vite to includes fixes from https://github.com/vitejs/vite/issues/7822 -> https://github.com/vitejs/vite/pull/7827 --- package.json | 2 +- yarn.lock | 28 ++++++++++++++-------------- 2 files changed, 15 insertions(+), 15 deletions(-) diff --git a/package.json b/package.json index 7a5e79cc..49369865 100644 --- a/package.json +++ b/package.json @@ -49,7 +49,7 @@ "regenerator-runtime": "^0.13.7", "text-encoding": "^0.7.0", "typescript": "^4.3.5", - "vite": "^2.9.1", + "vite": "^2.9.8", "xxhashjs": "^0.2.2" }, "dependencies": { diff --git a/yarn.lock b/yarn.lock index 71fe419e..9d882bd2 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1234,10 +1234,10 @@ ms@2.1.2: resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.2.tgz#d09d1f357b443f493382a8eb3ccd183872ae6009" integrity sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w== -nanoid@^3.3.1: - version "3.3.2" - resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.3.2.tgz#c89622fafb4381cd221421c69ec58547a1eec557" - integrity sha512-CuHBogktKwpm5g2sRgv83jEy2ijFzBwMoYA60orPDR7ynsLijJDqgsi4RDGj3OJpy3Ieb+LYwiRmIOGyytgITA== +nanoid@^3.3.3: + version "3.3.4" + resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.3.4.tgz#730b67e3cd09e2deacf03c027c81c9d9dbc5e8ab" + integrity sha512-MqBkQh/OHTS2egovRtLk45wEyNXwF+cokD+1YPf9u5VfJiRdAiRwB2froX5Co9Rh20xs4siNPm8naNotSD6RBw== natural-compare@^1.4.0: version "1.4.0" @@ -1353,12 +1353,12 @@ postcss-value-parser@^4.2.0: resolved "https://registry.yarnpkg.com/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz#723c09920836ba6d3e5af019f92bc0971c02e514" integrity sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ== -postcss@^8.4.12: - version "8.4.12" - resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.4.12.tgz#1e7de78733b28970fa4743f7da6f3763648b1905" - integrity sha512-lg6eITwYe9v6Hr5CncVbK70SoioNQIq81nsaG86ev5hAidQvmOeETBqs7jm43K2F5/Ley3ytDtriImV6TpNiSg== +postcss@^8.4.13: + version "8.4.13" + resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.4.13.tgz#7c87bc268e79f7f86524235821dfdf9f73e5d575" + integrity sha512-jtL6eTBrza5MPzy8oJLFuUscHDXTV5KcLlqAWHl5q5WYRfnNRGSmOZmOZ1T6Gy7A99mOZfqungmZMpMmCVJ8ZA== dependencies: - nanoid "^3.3.1" + nanoid "^3.3.3" picocolors "^1.0.0" source-map-js "^1.0.2" @@ -1653,13 +1653,13 @@ v8-compile-cache@^2.0.3: resolved "https://registry.yarnpkg.com/v8-compile-cache/-/v8-compile-cache-2.3.0.tgz#2de19618c66dc247dcfb6f99338035d8245a2cee" integrity sha512-l8lCEmLcLYZh4nbunNZvQCJc5pv7+RCwa8q/LdUx8u7lsWvPDKmpodJAJNwkAhJC//dFY48KuIEmjtd4RViDrA== -vite@^2.9.1: - version "2.9.1" - resolved "https://registry.yarnpkg.com/vite/-/vite-2.9.1.tgz#84bce95fae210a7beb566a0af06246748066b48f" - integrity sha512-vSlsSdOYGcYEJfkQ/NeLXgnRv5zZfpAsdztkIrs7AZHV8RCMZQkwjo4DS5BnrYTqoWqLoUe1Cah4aVO4oNNqCQ== +vite@^2.9.8: + version "2.9.8" + resolved "https://registry.yarnpkg.com/vite/-/vite-2.9.8.tgz#2c2cb0790beb0fbe4b8c0995b80fe691a91c2545" + integrity sha512-zsBGwn5UT3YS0NLSJ7hnR54+vUKfgzMUh/Z9CxF1YKEBVIe213+63jrFLmZphgGI5zXwQCSmqIdbPuE8NJywPw== dependencies: esbuild "^0.14.27" - postcss "^8.4.12" + postcss "^8.4.13" resolve "^1.22.0" rollup "^2.59.0" optionalDependencies: From e54482e4c00b285ab442b55ed67411e671bebe77 Mon Sep 17 00:00:00 2001 From: Eric Eastwood Date: Thu, 5 May 2022 17:57:25 -0500 Subject: [PATCH 185/238] Add some comments --- scripts/sdk/build.sh | 2 ++ vite.sdk-assets-config.js | 2 ++ 2 files changed, 4 insertions(+) diff --git a/scripts/sdk/build.sh b/scripts/sdk/build.sh index 6a7bc93f..992d2efc 100755 --- a/scripts/sdk/build.sh +++ b/scripts/sdk/build.sh @@ -5,6 +5,8 @@ set -o pipefail # Enable extended globs so we can use the `!(filename)` glob syntax shopt -s extglob +# Only remove the directory contents instead of the whole directory to maintain +# the `npm link`/`yarn link` symlink rm -rf target/* yarn run vite build -c vite.sdk-assets-config.js yarn run vite build -c vite.sdk-lib-config.js diff --git a/vite.sdk-assets-config.js b/vite.sdk-assets-config.js index 7535a441..beb7bb37 100644 --- a/vite.sdk-assets-config.js +++ b/vite.sdk-assets-config.js @@ -3,6 +3,8 @@ const mergeOptions = require('merge-options'); const themeBuilder = require("./scripts/build-plugins/rollup-plugin-build-themes"); const {commonOptions, compiledVariables} = require("./vite.common-config.js"); +// These paths will be saved without their hash so they havea consisent path to +// reference const pathsToExport = [ "main.js", "download-sandbox.html", From 139a87de994463f4ce95efb498832be4b5b2a49c Mon Sep 17 00:00:00 2001 From: RMidhunSuresh Date: Sun, 8 May 2022 19:14:51 +0530 Subject: [PATCH 186/238] Pass a copy of the options to the tiles --- src/domain/session/room/timeline/TilesCollection.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/domain/session/room/timeline/TilesCollection.js b/src/domain/session/room/timeline/TilesCollection.js index 173b0cf6..d8dd660d 100644 --- a/src/domain/session/room/timeline/TilesCollection.js +++ b/src/domain/session/room/timeline/TilesCollection.js @@ -35,7 +35,7 @@ export class TilesCollection extends BaseObservableList { _createTile(entry) { const Tile = this._tileOptions.tileClassForEntry(entry); if (Tile) { - return new Tile(entry, this._tileOptions); + return new Tile(entry, { ...this._tileOptions }); } } From 6beff7e55255a8c6635dd855d2906113d360c261 Mon Sep 17 00:00:00 2001 From: Bruno Windels <274386+bwindels@users.noreply.github.com> Date: Mon, 9 May 2022 14:09:45 +0200 Subject: [PATCH 187/238] override emitChange so no need to clone option object for all tiles instead, we don't store the emitChange in the options but rather on the tile itself. --- .../session/room/timeline/TilesCollection.js | 2 +- .../session/room/timeline/tiles/SimpleTile.js | 15 ++++++++++----- 2 files changed, 11 insertions(+), 6 deletions(-) diff --git a/src/domain/session/room/timeline/TilesCollection.js b/src/domain/session/room/timeline/TilesCollection.js index d8dd660d..173b0cf6 100644 --- a/src/domain/session/room/timeline/TilesCollection.js +++ b/src/domain/session/room/timeline/TilesCollection.js @@ -35,7 +35,7 @@ export class TilesCollection extends BaseObservableList { _createTile(entry) { const Tile = this._tileOptions.tileClassForEntry(entry); if (Tile) { - return new Tile(entry, { ...this._tileOptions }); + return new Tile(entry, this._tileOptions); } } diff --git a/src/domain/session/room/timeline/tiles/SimpleTile.js b/src/domain/session/room/timeline/tiles/SimpleTile.js index b8a7121e..04141576 100644 --- a/src/domain/session/room/timeline/tiles/SimpleTile.js +++ b/src/domain/session/room/timeline/tiles/SimpleTile.js @@ -22,6 +22,7 @@ export class SimpleTile extends ViewModel { constructor(entry, options) { super(options); this._entry = entry; + this._emitUpdate = undefined; } // view model props for all subclasses // hmmm, could also do instanceof ... ? @@ -67,16 +68,20 @@ export class SimpleTile extends ViewModel { // TilesCollection contract below setUpdateEmit(emitUpdate) { - this.updateOptions({emitChange: paramName => { + this._emitUpdate = emitUpdate; + } + + /** overrides the emitChange in ViewModel to also emit the update over the tiles collection */ + emitChange(changedProps) { + if (this._emitUpdate) { // it can happen that after some network call // we switched away from the room and the response // comes in, triggering an emitChange in a tile that // has been disposed already (and hence the change // callback has been cleared by dispose) We should just ignore this. - if (emitUpdate) { - emitUpdate(this, paramName); - } - }}); + this._emitUpdate(this, changedProps); + } + super.emitChange(changedProps); } get upperEntry() { From 3888291758a82fd59d20c31549861a048b2fe67c Mon Sep 17 00:00:00 2001 From: Bruno Windels <274386+bwindels@users.noreply.github.com> Date: Mon, 9 May 2022 14:10:50 +0200 Subject: [PATCH 188/238] updateOptions is unused,not the best idea since options is/can be shared --- src/domain/ViewModel.ts | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/domain/ViewModel.ts b/src/domain/ViewModel.ts index 8b8581ae..8c12829a 100644 --- a/src/domain/ViewModel.ts +++ b/src/domain/ViewModel.ts @@ -115,10 +115,6 @@ export class ViewModel extends EventEmitter<{change return result; } - updateOptions(options: O): void { - this._options = Object.assign(this._options, options); - } - emitChange(changedProps: any): void { if (this._options.emitChange) { this._options.emitChange(changedProps); From e903d3a6a47200d950b180ffab6d7d9a8226b3bf Mon Sep 17 00:00:00 2001 From: Bruno Windels <274386+bwindels@users.noreply.github.com> Date: Mon, 9 May 2022 14:12:31 +0200 Subject: [PATCH 189/238] mark options as readonly --- src/domain/ViewModel.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/domain/ViewModel.ts b/src/domain/ViewModel.ts index 8c12829a..0bc52f6e 100644 --- a/src/domain/ViewModel.ts +++ b/src/domain/ViewModel.ts @@ -40,9 +40,9 @@ export type Options = { export class ViewModel extends EventEmitter<{change: never}> { private disposables?: Disposables; private _isDisposed = false; - private _options: O; + private _options: Readonly; - constructor(options: O) { + constructor(options: Readonly) { super(); this._options = options; } @@ -51,7 +51,7 @@ export class ViewModel extends EventEmitter<{change return Object.assign({}, this._options, explicitOptions); } - get options(): O { return this._options; } + get options(): Readonly { return this._options; } // makes it easier to pass through dependencies of a sub-view model getOption(name: N): O[N] { From b7675f46c4ea7ed956c0a48e5fa6ec11f03a603e Mon Sep 17 00:00:00 2001 From: Bruno Windels <274386+bwindels@users.noreply.github.com> Date: Wed, 27 Apr 2022 10:20:22 +0100 Subject: [PATCH 190/238] bump sdk version --- scripts/sdk/base-manifest.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/sdk/base-manifest.json b/scripts/sdk/base-manifest.json index 7730bbac..312f2913 100644 --- a/scripts/sdk/base-manifest.json +++ b/scripts/sdk/base-manifest.json @@ -1,7 +1,7 @@ { "name": "hydrogen-view-sdk", "description": "Embeddable matrix client library, including view components", - "version": "0.0.10", + "version": "0.0.11", "main": "./hydrogen.es.js", "type": "module" } From f16a2e5d22abcea46f33aa162634f1bf514257d2 Mon Sep 17 00:00:00 2001 From: RMidhunSuresh Date: Mon, 18 Apr 2022 16:17:56 +0530 Subject: [PATCH 191/238] Don't add asset hash to manifest json on build --- vite.config.js | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/vite.config.js b/vite.config.js index 87e3d063..a44d0917 100644 --- a/vite.config.js +++ b/vite.config.js @@ -16,25 +16,33 @@ export default defineConfig(({mode}) => { sourcemap: true, rollupOptions: { output: { - assetFileNames: (asset) => asset.name.includes("config.json") ? "assets/[name][extname]": "assets/[name].[hash][extname]", + assetFileNames: (asset) => + asset.name.includes("config.json") || + asset.name.match(/theme-.+\.json/) + ? "assets/[name][extname]" + : "assets/[name].[hash][extname]", }, }, }, plugins: [ themeBuilder({ themeConfig: { - themes: {"element": "./src/platform/web/ui/css/themes/element"}, + themes: { + element: "./src/platform/web/ui/css/themes/element", + }, default: "element", }, - compiledVariables + compiledVariables, }), // important this comes before service worker // otherwise the manifest and the icons it refers to won't be cached injectWebManifest("assets/manifest.json"), injectServiceWorker("./src/platform/web/sw.js", ["index.html"], { // placeholders to replace at end of build by chunk name - "index": {DEFINE_GLOBAL_HASH: definePlaceholders.DEFINE_GLOBAL_HASH}, - "sw": definePlaceholders + index: { + DEFINE_GLOBAL_HASH: definePlaceholders.DEFINE_GLOBAL_HASH, + }, + sw: definePlaceholders, }), ], define: definePlaceholders, From 541cd96eeb3ef9d8ab0174b51bc89b2e98659434 Mon Sep 17 00:00:00 2001 From: RMidhunSuresh Date: Mon, 18 Apr 2022 16:49:11 +0530 Subject: [PATCH 192/238] Add script to cleanup after build --- package.json | 2 +- scripts/cleanup.sh | 3 +++ 2 files changed, 4 insertions(+), 1 deletion(-) create mode 100755 scripts/cleanup.sh diff --git a/package.json b/package.json index 5b53790e..679000e1 100644 --- a/package.json +++ b/package.json @@ -12,7 +12,7 @@ "test": "impunity --entry-point src/platform/web/main.js src/platform/web/Platform.js --force-esm-dirs lib/ src/ --root-dir src/", "test:postcss": "impunity --entry-point scripts/postcss/tests/css-compile-variables.test.js scripts/postcss/tests/css-url-to-variables.test.js", "start": "vite --port 3000", - "build": "vite build", + "build": "vite build && ./scripts/cleanup.sh", "build:sdk": "./scripts/sdk/build.sh" }, "repository": { diff --git a/scripts/cleanup.sh b/scripts/cleanup.sh new file mode 100755 index 00000000..cdad04eb --- /dev/null +++ b/scripts/cleanup.sh @@ -0,0 +1,3 @@ +#!/bin/bash +# Remove icons created in .tmp +rm -rf .tmp From cc2c74fdff151683cda8b2287de875be4ef620e0 Mon Sep 17 00:00:00 2001 From: RMidhunSuresh Date: Tue, 19 Apr 2022 12:16:02 +0530 Subject: [PATCH 193/238] Generate theme summary on build --- .../build-plugins/rollup-plugin-build-themes.js | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/scripts/build-plugins/rollup-plugin-build-themes.js b/scripts/build-plugins/rollup-plugin-build-themes.js index e7a2bb2b..01969b1a 100644 --- a/scripts/build-plugins/rollup-plugin-build-themes.js +++ b/scripts/build-plugins/rollup-plugin-build-themes.js @@ -231,6 +231,7 @@ module.exports = function buildThemes(options) { generateBundle(_, bundle) { const { assetMap, chunkMap, runtimeThemeChunk } = parseBundle(bundle); + const themeSummary = {}; for (const [location, chunkArray] of chunkMap) { const manifest = require(`${location}/manifest.json`); const compiledVariables = options.compiledVariables.get(location); @@ -249,6 +250,22 @@ module.exports = function buildThemes(options) { source: JSON.stringify(manifest), }); } + /** + * Generate a mapping from theme name to asset hashed location of said theme in build output. + * This can be used to enumerate themes during runtime. + */ + for (const [, chunkArray] of chunkMap) { + chunkArray.forEach((chunk) => { + const [, name, variant] = chunk.fileName.match(/theme-(.+)-(.+)\.css/); + const assetHashedFileName = assetMap.get(chunk.fileName).fileName; + themeSummary[`${name}-${variant}`] = assetHashedFileName; + }); + } + this.emitFile({ + type: "asset", + name: "theme-summary.json", + source: JSON.stringify(themeSummary), + }); }, } } From daae7442bb0175edf02dc6bc3e1030e4d4701dee Mon Sep 17 00:00:00 2001 From: RMidhunSuresh Date: Mon, 25 Apr 2022 14:29:31 +0530 Subject: [PATCH 194/238] Create theme chooser --- .../rollup-plugin-build-themes.js | 14 ++++++++++ .../session/settings/SettingsViewModel.js | 8 ++++++ src/platform/web/Platform.js | 27 +++++++++++++++++++ .../web/ui/session/settings/SettingsView.js | 9 +++++++ 4 files changed, 58 insertions(+) diff --git a/scripts/build-plugins/rollup-plugin-build-themes.js b/scripts/build-plugins/rollup-plugin-build-themes.js index 01969b1a..805590f4 100644 --- a/scripts/build-plugins/rollup-plugin-build-themes.js +++ b/scripts/build-plugins/rollup-plugin-build-themes.js @@ -31,6 +31,17 @@ function appendVariablesToCSS(variables, cssSource) { return cssSource + getRootSectionWithVariables(variables); } +function addThemesToConfig(bundle, themeSummary) { + for (const [fileName, info] of Object.entries(bundle)) { + if (fileName === "assets/config.json") { + const source = new TextDecoder().decode(info.source); + const config = JSON.parse(source); + config["themes"] = themeSummary; + info.source = new TextEncoder().encode(JSON.stringify(config)); + } + } +} + function parseBundle(bundle) { const chunkMap = new Map(); const assetMap = new Map(); @@ -215,6 +226,7 @@ module.exports = function buildThemes(options) { type: "text/css", media: "(prefers-color-scheme: dark)", href: `./${darkThemeLocation}`, + class: "default-theme", } }, { @@ -224,6 +236,7 @@ module.exports = function buildThemes(options) { type: "text/css", media: "(prefers-color-scheme: light)", href: `./${lightThemeLocation}`, + class: "default-theme", } }, ]; @@ -261,6 +274,7 @@ module.exports = function buildThemes(options) { themeSummary[`${name}-${variant}`] = assetHashedFileName; }); } + addThemesToConfig(bundle, themeSummary); this.emitFile({ type: "asset", name: "theme-summary.json", diff --git a/src/domain/session/settings/SettingsViewModel.js b/src/domain/session/settings/SettingsViewModel.js index 7464a659..649983e5 100644 --- a/src/domain/session/settings/SettingsViewModel.js +++ b/src/domain/session/settings/SettingsViewModel.js @@ -127,6 +127,14 @@ export class SettingsViewModel extends ViewModel { return this._formatBytes(this._estimate?.usage); } + get themes() { + return this.platform.themes; + } + + setTheme(name) { + this.platform.setTheme(name); + } + _formatBytes(n) { if (typeof n === "number") { return Math.round(n / (1024 * 1024)).toFixed(1) + " MB"; diff --git a/src/platform/web/Platform.js b/src/platform/web/Platform.js index 7d66301d..6ed4509d 100644 --- a/src/platform/web/Platform.js +++ b/src/platform/web/Platform.js @@ -307,6 +307,33 @@ export class Platform { return DEFINE_VERSION; } + get themes() { + return Object.keys(this.config["themes"]); + } + + setTheme(themeName) { + const themeLocation = this.config["themes"][themeName]; + if (!themeLocation) { + throw new Error(`Cannot find theme location for theme "${themeName}"!`); + } + this._replaceStylesheet(themeLocation); + } + + _replaceStylesheet(newPath) { + // remove default theme + const defaultStylesheets = document.getElementsByClassName("default-theme"); + for (const tag of defaultStylesheets) { + tag.remove(); + } + // add new theme + const head = document.querySelector("head"); + const styleTag = document.createElement("link"); + styleTag.href = `./${newPath}`; + styleTag.rel = "stylesheet"; + styleTag.type = "text/css"; + head.appendChild(styleTag); + } + dispose() { this._disposables.dispose(); } diff --git a/src/platform/web/ui/session/settings/SettingsView.js b/src/platform/web/ui/session/settings/SettingsView.js index 93e44307..b2c78f58 100644 --- a/src/platform/web/ui/session/settings/SettingsView.js +++ b/src/platform/web/ui/session/settings/SettingsView.js @@ -97,6 +97,7 @@ export class SettingsView extends TemplateView { settingNodes.push( t.h3("Preferences"), row(t, vm.i18n`Scale down images when sending`, this._imageCompressionRange(t, vm)), + row(t, vm.i18n`Use the following theme`, this._themeOptions(t, vm)), ); settingNodes.push( t.h3("Application"), @@ -135,4 +136,12 @@ export class SettingsView extends TemplateView { vm.i18n`no resizing`; })]; } + + _themeOptions(t, vm) { + const optionTags = []; + for (const name of vm.themes) { + optionTags.push(t.option({value: name}, name)); + } + return t.select({onChange: (e) => vm.setTheme(e.target.value)}, optionTags); + } } From ecb83bb277d036767eb3ded9053a2979454dd3b4 Mon Sep 17 00:00:00 2001 From: RMidhunSuresh Date: Mon, 25 Apr 2022 14:47:16 +0530 Subject: [PATCH 195/238] Store and load theme from setting --- src/domain/session/settings/SettingsViewModel.js | 1 + src/platform/web/Platform.js | 8 ++++++++ 2 files changed, 9 insertions(+) diff --git a/src/domain/session/settings/SettingsViewModel.js b/src/domain/session/settings/SettingsViewModel.js index 649983e5..0bdb1355 100644 --- a/src/domain/session/settings/SettingsViewModel.js +++ b/src/domain/session/settings/SettingsViewModel.js @@ -133,6 +133,7 @@ export class SettingsViewModel extends ViewModel { setTheme(name) { this.platform.setTheme(name); + this.platform.settingsStorage.setString("theme", name); } _formatBytes(n) { diff --git a/src/platform/web/Platform.js b/src/platform/web/Platform.js index 6ed4509d..d1b96909 100644 --- a/src/platform/web/Platform.js +++ b/src/platform/web/Platform.js @@ -178,6 +178,14 @@ export class Platform { this._serviceWorkerHandler, this._config.push ); + await this._loadThemeFromSetting(); + } + + async _loadThemeFromSetting() { + const themeName = await this.settingsStorage.getString("theme"); + if (themeName) { + this.setTheme(themeName); + } } _createLogger(isDevelopment) { From c611d3f85c2ceab9967d4d8109df100b07ff4a41 Mon Sep 17 00:00:00 2001 From: RMidhunSuresh Date: Mon, 25 Apr 2022 15:56:45 +0530 Subject: [PATCH 196/238] Select current theme in dropdown --- .../build-plugins/rollup-plugin-build-themes.js | 9 ++++++--- .../session/settings/SettingsViewModel.js | 6 ++++++ src/platform/web/Platform.js | 17 +++++++++++++++++ .../web/ui/session/settings/SettingsView.js | 8 +++++--- 4 files changed, 34 insertions(+), 6 deletions(-) diff --git a/scripts/build-plugins/rollup-plugin-build-themes.js b/scripts/build-plugins/rollup-plugin-build-themes.js index 805590f4..f36db855 100644 --- a/scripts/build-plugins/rollup-plugin-build-themes.js +++ b/scripts/build-plugins/rollup-plugin-build-themes.js @@ -31,12 +31,13 @@ function appendVariablesToCSS(variables, cssSource) { return cssSource + getRootSectionWithVariables(variables); } -function addThemesToConfig(bundle, themeSummary) { +function addThemesToConfig(bundle, themeSummary, defaultThemes) { for (const [fileName, info] of Object.entries(bundle)) { if (fileName === "assets/config.json") { const source = new TextDecoder().decode(info.source); const config = JSON.parse(source); config["themes"] = themeSummary; + config["defaultTheme"] = defaultThemes; info.source = new TextEncoder().encode(JSON.stringify(config)); } } @@ -83,7 +84,7 @@ function parseBundle(bundle) { } module.exports = function buildThemes(options) { - let manifest, variants, defaultDark, defaultLight; + let manifest, variants, defaultDark, defaultLight,defaultThemes = {}; let isDevelopment = false; const virtualModuleId = '@theme/' const resolvedVirtualModuleId = '\0' + virtualModuleId; @@ -110,9 +111,11 @@ module.exports = function buildThemes(options) { // This is the default theme, stash the file name for later if (details.dark) { defaultDark = fileName; + defaultThemes["dark"] = `${name}-${variant}`; } else { defaultLight = fileName; + defaultThemes["light"] = `${name}-${variant}`; } } // emit the css as built theme bundle @@ -274,7 +277,7 @@ module.exports = function buildThemes(options) { themeSummary[`${name}-${variant}`] = assetHashedFileName; }); } - addThemesToConfig(bundle, themeSummary); + addThemesToConfig(bundle, themeSummary, defaultThemes); this.emitFile({ type: "asset", name: "theme-summary.json", diff --git a/src/domain/session/settings/SettingsViewModel.js b/src/domain/session/settings/SettingsViewModel.js index 0bdb1355..083c209e 100644 --- a/src/domain/session/settings/SettingsViewModel.js +++ b/src/domain/session/settings/SettingsViewModel.js @@ -50,6 +50,7 @@ export class SettingsViewModel extends ViewModel { this.minSentImageSizeLimit = 400; this.maxSentImageSizeLimit = 4000; this.pushNotifications = new PushNotificationStatus(); + this._activeTheme = undefined; } get _session() { @@ -76,6 +77,7 @@ export class SettingsViewModel extends ViewModel { this.sentImageSizeLimit = await this.platform.settingsStorage.getInt("sentImageSizeLimit"); this.pushNotifications.supported = await this.platform.notificationService.supportsPush(); this.pushNotifications.enabled = await this._session.arePushNotificationsEnabled(); + this._activeTheme = await this.platform.getActiveTheme(); this.emitChange(""); } @@ -131,6 +133,10 @@ export class SettingsViewModel extends ViewModel { return this.platform.themes; } + get activeTheme() { + return this._activeTheme; + } + setTheme(name) { this.platform.setTheme(name); this.platform.settingsStorage.setString("theme", name); diff --git a/src/platform/web/Platform.js b/src/platform/web/Platform.js index d1b96909..415081ba 100644 --- a/src/platform/web/Platform.js +++ b/src/platform/web/Platform.js @@ -319,6 +319,23 @@ export class Platform { return Object.keys(this.config["themes"]); } + async getActiveTheme() { + // check if theme is set via settings + let theme = await this.settingsStorage.getString("theme"); + if (theme) { + return theme; + } + // return default theme + if (window.matchMedia) { + if (window.matchMedia("(prefers-color-scheme: dark)")) { + return this.config["defaultTheme"].dark; + } else if (window.matchMedia("(prefers-color-scheme: light)")) { + return this.config["defaultTheme"].light; + } + } + return undefined; + } + setTheme(themeName) { const themeLocation = this.config["themes"][themeName]; if (!themeLocation) { diff --git a/src/platform/web/ui/session/settings/SettingsView.js b/src/platform/web/ui/session/settings/SettingsView.js index b2c78f58..3e62bba6 100644 --- a/src/platform/web/ui/session/settings/SettingsView.js +++ b/src/platform/web/ui/session/settings/SettingsView.js @@ -97,7 +97,9 @@ export class SettingsView extends TemplateView { settingNodes.push( t.h3("Preferences"), row(t, vm.i18n`Scale down images when sending`, this._imageCompressionRange(t, vm)), - row(t, vm.i18n`Use the following theme`, this._themeOptions(t, vm)), + t.map(vm => vm.activeTheme, (theme, t) => { + return row(t, vm.i18n`Use the following theme`, this._themeOptions(t, vm, theme)); + }), ); settingNodes.push( t.h3("Application"), @@ -137,10 +139,10 @@ export class SettingsView extends TemplateView { })]; } - _themeOptions(t, vm) { + _themeOptions(t, vm, activeTheme) { const optionTags = []; for (const name of vm.themes) { - optionTags.push(t.option({value: name}, name)); + optionTags.push(t.option({value: name, selected: name === activeTheme}, name)); } return t.select({onChange: (e) => vm.setTheme(e.target.value)}, optionTags); } From 12a70469eb963b4ea01f442a603fce8282240afa Mon Sep 17 00:00:00 2001 From: RMidhunSuresh Date: Mon, 25 Apr 2022 16:01:02 +0530 Subject: [PATCH 197/238] Fix formatting --- scripts/build-plugins/rollup-plugin-build-themes.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/build-plugins/rollup-plugin-build-themes.js b/scripts/build-plugins/rollup-plugin-build-themes.js index f36db855..9776fb92 100644 --- a/scripts/build-plugins/rollup-plugin-build-themes.js +++ b/scripts/build-plugins/rollup-plugin-build-themes.js @@ -84,7 +84,7 @@ function parseBundle(bundle) { } module.exports = function buildThemes(options) { - let manifest, variants, defaultDark, defaultLight,defaultThemes = {}; + let manifest, variants, defaultDark, defaultLight, defaultThemes = {}; let isDevelopment = false; const virtualModuleId = '@theme/' const resolvedVirtualModuleId = '\0' + virtualModuleId; From af9cbd727f017ffd6d3b420938ec0023f5d535cc Mon Sep 17 00:00:00 2001 From: RMidhunSuresh Date: Mon, 25 Apr 2022 16:33:31 +0530 Subject: [PATCH 198/238] Remove existing stylesheets when changing themes --- scripts/build-plugins/rollup-plugin-build-themes.js | 4 ++-- src/platform/web/Platform.js | 10 ++++------ 2 files changed, 6 insertions(+), 8 deletions(-) diff --git a/scripts/build-plugins/rollup-plugin-build-themes.js b/scripts/build-plugins/rollup-plugin-build-themes.js index 9776fb92..f429bc80 100644 --- a/scripts/build-plugins/rollup-plugin-build-themes.js +++ b/scripts/build-plugins/rollup-plugin-build-themes.js @@ -229,7 +229,7 @@ module.exports = function buildThemes(options) { type: "text/css", media: "(prefers-color-scheme: dark)", href: `./${darkThemeLocation}`, - class: "default-theme", + class: "theme", } }, { @@ -239,7 +239,7 @@ module.exports = function buildThemes(options) { type: "text/css", media: "(prefers-color-scheme: light)", href: `./${lightThemeLocation}`, - class: "default-theme", + class: "theme", } }, ]; diff --git a/src/platform/web/Platform.js b/src/platform/web/Platform.js index 415081ba..24480f59 100644 --- a/src/platform/web/Platform.js +++ b/src/platform/web/Platform.js @@ -345,17 +345,15 @@ export class Platform { } _replaceStylesheet(newPath) { - // remove default theme - const defaultStylesheets = document.getElementsByClassName("default-theme"); - for (const tag of defaultStylesheets) { - tag.remove(); - } - // add new theme const head = document.querySelector("head"); + // remove default theme + document.querySelectorAll(".theme").forEach(e => e.remove()); + // add new theme const styleTag = document.createElement("link"); styleTag.href = `./${newPath}`; styleTag.rel = "stylesheet"; styleTag.type = "text/css"; + styleTag.className = "theme"; head.appendChild(styleTag); } From bb3368959ffd36782261a3dc1cea64e2f14ee6c8 Mon Sep 17 00:00:00 2001 From: RMidhunSuresh Date: Mon, 25 Apr 2022 17:24:58 +0530 Subject: [PATCH 199/238] Use sh instead of bash --- scripts/cleanup.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/cleanup.sh b/scripts/cleanup.sh index cdad04eb..6917af5e 100755 --- a/scripts/cleanup.sh +++ b/scripts/cleanup.sh @@ -1,3 +1,3 @@ -#!/bin/bash +#!/bin/sh # Remove icons created in .tmp rm -rf .tmp From c39f0d2efb5a39ee2b34998cb996a12dd82e845e Mon Sep 17 00:00:00 2001 From: RMidhunSuresh Date: Mon, 25 Apr 2022 17:49:16 +0530 Subject: [PATCH 200/238] Don't show theme chooser on dev --- src/domain/session/settings/SettingsViewModel.js | 4 +++- src/platform/web/ui/session/settings/SettingsView.js | 4 ++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/src/domain/session/settings/SettingsViewModel.js b/src/domain/session/settings/SettingsViewModel.js index 083c209e..9d2a4f3e 100644 --- a/src/domain/session/settings/SettingsViewModel.js +++ b/src/domain/session/settings/SettingsViewModel.js @@ -77,7 +77,9 @@ export class SettingsViewModel extends ViewModel { this.sentImageSizeLimit = await this.platform.settingsStorage.getInt("sentImageSizeLimit"); this.pushNotifications.supported = await this.platform.notificationService.supportsPush(); this.pushNotifications.enabled = await this._session.arePushNotificationsEnabled(); - this._activeTheme = await this.platform.getActiveTheme(); + if (!import.meta.env.DEV) { + this._activeTheme = await this.platform.getActiveTheme(); + } this.emitChange(""); } diff --git a/src/platform/web/ui/session/settings/SettingsView.js b/src/platform/web/ui/session/settings/SettingsView.js index 3e62bba6..eef3bc64 100644 --- a/src/platform/web/ui/session/settings/SettingsView.js +++ b/src/platform/web/ui/session/settings/SettingsView.js @@ -93,12 +93,12 @@ export class SettingsView extends TemplateView { ]); }) ); - + settingNodes.push( t.h3("Preferences"), row(t, vm.i18n`Scale down images when sending`, this._imageCompressionRange(t, vm)), t.map(vm => vm.activeTheme, (theme, t) => { - return row(t, vm.i18n`Use the following theme`, this._themeOptions(t, vm, theme)); + return !import.meta.env.DEV? row(t, vm.i18n`Use the following theme`, this._themeOptions(t, vm, theme)): null; }), ); settingNodes.push( From 5204fe5c99a6ad576c94718e9846351116e070e0 Mon Sep 17 00:00:00 2001 From: RMidhunSuresh Date: Tue, 10 May 2022 14:22:37 +0530 Subject: [PATCH 201/238] This emitFile is no longer needed --- scripts/build-plugins/rollup-plugin-build-themes.js | 5 ----- 1 file changed, 5 deletions(-) diff --git a/scripts/build-plugins/rollup-plugin-build-themes.js b/scripts/build-plugins/rollup-plugin-build-themes.js index f429bc80..871e8fab 100644 --- a/scripts/build-plugins/rollup-plugin-build-themes.js +++ b/scripts/build-plugins/rollup-plugin-build-themes.js @@ -278,11 +278,6 @@ module.exports = function buildThemes(options) { }); } addThemesToConfig(bundle, themeSummary, defaultThemes); - this.emitFile({ - type: "asset", - name: "theme-summary.json", - source: JSON.stringify(themeSummary), - }); }, } } From e8a4ab5ecc37dcb6150fb7573da12a773ea2ac72 Mon Sep 17 00:00:00 2001 From: RMidhunSuresh Date: Tue, 10 May 2022 16:58:06 +0530 Subject: [PATCH 202/238] built-asset must be a mapping A mapping from theme-name to location of css file --- scripts/build-plugins/rollup-plugin-build-themes.js | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/scripts/build-plugins/rollup-plugin-build-themes.js b/scripts/build-plugins/rollup-plugin-build-themes.js index 871e8fab..d3733e01 100644 --- a/scripts/build-plugins/rollup-plugin-build-themes.js +++ b/scripts/build-plugins/rollup-plugin-build-themes.js @@ -253,8 +253,13 @@ module.exports = function buildThemes(options) { const compiledVariables = options.compiledVariables.get(location); const derivedVariables = compiledVariables["derived-variables"]; const icon = compiledVariables["icon"]; + const builtAsset = {}; + for (const chunk of chunkArray) { + const [, name, variant] = chunk.fileName.match(/theme-(.+)-(.+)\.css/); + builtAsset[`${name}-${variant}`] = assetMap.get(chunk.fileName).fileName; + } manifest.source = { - "built-asset": chunkArray.map(chunk => assetMap.get(chunk.fileName).fileName), + "built-asset": builtAsset, "runtime-asset": assetMap.get(runtimeThemeChunk.fileName).fileName, "derived-variables": derivedVariables, "icon": icon From 855298bdaf7b8eaa7ae08d0dc154f524709df8f4 Mon Sep 17 00:00:00 2001 From: RMidhunSuresh Date: Wed, 11 May 2022 12:40:32 +0530 Subject: [PATCH 203/238] Read from manifest --- .../rollup-plugin-build-themes.js | 24 +++++++------------ src/platform/web/Platform.js | 17 +++++++++++-- 2 files changed, 24 insertions(+), 17 deletions(-) diff --git a/scripts/build-plugins/rollup-plugin-build-themes.js b/scripts/build-plugins/rollup-plugin-build-themes.js index d3733e01..e9251224 100644 --- a/scripts/build-plugins/rollup-plugin-build-themes.js +++ b/scripts/build-plugins/rollup-plugin-build-themes.js @@ -31,12 +31,12 @@ function appendVariablesToCSS(variables, cssSource) { return cssSource + getRootSectionWithVariables(variables); } -function addThemesToConfig(bundle, themeSummary, defaultThemes) { +function addThemesToConfig(bundle, manifestLocations, defaultThemes) { for (const [fileName, info] of Object.entries(bundle)) { if (fileName === "assets/config.json") { const source = new TextDecoder().decode(info.source); const config = JSON.parse(source); - config["themes"] = themeSummary; + config["themeManifests"] = manifestLocations; config["defaultTheme"] = defaultThemes; info.source = new TextEncoder().encode(JSON.stringify(config)); } @@ -247,13 +247,17 @@ module.exports = function buildThemes(options) { generateBundle(_, bundle) { const { assetMap, chunkMap, runtimeThemeChunk } = parseBundle(bundle); - const themeSummary = {}; + const manifestLocations = []; for (const [location, chunkArray] of chunkMap) { const manifest = require(`${location}/manifest.json`); const compiledVariables = options.compiledVariables.get(location); const derivedVariables = compiledVariables["derived-variables"]; const icon = compiledVariables["icon"]; const builtAsset = {}; + /** + * Generate a mapping from theme name to asset hashed location of said theme in build output. + * This can be used to enumerate themes during runtime. + */ for (const chunk of chunkArray) { const [, name, variant] = chunk.fileName.match(/theme-(.+)-(.+)\.css/); builtAsset[`${name}-${variant}`] = assetMap.get(chunk.fileName).fileName; @@ -265,24 +269,14 @@ module.exports = function buildThemes(options) { "icon": icon }; const name = `theme-${manifest.name}.json`; + manifestLocations.push(`assets/${name}`); this.emitFile({ type: "asset", name, source: JSON.stringify(manifest), }); } - /** - * Generate a mapping from theme name to asset hashed location of said theme in build output. - * This can be used to enumerate themes during runtime. - */ - for (const [, chunkArray] of chunkMap) { - chunkArray.forEach((chunk) => { - const [, name, variant] = chunk.fileName.match(/theme-(.+)-(.+)\.css/); - const assetHashedFileName = assetMap.get(chunk.fileName).fileName; - themeSummary[`${name}-${variant}`] = assetHashedFileName; - }); - } - addThemesToConfig(bundle, themeSummary, defaultThemes); + addThemesToConfig(bundle, manifestLocations, defaultThemes); }, } } diff --git a/src/platform/web/Platform.js b/src/platform/web/Platform.js index 24480f59..7b803e24 100644 --- a/src/platform/web/Platform.js +++ b/src/platform/web/Platform.js @@ -164,6 +164,8 @@ export class Platform { this._disposables = new Disposables(); this._olmPromise = undefined; this._workerPromise = undefined; + // Mapping from theme-name to asset hashed location of css file + this._themeMapping = {}; } async init() { @@ -178,9 +180,20 @@ export class Platform { this._serviceWorkerHandler, this._config.push ); + this._themeMapping = await this._createThemeMappingFromManifests(); await this._loadThemeFromSetting(); } + async _createThemeMappingFromManifests() { + const mapping = {}; + const manifests = this.config["themeManifests"]; + for (const manifestLocation of manifests) { + const {body}= await this.request(manifestLocation, {method: "GET", format: "json", cache: true}).response(); + Object.assign(mapping, body["source"]["built-asset"]); + } + return mapping; + } + async _loadThemeFromSetting() { const themeName = await this.settingsStorage.getString("theme"); if (themeName) { @@ -316,7 +329,7 @@ export class Platform { } get themes() { - return Object.keys(this.config["themes"]); + return Object.keys(this._themeMapping); } async getActiveTheme() { @@ -337,7 +350,7 @@ export class Platform { } setTheme(themeName) { - const themeLocation = this.config["themes"][themeName]; + const themeLocation = this._themeMapping[themeName]; if (!themeLocation) { throw new Error(`Cannot find theme location for theme "${themeName}"!`); } From 213f87378b5b9bd7e3fa573cf4c4e599ea974dfd Mon Sep 17 00:00:00 2001 From: RMidhunSuresh Date: Wed, 11 May 2022 12:46:12 +0530 Subject: [PATCH 204/238] Use t.if instead of t.map --- src/platform/web/ui/session/settings/SettingsView.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/platform/web/ui/session/settings/SettingsView.js b/src/platform/web/ui/session/settings/SettingsView.js index eef3bc64..a6b3e363 100644 --- a/src/platform/web/ui/session/settings/SettingsView.js +++ b/src/platform/web/ui/session/settings/SettingsView.js @@ -97,7 +97,7 @@ export class SettingsView extends TemplateView { settingNodes.push( t.h3("Preferences"), row(t, vm.i18n`Scale down images when sending`, this._imageCompressionRange(t, vm)), - t.map(vm => vm.activeTheme, (theme, t) => { + t.if(vm => vm.activeTheme, (theme, t) => { return !import.meta.env.DEV? row(t, vm.i18n`Use the following theme`, this._themeOptions(t, vm, theme)): null; }), ); From 2761789f452eed2457b257037534076986a69f0a Mon Sep 17 00:00:00 2001 From: RMidhunSuresh Date: Wed, 11 May 2022 14:58:14 +0530 Subject: [PATCH 205/238] Move theme code to separate file --- .../session/settings/SettingsViewModel.js | 7 +- src/platform/web/Platform.js | 55 ++------------ src/platform/web/ThemeLoader.ts | 76 +++++++++++++++++++ .../web/ui/session/settings/SettingsView.js | 7 +- 4 files changed, 90 insertions(+), 55 deletions(-) create mode 100644 src/platform/web/ThemeLoader.ts diff --git a/src/domain/session/settings/SettingsViewModel.js b/src/domain/session/settings/SettingsViewModel.js index 9d2a4f3e..5c89236f 100644 --- a/src/domain/session/settings/SettingsViewModel.js +++ b/src/domain/session/settings/SettingsViewModel.js @@ -78,7 +78,7 @@ export class SettingsViewModel extends ViewModel { this.pushNotifications.supported = await this.platform.notificationService.supportsPush(); this.pushNotifications.enabled = await this._session.arePushNotificationsEnabled(); if (!import.meta.env.DEV) { - this._activeTheme = await this.platform.getActiveTheme(); + this._activeTheme = await this.platform.themeLoader.getActiveTheme(); } this.emitChange(""); } @@ -132,7 +132,7 @@ export class SettingsViewModel extends ViewModel { } get themes() { - return this.platform.themes; + return this.platform.themeLoader.themes; } get activeTheme() { @@ -140,8 +140,7 @@ export class SettingsViewModel extends ViewModel { } setTheme(name) { - this.platform.setTheme(name); - this.platform.settingsStorage.setString("theme", name); + this.platform.themeLoader.setTheme(name); } _formatBytes(n) { diff --git a/src/platform/web/Platform.js b/src/platform/web/Platform.js index 7b803e24..2481d256 100644 --- a/src/platform/web/Platform.js +++ b/src/platform/web/Platform.js @@ -38,6 +38,7 @@ import {downloadInIframe} from "./dom/download.js"; import {Disposables} from "../../utils/Disposables"; import {parseHTML} from "./parsehtml.js"; import {handleAvatarError} from "./ui/avatar"; +import {ThemeLoader} from "./ThemeLoader"; function addScript(src) { return new Promise(function (resolve, reject) { @@ -164,8 +165,7 @@ export class Platform { this._disposables = new Disposables(); this._olmPromise = undefined; this._workerPromise = undefined; - // Mapping from theme-name to asset hashed location of css file - this._themeMapping = {}; + this._themeLoader = new ThemeLoader(this); } async init() { @@ -180,25 +180,9 @@ export class Platform { this._serviceWorkerHandler, this._config.push ); - this._themeMapping = await this._createThemeMappingFromManifests(); - await this._loadThemeFromSetting(); - } - - async _createThemeMappingFromManifests() { - const mapping = {}; const manifests = this.config["themeManifests"]; - for (const manifestLocation of manifests) { - const {body}= await this.request(manifestLocation, {method: "GET", format: "json", cache: true}).response(); - Object.assign(mapping, body["source"]["built-asset"]); - } - return mapping; - } - - async _loadThemeFromSetting() { - const themeName = await this.settingsStorage.getString("theme"); - if (themeName) { - this.setTheme(themeName); - } + await this._themeLoader.init(manifests); + await this._themeLoader.loadThemeFromSetting(); } _createLogger(isDevelopment) { @@ -328,36 +312,11 @@ export class Platform { return DEFINE_VERSION; } - get themes() { - return Object.keys(this._themeMapping); + get themeLoader() { + return this._themeLoader; } - async getActiveTheme() { - // check if theme is set via settings - let theme = await this.settingsStorage.getString("theme"); - if (theme) { - return theme; - } - // return default theme - if (window.matchMedia) { - if (window.matchMedia("(prefers-color-scheme: dark)")) { - return this.config["defaultTheme"].dark; - } else if (window.matchMedia("(prefers-color-scheme: light)")) { - return this.config["defaultTheme"].light; - } - } - return undefined; - } - - setTheme(themeName) { - const themeLocation = this._themeMapping[themeName]; - if (!themeLocation) { - throw new Error(`Cannot find theme location for theme "${themeName}"!`); - } - this._replaceStylesheet(themeLocation); - } - - _replaceStylesheet(newPath) { + replaceStylesheet(newPath) { const head = document.querySelector("head"); // remove default theme document.querySelectorAll(".theme").forEach(e => e.remove()); diff --git a/src/platform/web/ThemeLoader.ts b/src/platform/web/ThemeLoader.ts new file mode 100644 index 00000000..6c81a4d6 --- /dev/null +++ b/src/platform/web/ThemeLoader.ts @@ -0,0 +1,76 @@ +/* +Copyright 2020 The Matrix.org Foundation C.I.C. + +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 type {Platform} from "./Platform.js"; + +export class ThemeLoader { + private _platform: Platform; + private _themeMapping: Record = {}; + + constructor(platform: Platform) { + this._platform = platform; + } + + async init(manifestLocations: Iterable>): Promise { + for (const manifestLocation of manifestLocations) { + const { body } = await this._platform + .request(manifestLocation, { + method: "GET", + format: "json", + cache: true, + }) + .response(); + Object.assign(this._themeMapping, body["source"]["built-asset"]); + } + } + + async loadThemeFromSetting() { + const themeName = await this._platform.settingsStorage.getString( "theme"); + if (themeName) { + this.setTheme(themeName); + } + } + + setTheme(themeName: string) { + const themeLocation = this._themeMapping[themeName]; + if (!themeLocation) { + throw new Error( `Cannot find theme location for theme "${themeName}"!`); + } + this._platform.replaceStylesheet(themeLocation); + this._platform.settingsStorage.setString("theme", themeName); + } + + get themes(): string[] { + return Object.keys(this._themeMapping); + } + + async getActiveTheme(): Promise { + // check if theme is set via settings + let theme = await this._platform.settingsStorage.getString("theme"); + if (theme) { + return theme; + } + // return default theme + if (window.matchMedia) { + if (window.matchMedia("(prefers-color-scheme: dark)")) { + return this._platform.config["defaultTheme"].dark; + } else if (window.matchMedia("(prefers-color-scheme: light)")) { + return this._platform.config["defaultTheme"].light; + } + } + return undefined; + } +} diff --git a/src/platform/web/ui/session/settings/SettingsView.js b/src/platform/web/ui/session/settings/SettingsView.js index a6b3e363..69827c33 100644 --- a/src/platform/web/ui/session/settings/SettingsView.js +++ b/src/platform/web/ui/session/settings/SettingsView.js @@ -97,8 +97,8 @@ export class SettingsView extends TemplateView { settingNodes.push( t.h3("Preferences"), row(t, vm.i18n`Scale down images when sending`, this._imageCompressionRange(t, vm)), - t.if(vm => vm.activeTheme, (theme, t) => { - return !import.meta.env.DEV? row(t, vm.i18n`Use the following theme`, this._themeOptions(t, vm, theme)): null; + t.if(vm => vm.activeTheme, (t, vm) => { + return !import.meta.env.DEV? row(t, vm.i18n`Use the following theme`, this._themeOptions(t, vm)): null; }), ); settingNodes.push( @@ -139,7 +139,8 @@ export class SettingsView extends TemplateView { })]; } - _themeOptions(t, vm, activeTheme) { + _themeOptions(t, vm) { + const activeTheme = vm.activeTheme; const optionTags = []; for (const name of vm.themes) { optionTags.push(t.option({value: name, selected: name === activeTheme}, name)); From c26dc04b520c16f1e0de3c23df2da983ce8e0525 Mon Sep 17 00:00:00 2001 From: RMidhunSuresh Date: Wed, 11 May 2022 15:03:32 +0530 Subject: [PATCH 206/238] Fix type --- src/platform/web/ThemeLoader.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/platform/web/ThemeLoader.ts b/src/platform/web/ThemeLoader.ts index 6c81a4d6..256aed54 100644 --- a/src/platform/web/ThemeLoader.ts +++ b/src/platform/web/ThemeLoader.ts @@ -24,7 +24,7 @@ export class ThemeLoader { this._platform = platform; } - async init(manifestLocations: Iterable>): Promise { + async init(manifestLocations: string[]): Promise { for (const manifestLocation of manifestLocations) { const { body } = await this._platform .request(manifestLocation, { From 174adc075573716d2c697b4ac8af55d144714c47 Mon Sep 17 00:00:00 2001 From: RMidhunSuresh Date: Wed, 11 May 2022 15:38:37 +0530 Subject: [PATCH 207/238] Move platform dependent code to Platform --- src/platform/web/Platform.js | 11 ++++++++++- src/platform/web/ThemeLoader.ts | 10 ++++++---- 2 files changed, 16 insertions(+), 5 deletions(-) diff --git a/src/platform/web/Platform.js b/src/platform/web/Platform.js index 2481d256..e2628351 100644 --- a/src/platform/web/Platform.js +++ b/src/platform/web/Platform.js @@ -38,7 +38,7 @@ import {downloadInIframe} from "./dom/download.js"; import {Disposables} from "../../utils/Disposables"; import {parseHTML} from "./parsehtml.js"; import {handleAvatarError} from "./ui/avatar"; -import {ThemeLoader} from "./ThemeLoader"; +import {ThemeLoader, COLOR_SCHEME_PREFERENCE} from "./ThemeLoader"; function addScript(src) { return new Promise(function (resolve, reject) { @@ -316,6 +316,15 @@ export class Platform { return this._themeLoader; } + get preferredColorScheme() { + if (window.matchMedia("(prefers-color-scheme: dark)")) { + return COLOR_SCHEME_PREFERENCE.DARK; + } else if (window.matchMedia("(prefers-color-scheme: light)")) { + return COLOR_SCHEME_PREFERENCE.LIGHT; + } + return undefined; + } + replaceStylesheet(newPath) { const head = document.querySelector("head"); // remove default theme diff --git a/src/platform/web/ThemeLoader.ts b/src/platform/web/ThemeLoader.ts index 256aed54..713c7a62 100644 --- a/src/platform/web/ThemeLoader.ts +++ b/src/platform/web/ThemeLoader.ts @@ -16,6 +16,8 @@ limitations under the License. import type {Platform} from "./Platform.js"; +export enum COLOR_SCHEME_PREFERENCE { DARK, LIGHT, } + export class ThemeLoader { private _platform: Platform; private _themeMapping: Record = {}; @@ -64,12 +66,12 @@ export class ThemeLoader { return theme; } // return default theme - if (window.matchMedia) { - if (window.matchMedia("(prefers-color-scheme: dark)")) { + const preference = this._platform.preferredColorScheme; + switch (preference) { + case COLOR_SCHEME_PREFERENCE.DARK: return this._platform.config["defaultTheme"].dark; - } else if (window.matchMedia("(prefers-color-scheme: light)")) { + case COLOR_SCHEME_PREFERENCE.LIGHT: return this._platform.config["defaultTheme"].light; - } } return undefined; } From cc88245933596f55c787b0bc028dbf33bb2518e4 Mon Sep 17 00:00:00 2001 From: RMidhunSuresh Date: Wed, 11 May 2022 15:46:12 +0530 Subject: [PATCH 208/238] Create themeLoader only if not dev --- src/platform/web/Platform.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/platform/web/Platform.js b/src/platform/web/Platform.js index e2628351..ed117b3d 100644 --- a/src/platform/web/Platform.js +++ b/src/platform/web/Platform.js @@ -165,7 +165,7 @@ export class Platform { this._disposables = new Disposables(); this._olmPromise = undefined; this._workerPromise = undefined; - this._themeLoader = new ThemeLoader(this); + this._themeLoader = import.meta.env.DEV? null: new ThemeLoader(this); } async init() { @@ -181,8 +181,8 @@ export class Platform { this._config.push ); const manifests = this.config["themeManifests"]; - await this._themeLoader.init(manifests); - await this._themeLoader.loadThemeFromSetting(); + await this._themeLoader?.init(manifests); + await this._themeLoader?.loadThemeFromSetting(); } _createLogger(isDevelopment) { From 6fde6bbf6b63aecbe5348fa7497df0345bf9204a Mon Sep 17 00:00:00 2001 From: Bruno Windels <274386+bwindels@users.noreply.github.com> Date: Wed, 11 May 2022 14:58:57 +0200 Subject: [PATCH 209/238] bump sdk version --- scripts/sdk/base-manifest.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/sdk/base-manifest.json b/scripts/sdk/base-manifest.json index 312f2913..0ed9fdab 100644 --- a/scripts/sdk/base-manifest.json +++ b/scripts/sdk/base-manifest.json @@ -1,7 +1,7 @@ { "name": "hydrogen-view-sdk", "description": "Embeddable matrix client library, including view components", - "version": "0.0.11", + "version": "0.0.12", "main": "./hydrogen.es.js", "type": "module" } From d5bc9f5d7d340c62d2fe7cf33d9fbb0f8219b57c Mon Sep 17 00:00:00 2001 From: R Midhun Suresh Date: Thu, 12 May 2022 12:48:34 +0530 Subject: [PATCH 210/238] Update src/platform/web/Platform.js Co-authored-by: Bruno Windels <274386+bwindels@users.noreply.github.com> --- src/platform/web/Platform.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/platform/web/Platform.js b/src/platform/web/Platform.js index ed117b3d..e222691b 100644 --- a/src/platform/web/Platform.js +++ b/src/platform/web/Platform.js @@ -317,7 +317,7 @@ export class Platform { } get preferredColorScheme() { - if (window.matchMedia("(prefers-color-scheme: dark)")) { + if (window.matchMedia("(prefers-color-scheme: dark)").matches) { return COLOR_SCHEME_PREFERENCE.DARK; } else if (window.matchMedia("(prefers-color-scheme: light)")) { return COLOR_SCHEME_PREFERENCE.LIGHT; From 4231037345708677a85fdc4d43425098910eb623 Mon Sep 17 00:00:00 2001 From: R Midhun Suresh Date: Thu, 12 May 2022 12:48:41 +0530 Subject: [PATCH 211/238] Update src/platform/web/Platform.js Co-authored-by: Bruno Windels <274386+bwindels@users.noreply.github.com> --- src/platform/web/Platform.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/platform/web/Platform.js b/src/platform/web/Platform.js index e222691b..5a921aaf 100644 --- a/src/platform/web/Platform.js +++ b/src/platform/web/Platform.js @@ -319,7 +319,7 @@ export class Platform { get preferredColorScheme() { if (window.matchMedia("(prefers-color-scheme: dark)").matches) { return COLOR_SCHEME_PREFERENCE.DARK; - } else if (window.matchMedia("(prefers-color-scheme: light)")) { + } else if (window.matchMedia("(prefers-color-scheme: light)").matches) { return COLOR_SCHEME_PREFERENCE.LIGHT; } return undefined; From b3063447399812d22e7eddeaaee61223f352c219 Mon Sep 17 00:00:00 2001 From: RMidhunSuresh Date: Thu, 12 May 2022 12:55:08 +0530 Subject: [PATCH 212/238] Add explaining comment --- src/platform/web/ThemeLoader.ts | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/platform/web/ThemeLoader.ts b/src/platform/web/ThemeLoader.ts index 713c7a62..ab799b0a 100644 --- a/src/platform/web/ThemeLoader.ts +++ b/src/platform/web/ThemeLoader.ts @@ -35,6 +35,11 @@ export class ThemeLoader { cache: true, }) .response(); + /* + After build has finished, the source section of each theme manifest + contains `built-asset` which is a mapping from the theme-name to the + location of the css file in build. + */ Object.assign(this._themeMapping, body["source"]["built-asset"]); } } From 654e83a5f98d18c5e5d1339be677e3b8e81fded2 Mon Sep 17 00:00:00 2001 From: RMidhunSuresh Date: Thu, 12 May 2022 13:28:11 +0530 Subject: [PATCH 213/238] Remove method --- src/platform/web/Platform.js | 2 +- src/platform/web/ThemeLoader.ts | 7 ------- 2 files changed, 1 insertion(+), 8 deletions(-) diff --git a/src/platform/web/Platform.js b/src/platform/web/Platform.js index 5a921aaf..662525d8 100644 --- a/src/platform/web/Platform.js +++ b/src/platform/web/Platform.js @@ -182,7 +182,7 @@ export class Platform { ); const manifests = this.config["themeManifests"]; await this._themeLoader?.init(manifests); - await this._themeLoader?.loadThemeFromSetting(); + this._themeLoader?.setTheme(await this._themeLoader.getActiveTheme()); } _createLogger(isDevelopment) { diff --git a/src/platform/web/ThemeLoader.ts b/src/platform/web/ThemeLoader.ts index ab799b0a..34b3c7d0 100644 --- a/src/platform/web/ThemeLoader.ts +++ b/src/platform/web/ThemeLoader.ts @@ -44,13 +44,6 @@ export class ThemeLoader { } } - async loadThemeFromSetting() { - const themeName = await this._platform.settingsStorage.getString( "theme"); - if (themeName) { - this.setTheme(themeName); - } - } - setTheme(themeName: string) { const themeLocation = this._themeMapping[themeName]; if (!themeLocation) { From 0984aeb5708c3fe0cbbc925055ab8d2c42b0d3a1 Mon Sep 17 00:00:00 2001 From: RMidhunSuresh Date: Thu, 12 May 2022 13:39:57 +0530 Subject: [PATCH 214/238] Move code to ThemeLoader --- src/platform/web/Platform.js | 9 --------- src/platform/web/ThemeLoader.ts | 10 ++++------ 2 files changed, 4 insertions(+), 15 deletions(-) diff --git a/src/platform/web/Platform.js b/src/platform/web/Platform.js index 662525d8..a142ace7 100644 --- a/src/platform/web/Platform.js +++ b/src/platform/web/Platform.js @@ -316,15 +316,6 @@ export class Platform { return this._themeLoader; } - get preferredColorScheme() { - if (window.matchMedia("(prefers-color-scheme: dark)").matches) { - return COLOR_SCHEME_PREFERENCE.DARK; - } else if (window.matchMedia("(prefers-color-scheme: light)").matches) { - return COLOR_SCHEME_PREFERENCE.LIGHT; - } - return undefined; - } - replaceStylesheet(newPath) { const head = document.querySelector("head"); // remove default theme diff --git a/src/platform/web/ThemeLoader.ts b/src/platform/web/ThemeLoader.ts index 34b3c7d0..4c0ec6d3 100644 --- a/src/platform/web/ThemeLoader.ts +++ b/src/platform/web/ThemeLoader.ts @@ -64,12 +64,10 @@ export class ThemeLoader { return theme; } // return default theme - const preference = this._platform.preferredColorScheme; - switch (preference) { - case COLOR_SCHEME_PREFERENCE.DARK: - return this._platform.config["defaultTheme"].dark; - case COLOR_SCHEME_PREFERENCE.LIGHT: - return this._platform.config["defaultTheme"].light; + if (window.matchMedia("(prefers-color-scheme: dark)").matches) { + return this._platform.config["defaultTheme"].dark; + } else if (window.matchMedia("(prefers-color-scheme: light)").matches) { + return this._platform.config["defaultTheme"].light; } return undefined; } From e63440527a99172b31b684e9f98551e171674342 Mon Sep 17 00:00:00 2001 From: RMidhunSuresh Date: Thu, 12 May 2022 13:43:19 +0530 Subject: [PATCH 215/238] Move condition to binding --- src/platform/web/ui/session/settings/SettingsView.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/platform/web/ui/session/settings/SettingsView.js b/src/platform/web/ui/session/settings/SettingsView.js index 69827c33..dd7bbc03 100644 --- a/src/platform/web/ui/session/settings/SettingsView.js +++ b/src/platform/web/ui/session/settings/SettingsView.js @@ -97,8 +97,8 @@ export class SettingsView extends TemplateView { settingNodes.push( t.h3("Preferences"), row(t, vm.i18n`Scale down images when sending`, this._imageCompressionRange(t, vm)), - t.if(vm => vm.activeTheme, (t, vm) => { - return !import.meta.env.DEV? row(t, vm.i18n`Use the following theme`, this._themeOptions(t, vm)): null; + t.if(vm => !import.meta.env.DEV && vm.activeTheme, (t, vm) => { + return row(t, vm.i18n`Use the following theme`, this._themeOptions(t, vm)); }), ); settingNodes.push( From 4ddfd3b5086d2fcc247bc14cae8386a8302ef6a1 Mon Sep 17 00:00:00 2001 From: RMidhunSuresh Date: Thu, 12 May 2022 14:31:28 +0530 Subject: [PATCH 216/238] built-asset --> built-assets --- scripts/build-plugins/rollup-plugin-build-themes.js | 6 +++--- src/platform/web/ThemeLoader.ts | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/scripts/build-plugins/rollup-plugin-build-themes.js b/scripts/build-plugins/rollup-plugin-build-themes.js index e9251224..c45c5aaa 100644 --- a/scripts/build-plugins/rollup-plugin-build-themes.js +++ b/scripts/build-plugins/rollup-plugin-build-themes.js @@ -253,17 +253,17 @@ module.exports = function buildThemes(options) { const compiledVariables = options.compiledVariables.get(location); const derivedVariables = compiledVariables["derived-variables"]; const icon = compiledVariables["icon"]; - const builtAsset = {}; + const builtAssets = {}; /** * Generate a mapping from theme name to asset hashed location of said theme in build output. * This can be used to enumerate themes during runtime. */ for (const chunk of chunkArray) { const [, name, variant] = chunk.fileName.match(/theme-(.+)-(.+)\.css/); - builtAsset[`${name}-${variant}`] = assetMap.get(chunk.fileName).fileName; + builtAssets[`${name}-${variant}`] = assetMap.get(chunk.fileName).fileName; } manifest.source = { - "built-asset": builtAsset, + "built-assets": builtAssets, "runtime-asset": assetMap.get(runtimeThemeChunk.fileName).fileName, "derived-variables": derivedVariables, "icon": icon diff --git a/src/platform/web/ThemeLoader.ts b/src/platform/web/ThemeLoader.ts index 4c0ec6d3..0234a04a 100644 --- a/src/platform/web/ThemeLoader.ts +++ b/src/platform/web/ThemeLoader.ts @@ -37,10 +37,10 @@ export class ThemeLoader { .response(); /* After build has finished, the source section of each theme manifest - contains `built-asset` which is a mapping from the theme-name to the + contains `built-assets` which is a mapping from the theme-name to the location of the css file in build. */ - Object.assign(this._themeMapping, body["source"]["built-asset"]); + Object.assign(this._themeMapping, body["source"]["built-assets"]); } } From 9ba153439026535cf28b3301537fbf9c442e608d Mon Sep 17 00:00:00 2001 From: RMidhunSuresh Date: Thu, 12 May 2022 16:03:06 +0530 Subject: [PATCH 217/238] Remove unused import --- src/platform/web/Platform.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/platform/web/Platform.js b/src/platform/web/Platform.js index a142ace7..02990ea0 100644 --- a/src/platform/web/Platform.js +++ b/src/platform/web/Platform.js @@ -38,7 +38,7 @@ import {downloadInIframe} from "./dom/download.js"; import {Disposables} from "../../utils/Disposables"; import {parseHTML} from "./parsehtml.js"; import {handleAvatarError} from "./ui/avatar"; -import {ThemeLoader, COLOR_SCHEME_PREFERENCE} from "./ThemeLoader"; +import {ThemeLoader} from "./ThemeLoader"; function addScript(src) { return new Promise(function (resolve, reject) { From 34e8b609174822fab062e1773bae98f0ede29f89 Mon Sep 17 00:00:00 2001 From: RMidhunSuresh Date: Thu, 12 May 2022 16:02:03 +0530 Subject: [PATCH 218/238] Create config.json in root --- .../build-plugins/rollup-plugin-build-themes.js | 2 +- vite.config.js | 16 +++++++++++----- 2 files changed, 12 insertions(+), 6 deletions(-) diff --git a/scripts/build-plugins/rollup-plugin-build-themes.js b/scripts/build-plugins/rollup-plugin-build-themes.js index c45c5aaa..3cb9ed0c 100644 --- a/scripts/build-plugins/rollup-plugin-build-themes.js +++ b/scripts/build-plugins/rollup-plugin-build-themes.js @@ -33,7 +33,7 @@ function appendVariablesToCSS(variables, cssSource) { function addThemesToConfig(bundle, manifestLocations, defaultThemes) { for (const [fileName, info] of Object.entries(bundle)) { - if (fileName === "assets/config.json") { + if (fileName === "config.json") { const source = new TextDecoder().decode(info.source); const config = JSON.parse(source); config["themeManifests"] = manifestLocations; diff --git a/vite.config.js b/vite.config.js index a44d0917..2e4895d2 100644 --- a/vite.config.js +++ b/vite.config.js @@ -16,11 +16,17 @@ export default defineConfig(({mode}) => { sourcemap: true, rollupOptions: { output: { - assetFileNames: (asset) => - asset.name.includes("config.json") || - asset.name.match(/theme-.+\.json/) - ? "assets/[name][extname]" - : "assets/[name].[hash][extname]", + assetFileNames: (asset) => { + if (asset.name.includes("config.json")) { + return "[name][extname]"; + } + else if (asset.name.match(/theme-.+\.json/)) { + return "assets/[name][extname]"; + } + else { + return "assets/[name].[hash][extname]"; + } + } }, }, }, From b725269c7a6ea62cb0a1e2aaec90c9b8b54f7001 Mon Sep 17 00:00:00 2001 From: Eric Eastwood Date: Wed, 18 May 2022 00:21:56 -0500 Subject: [PATCH 219/238] Clean up index.html in the right spot --- scripts/sdk/build.sh | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/scripts/sdk/build.sh b/scripts/sdk/build.sh index 992d2efc..5e1632d3 100755 --- a/scripts/sdk/build.sh +++ b/scripts/sdk/build.sh @@ -16,10 +16,10 @@ mkdir target/paths # this doesn't work, the ?url imports need to be in the consuming project, so disable for now # ./scripts/sdk/transform-paths.js ./src/platform/web/sdk/paths/vite.js ./target/paths/vite.js cp doc/SDK.md target/README.md -pushd target -pushd asset-build/assets +pushd target/asset-build +rm index.html +popd +pushd target/asset-build/assets # Remove all `*.wasm` and `*.js` files except for `main.js` rm !(main).js *.wasm popd -rm index.html -popd From 1b22a48b5415ddb8803c1d1200d2d201259231a4 Mon Sep 17 00:00:00 2001 From: RMidhunSuresh Date: Wed, 18 May 2022 14:23:41 +0530 Subject: [PATCH 220/238] Treat theme-manifests the same way as config --- src/platform/web/sw.js | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/src/platform/web/sw.js b/src/platform/web/sw.js index a9a92979..ce83e8c2 100644 --- a/src/platform/web/sw.js +++ b/src/platform/web/sw.js @@ -95,8 +95,8 @@ let pendingFetchAbortController = new AbortController(); async function handleRequest(request) { try { - if (request.url.includes("config.json")) { - return handleConfigRequest(request); + if (request.url.includes("config.json") || /theme-.+\.json/.test(request.url)) { + return handleSpecialRequest(request); } const url = new URL(request.url); // rewrite / to /index.html so it hits the cache @@ -123,9 +123,13 @@ async function handleRequest(request) { } } -async function handleConfigRequest(request) { +/** + * For some files (config.json and theme manifests) we satisfy the request from cache, + * but at the same time we refresh the cache with up-to-date content of the file + */ +async function handleSpecialRequest(request) { let response = await readCache(request); - const networkResponsePromise = fetchAndUpdateConfig(request); + const networkResponsePromise = fetchAndUpdateCache(request); if (response) { return response; } else { @@ -133,7 +137,7 @@ async function handleConfigRequest(request) { } } -async function fetchAndUpdateConfig(request) { +async function fetchAndUpdateCache(request) { const response = await fetch(request, { signal: pendingFetchAbortController.signal, headers: { From 660a08db3eb967b3adb9fc96549bef27ba989359 Mon Sep 17 00:00:00 2001 From: RMidhunSuresh Date: Wed, 18 May 2022 14:41:52 +0530 Subject: [PATCH 221/238] Give a better name --- src/platform/web/sw.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/platform/web/sw.js b/src/platform/web/sw.js index ce83e8c2..6b07f42e 100644 --- a/src/platform/web/sw.js +++ b/src/platform/web/sw.js @@ -96,7 +96,7 @@ let pendingFetchAbortController = new AbortController(); async function handleRequest(request) { try { if (request.url.includes("config.json") || /theme-.+\.json/.test(request.url)) { - return handleSpecialRequest(request); + return handleStaleWhileRevalidateRequest(request); } const url = new URL(request.url); // rewrite / to /index.html so it hits the cache @@ -124,10 +124,10 @@ async function handleRequest(request) { } /** - * For some files (config.json and theme manifests) we satisfy the request from cache, - * but at the same time we refresh the cache with up-to-date content of the file + * Stale-while-revalidate caching for certain files + * see https://developer.chrome.com/docs/workbox/caching-strategies-overview/#stale-while-revalidate */ -async function handleSpecialRequest(request) { +async function handleStaleWhileRevalidateRequest(request) { let response = await readCache(request); const networkResponsePromise = fetchAndUpdateCache(request); if (response) { From 7426d17e33784baaab4daf219374163d37712f89 Mon Sep 17 00:00:00 2001 From: RMidhunSuresh Date: Wed, 18 May 2022 15:42:49 +0530 Subject: [PATCH 222/238] Precache config and theme manifest --- vite.config.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vite.config.js b/vite.config.js index a44d0917..362f8f81 100644 --- a/vite.config.js +++ b/vite.config.js @@ -37,7 +37,7 @@ export default defineConfig(({mode}) => { // important this comes before service worker // otherwise the manifest and the icons it refers to won't be cached injectWebManifest("assets/manifest.json"), - injectServiceWorker("./src/platform/web/sw.js", ["index.html"], { + injectServiceWorker("./src/platform/web/sw.js", ["index.html", "assets/config.json", "assets/theme-element.json"], { // placeholders to replace at end of build by chunk name index: { DEFINE_GLOBAL_HASH: definePlaceholders.DEFINE_GLOBAL_HASH, From 7952a34d64d43cbd56ec789909ee76a1063efc51 Mon Sep 17 00:00:00 2001 From: RMidhunSuresh Date: Wed, 18 May 2022 16:09:09 +0530 Subject: [PATCH 223/238] Add logging --- src/platform/web/Platform.js | 28 +++++++------ src/platform/web/ThemeLoader.ts | 72 ++++++++++++++++++--------------- 2 files changed, 54 insertions(+), 46 deletions(-) diff --git a/src/platform/web/Platform.js b/src/platform/web/Platform.js index 02990ea0..25439ad0 100644 --- a/src/platform/web/Platform.js +++ b/src/platform/web/Platform.js @@ -169,20 +169,22 @@ export class Platform { } async init() { - if (!this._config) { - if (!this._configURL) { - throw new Error("Neither config nor configURL was provided!"); + await this.logger.run("Platform init", async () => { + if (!this._config) { + if (!this._configURL) { + throw new Error("Neither config nor configURL was provided!"); + } + const {body}= await this.request(this._configURL, {method: "GET", format: "json", cache: true}).response(); + this._config = body; } - const {body}= await this.request(this._configURL, {method: "GET", format: "json", cache: true}).response(); - this._config = body; - } - this.notificationService = new NotificationService( - this._serviceWorkerHandler, - this._config.push - ); - const manifests = this.config["themeManifests"]; - await this._themeLoader?.init(manifests); - this._themeLoader?.setTheme(await this._themeLoader.getActiveTheme()); + this.notificationService = new NotificationService( + this._serviceWorkerHandler, + this._config.push + ); + const manifests = this.config["themeManifests"]; + await this._themeLoader?.init(manifests); + this._themeLoader?.setTheme(await this._themeLoader.getActiveTheme()); + }); } _createLogger(isDevelopment) { diff --git a/src/platform/web/ThemeLoader.ts b/src/platform/web/ThemeLoader.ts index 0234a04a..5d93ad68 100644 --- a/src/platform/web/ThemeLoader.ts +++ b/src/platform/web/ThemeLoader.ts @@ -27,30 +27,34 @@ export class ThemeLoader { } async init(manifestLocations: string[]): Promise { - for (const manifestLocation of manifestLocations) { - const { body } = await this._platform - .request(manifestLocation, { - method: "GET", - format: "json", - cache: true, - }) - .response(); - /* - After build has finished, the source section of each theme manifest - contains `built-assets` which is a mapping from the theme-name to the - location of the css file in build. - */ - Object.assign(this._themeMapping, body["source"]["built-assets"]); - } + await this._platform.logger.run("ThemeLoader.init", async () => { + for (const manifestLocation of manifestLocations) { + const { body } = await this._platform + .request(manifestLocation, { + method: "GET", + format: "json", + cache: true, + }) + .response(); + /* + After build has finished, the source section of each theme manifest + contains `built-assets` which is a mapping from the theme-name to the + location of the css file in build. + */ + Object.assign(this._themeMapping, body["source"]["built-assets"]); + } + }); } setTheme(themeName: string) { - const themeLocation = this._themeMapping[themeName]; - if (!themeLocation) { - throw new Error( `Cannot find theme location for theme "${themeName}"!`); - } - this._platform.replaceStylesheet(themeLocation); - this._platform.settingsStorage.setString("theme", themeName); + this._platform.logger.run("ThemeLoader.setTheme", () => { + const themeLocation = this._themeMapping[themeName]; + if (!themeLocation) { + throw new Error( `Cannot find theme location for theme "${themeName}"!`); + } + this._platform.replaceStylesheet(themeLocation); + this._platform.settingsStorage.setString("theme", themeName); + }); } get themes(): string[] { @@ -58,17 +62,19 @@ export class ThemeLoader { } async getActiveTheme(): Promise { - // check if theme is set via settings - let theme = await this._platform.settingsStorage.getString("theme"); - if (theme) { - return theme; - } - // return default theme - if (window.matchMedia("(prefers-color-scheme: dark)").matches) { - return this._platform.config["defaultTheme"].dark; - } else if (window.matchMedia("(prefers-color-scheme: light)").matches) { - return this._platform.config["defaultTheme"].light; - } - return undefined; + return await this._platform.logger.run("ThemeLoader.getActiveTheme", async () => { + // check if theme is set via settings + let theme = await this._platform.settingsStorage.getString("theme"); + if (theme) { + return theme; + } + // return default theme + if (window.matchMedia("(prefers-color-scheme: dark)").matches) { + return this._platform.config["defaultTheme"].dark; + } else if (window.matchMedia("(prefers-color-scheme: light)").matches) { + return this._platform.config["defaultTheme"].light; + } + return undefined; + }); } } From 683ffa9ed376ae3d5197c6dda5789db2588b33dc Mon Sep 17 00:00:00 2001 From: RMidhunSuresh Date: Wed, 18 May 2022 17:31:17 +0530 Subject: [PATCH 224/238] injectServiceWorker plugin should accept callback --- scripts/build-plugins/service-worker.js | 3 ++- vite.config.js | 15 ++++++++++++++- 2 files changed, 16 insertions(+), 2 deletions(-) diff --git a/scripts/build-plugins/service-worker.js b/scripts/build-plugins/service-worker.js index 805f6000..85619545 100644 --- a/scripts/build-plugins/service-worker.js +++ b/scripts/build-plugins/service-worker.js @@ -8,7 +8,7 @@ function contentHash(str) { return hasher.digest(); } -function injectServiceWorker(swFile, otherUnhashedFiles, placeholdersPerChunk) { +function injectServiceWorker(swFile, findUnhashedFileNamesFromBundle, placeholdersPerChunk) { const swName = path.basename(swFile); let root; let version; @@ -31,6 +31,7 @@ function injectServiceWorker(swFile, otherUnhashedFiles, placeholdersPerChunk) { logger = config.logger; }, generateBundle: async function(options, bundle) { + const otherUnhashedFiles = findUnhashedFileNamesFromBundle(bundle); const unhashedFilenames = [swName].concat(otherUnhashedFiles); const unhashedFileContentMap = unhashedFilenames.reduce((map, fileName) => { const chunkOrAsset = bundle[fileName]; diff --git a/vite.config.js b/vite.config.js index 362f8f81..53a7f452 100644 --- a/vite.config.js +++ b/vite.config.js @@ -37,7 +37,7 @@ export default defineConfig(({mode}) => { // important this comes before service worker // otherwise the manifest and the icons it refers to won't be cached injectWebManifest("assets/manifest.json"), - injectServiceWorker("./src/platform/web/sw.js", ["index.html", "assets/config.json", "assets/theme-element.json"], { + injectServiceWorker("./src/platform/web/sw.js", findUnhashedFileNamesFromBundle, { // placeholders to replace at end of build by chunk name index: { DEFINE_GLOBAL_HASH: definePlaceholders.DEFINE_GLOBAL_HASH, @@ -48,3 +48,16 @@ export default defineConfig(({mode}) => { define: definePlaceholders, }); }); + +function findUnhashedFileNamesFromBundle(bundle) { + const names = ["index.html"]; + for (const fileName of Object.keys(bundle)) { + if (fileName.includes("config.json")) { + names.push(fileName); + } + if (/theme-.+\.json/.test(fileName)) { + names.push(fileName); + } + } + return names; +} From a550788788b16aed4a7c29dfeace5800355eb40c Mon Sep 17 00:00:00 2001 From: RMidhunSuresh Date: Wed, 18 May 2022 18:56:28 +0530 Subject: [PATCH 225/238] Remove some logging + use wrapOrRun --- src/platform/web/Platform.js | 4 +- src/platform/web/ThemeLoader.ts | 65 +++++++++++++++------------------ 2 files changed, 32 insertions(+), 37 deletions(-) diff --git a/src/platform/web/Platform.js b/src/platform/web/Platform.js index 25439ad0..2a691580 100644 --- a/src/platform/web/Platform.js +++ b/src/platform/web/Platform.js @@ -169,7 +169,7 @@ export class Platform { } async init() { - await this.logger.run("Platform init", async () => { + await this.logger.run("Platform init", async (log) => { if (!this._config) { if (!this._configURL) { throw new Error("Neither config nor configURL was provided!"); @@ -183,7 +183,7 @@ export class Platform { ); const manifests = this.config["themeManifests"]; await this._themeLoader?.init(manifests); - this._themeLoader?.setTheme(await this._themeLoader.getActiveTheme()); + this._themeLoader?.setTheme(await this._themeLoader.getActiveTheme(), log); }); } diff --git a/src/platform/web/ThemeLoader.ts b/src/platform/web/ThemeLoader.ts index 5d93ad68..72865279 100644 --- a/src/platform/web/ThemeLoader.ts +++ b/src/platform/web/ThemeLoader.ts @@ -14,10 +14,9 @@ See the License for the specific language governing permissions and limitations under the License. */ +import type {ILogItem} from "../../logging/types.js"; import type {Platform} from "./Platform.js"; -export enum COLOR_SCHEME_PREFERENCE { DARK, LIGHT, } - export class ThemeLoader { private _platform: Platform; private _themeMapping: Record = {}; @@ -27,27 +26,25 @@ export class ThemeLoader { } async init(manifestLocations: string[]): Promise { - await this._platform.logger.run("ThemeLoader.init", async () => { - for (const manifestLocation of manifestLocations) { - const { body } = await this._platform - .request(manifestLocation, { - method: "GET", - format: "json", - cache: true, - }) - .response(); - /* - After build has finished, the source section of each theme manifest - contains `built-assets` which is a mapping from the theme-name to the - location of the css file in build. - */ - Object.assign(this._themeMapping, body["source"]["built-assets"]); - } - }); + for (const manifestLocation of manifestLocations) { + const { body } = await this._platform + .request(manifestLocation, { + method: "GET", + format: "json", + cache: true, + }) + .response(); + /* + After build has finished, the source section of each theme manifest + contains `built-assets` which is a mapping from the theme-name to the + location of the css file in build. + */ + Object.assign(this._themeMapping, body["source"]["built-assets"]); + } } - setTheme(themeName: string) { - this._platform.logger.run("ThemeLoader.setTheme", () => { + setTheme(themeName: string, log?: ILogItem) { + this._platform.logger.wrapOrRun(log, "setTheme", () => { const themeLocation = this._themeMapping[themeName]; if (!themeLocation) { throw new Error( `Cannot find theme location for theme "${themeName}"!`); @@ -62,19 +59,17 @@ export class ThemeLoader { } async getActiveTheme(): Promise { - return await this._platform.logger.run("ThemeLoader.getActiveTheme", async () => { - // check if theme is set via settings - let theme = await this._platform.settingsStorage.getString("theme"); - if (theme) { - return theme; - } - // return default theme - if (window.matchMedia("(prefers-color-scheme: dark)").matches) { - return this._platform.config["defaultTheme"].dark; - } else if (window.matchMedia("(prefers-color-scheme: light)").matches) { - return this._platform.config["defaultTheme"].light; - } - return undefined; - }); + // check if theme is set via settings + let theme = await this._platform.settingsStorage.getString("theme"); + if (theme) { + return theme; + } + // return default theme + if (window.matchMedia("(prefers-color-scheme: dark)").matches) { + return this._platform.config["defaultTheme"].dark; + } else if (window.matchMedia("(prefers-color-scheme: light)").matches) { + return this._platform.config["defaultTheme"].light; + } + return undefined; } } From 03ab1ee2c7a48721e763f8b301c162b6625940b8 Mon Sep 17 00:00:00 2001 From: Bruno Windels <274386+bwindels@users.noreply.github.com> Date: Wed, 18 May 2022 17:48:03 +0200 Subject: [PATCH 226/238] log theme being loaded --- src/platform/web/ThemeLoader.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/platform/web/ThemeLoader.ts b/src/platform/web/ThemeLoader.ts index 72865279..d9aaabb6 100644 --- a/src/platform/web/ThemeLoader.ts +++ b/src/platform/web/ThemeLoader.ts @@ -44,7 +44,7 @@ export class ThemeLoader { } setTheme(themeName: string, log?: ILogItem) { - this._platform.logger.wrapOrRun(log, "setTheme", () => { + this._platform.logger.wrapOrRun(log, {l: "change theme", id: themeName}, () => { const themeLocation = this._themeMapping[themeName]; if (!themeLocation) { throw new Error( `Cannot find theme location for theme "${themeName}"!`); From 7a197c0a1a4319b329ec025fb5fdea23a7950c6a Mon Sep 17 00:00:00 2001 From: Bruno Windels <274386+bwindels@users.noreply.github.com> Date: Wed, 18 May 2022 20:44:04 +0200 Subject: [PATCH 227/238] add deployment instruction now that we have a config file --- README.md | 23 +++++++++++++++++++++-- 1 file changed, 21 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 6ee25ae6..431551ee 100644 --- a/README.md +++ b/README.md @@ -10,13 +10,32 @@ Hydrogen's goals are: - It is a standalone webapp, but can also be easily embedded into an existing website/webapp to add chat capabilities. - Loading (unused) parts of the application after initial page load should be supported +For embedded usage, see the [SDK instructions](doc/SDK.md). + If you find this interesting, come and discuss on [`#hydrogen:matrix.org`](https://matrix.to/#/#hydrogen:matrix.org). # How to use -Hydrogen is deployed to [hydrogen.element.io](https://hydrogen.element.io). You can run it locally `yarn install` (only the first time) and `yarn start` in the terminal, and point your browser to `http://localhost:3000`. If you prefer, you can also [use docker](doc/docker.md). +Hydrogen is deployed to [hydrogen.element.io](https://hydrogen.element.io). You can also deploy Hydrogen on your own web server: -Hydrogen uses symbolic links in the codebase, so if you are on Windows, have a look at [making git & symlinks work](https://github.com/git-for-windows/git/wiki/Symbolic-Links) there. + 1. Download the [latest release package](https://github.com/vector-im/hydrogen-web/releases). + 1. Extract the package to the public directory of your web server. + 1. If this is your first deploy: + 1. copy `config.sample.json` to `config.json` and if needed, make any modifications. + 1. Disable caching entirely on the server (they will still be cached client-side by the service worker) for: + - `index.html` + - `sw.js` + - `config.json` + - All theme manifests referenced in the `themeManifests` of `config.json`, these files are typically called `theme-{name}.json`. + +## Set up a dev environment + +You can run Hydrogen locally by the following commands in the terminal: + + - `yarn install` (only the first time) + - `yarn start` in the terminal + +Now point your browser to `http://localhost:3000`. If you prefer, you can also [use docker](doc/docker.md). # FAQ From f21e10327059469e4a96cd1e7da8b2c640e80951 Mon Sep 17 00:00:00 2001 From: Bruno Windels <274386+bwindels@users.noreply.github.com> Date: Wed, 18 May 2022 20:46:38 +0200 Subject: [PATCH 228/238] add newlines to config file when rewriting with theme stuff --- scripts/build-plugins/rollup-plugin-build-themes.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/build-plugins/rollup-plugin-build-themes.js b/scripts/build-plugins/rollup-plugin-build-themes.js index 3cb9ed0c..da2db73b 100644 --- a/scripts/build-plugins/rollup-plugin-build-themes.js +++ b/scripts/build-plugins/rollup-plugin-build-themes.js @@ -38,7 +38,7 @@ function addThemesToConfig(bundle, manifestLocations, defaultThemes) { const config = JSON.parse(source); config["themeManifests"] = manifestLocations; config["defaultTheme"] = defaultThemes; - info.source = new TextEncoder().encode(JSON.stringify(config)); + info.source = new TextEncoder().encode(JSON.stringify(config, undefined, 2)); } } } From 7b0591be46cb041437ec8f7f44f7008881396893 Mon Sep 17 00:00:00 2001 From: Bruno Windels <274386+bwindels@users.noreply.github.com> Date: Wed, 18 May 2022 20:51:50 +0200 Subject: [PATCH 229/238] explain that push section of config usually doesn't need to be touched --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 431551ee..1a457741 100644 --- a/README.md +++ b/README.md @@ -21,7 +21,7 @@ Hydrogen is deployed to [hydrogen.element.io](https://hydrogen.element.io). You 1. Download the [latest release package](https://github.com/vector-im/hydrogen-web/releases). 1. Extract the package to the public directory of your web server. 1. If this is your first deploy: - 1. copy `config.sample.json` to `config.json` and if needed, make any modifications. + 1. copy `config.sample.json` to `config.json` and if needed, make any modifications (unless you've set up your own [sygnal](https://github.com/matrix-org/sygnal) instance, you don't need to change anything in the `push` section). 1. Disable caching entirely on the server (they will still be cached client-side by the service worker) for: - `index.html` - `sw.js` From 0e46aed0df32e98e67a82f5f0834657df0a86425 Mon Sep 17 00:00:00 2001 From: Bruno Windels <274386+bwindels@users.noreply.github.com> Date: Wed, 18 May 2022 20:52:18 +0200 Subject: [PATCH 230/238] rename config file to config.sample.json when packaging --- scripts/package.sh | 3 +++ 1 file changed, 3 insertions(+) diff --git a/scripts/package.sh b/scripts/package.sh index 8146fe58..6ad136a3 100755 --- a/scripts/package.sh +++ b/scripts/package.sh @@ -2,6 +2,9 @@ VERSION=$(jq -r ".version" package.json) PACKAGE=hydrogen-web-$VERSION.tar.gz yarn build pushd target +# move config file so we don't override it +# when deploying a new version +mv config.json config.sample.json tar -czvf ../$PACKAGE ./ popd echo $PACKAGE From 1555b0f4bcddc541a09bd63e6fcb7c4bcb521817 Mon Sep 17 00:00:00 2001 From: Bruno Windels <274386+bwindels@users.noreply.github.com> Date: Wed, 18 May 2022 21:41:31 +0200 Subject: [PATCH 231/238] put a message in container node when config file is not found --- src/platform/web/Platform.js | 40 ++++++++++++++++++++++-------------- 1 file changed, 25 insertions(+), 15 deletions(-) diff --git a/src/platform/web/Platform.js b/src/platform/web/Platform.js index 2a691580..8e079c85 100644 --- a/src/platform/web/Platform.js +++ b/src/platform/web/Platform.js @@ -169,22 +169,32 @@ export class Platform { } async init() { - await this.logger.run("Platform init", async (log) => { - if (!this._config) { - if (!this._configURL) { - throw new Error("Neither config nor configURL was provided!"); + try { + await this.logger.run("Platform init", async (log) => { + if (!this._config) { + if (!this._configURL) { + throw new Error("Neither config nor configURL was provided!"); + } + const {status, body}= await this.request(this._configURL, {method: "GET", format: "json", cache: true}).response(); + if (status === 404) { + throw new Error(`Could not find ${this._configURL}. Did you copy over config.sample.json?`); + } else if (status >= 400) { + throw new Error(`Got status ${status} while trying to fetch ${this._configURL}`); + } + this._config = body; } - const {body}= await this.request(this._configURL, {method: "GET", format: "json", cache: true}).response(); - this._config = body; - } - this.notificationService = new NotificationService( - this._serviceWorkerHandler, - this._config.push - ); - const manifests = this.config["themeManifests"]; - await this._themeLoader?.init(manifests); - this._themeLoader?.setTheme(await this._themeLoader.getActiveTheme(), log); - }); + this.notificationService = new NotificationService( + this._serviceWorkerHandler, + this._config.push + ); + const manifests = this.config["themeManifests"]; + await this._themeLoader?.init(manifests); + this._themeLoader?.setTheme(await this._themeLoader.getActiveTheme(), log); + }); + } catch (err) { + this._container.innerText = err.message; + throw err; + } } _createLogger(isDevelopment) { From 13428bd03c7ec3821352ab13eb631fb0bbe23e94 Mon Sep 17 00:00:00 2001 From: Bruno Windels <274386+bwindels@users.noreply.github.com> Date: Wed, 18 May 2022 21:41:47 +0200 Subject: [PATCH 232/238] allow updating cache of unhashed assets (like config) in service worker --- src/platform/web/sw.js | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/platform/web/sw.js b/src/platform/web/sw.js index 6b07f42e..088bc059 100644 --- a/src/platform/web/sw.js +++ b/src/platform/web/sw.js @@ -160,8 +160,14 @@ async function updateCache(request, response) { cache.put(request, response.clone()); } else if (request.url.startsWith(baseURL)) { let assetName = request.url.substr(baseURL.length); + let cacheName; if (HASHED_CACHED_ON_REQUEST_ASSETS.includes(assetName)) { - const cache = await caches.open(hashedCacheName); + cacheName = hashedCacheName; + } else if (UNHASHED_PRECACHED_ASSETS.includes(assetName)) { + cacheName = unhashedCacheName; + } + if (cacheName) { + const cache = await caches.open(cacheName); await cache.put(request, response.clone()); } } From 514d5c0a50060cb92f7aa46c3ef915f5dbe06710 Mon Sep 17 00:00:00 2001 From: Bruno Windels <274386+bwindels@users.noreply.github.com> Date: Wed, 18 May 2022 19:44:39 +0000 Subject: [PATCH 233/238] add notes about client side caching --- README.md | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 1a457741..6c447024 100644 --- a/README.md +++ b/README.md @@ -22,12 +22,14 @@ Hydrogen is deployed to [hydrogen.element.io](https://hydrogen.element.io). You 1. Extract the package to the public directory of your web server. 1. If this is your first deploy: 1. copy `config.sample.json` to `config.json` and if needed, make any modifications (unless you've set up your own [sygnal](https://github.com/matrix-org/sygnal) instance, you don't need to change anything in the `push` section). - 1. Disable caching entirely on the server (they will still be cached client-side by the service worker) for: + 1. Disable caching entirely on the server for: - `index.html` - `sw.js` - `config.json` - All theme manifests referenced in the `themeManifests` of `config.json`, these files are typically called `theme-{name}.json`. - + + These resources will still be cached client-side by the service worker. Because of this; you'll still need to refresh the app twice before config.json changes are applied. + ## Set up a dev environment You can run Hydrogen locally by the following commands in the terminal: From ed8c98558db9eeeb38ec2786d428f153a639288a Mon Sep 17 00:00:00 2001 From: Bruno Windels <274386+bwindels@users.noreply.github.com> Date: Wed, 18 May 2022 21:45:45 +0200 Subject: [PATCH 234/238] release v0.2.29 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 679000e1..4fa5f34b 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "hydrogen-web", - "version": "0.2.28", + "version": "0.2.29", "description": "A javascript matrix client prototype, trying to minize RAM usage by offloading as much as possible to IndexedDB", "directories": { "doc": "doc" From 11d7535c238ea1bdaccfca67180455ab8802d05f Mon Sep 17 00:00:00 2001 From: Bruno Windels <274386+bwindels@users.noreply.github.com> Date: Tue, 31 May 2022 13:38:34 +0200 Subject: [PATCH 235/238] add some basic tests (with mock utils) for DeviceTracker --- src/matrix/e2ee/DeviceTracker.js | 122 +++++++++++++++++++++++++++++++ 1 file changed, 122 insertions(+) diff --git a/src/matrix/e2ee/DeviceTracker.js b/src/matrix/e2ee/DeviceTracker.js index 0068a1f9..c640b04a 100644 --- a/src/matrix/e2ee/DeviceTracker.js +++ b/src/matrix/e2ee/DeviceTracker.js @@ -363,3 +363,125 @@ export class DeviceTracker { return await txn.deviceIdentities.getByCurve25519Key(curve25519Key); } } + +import {createMockStorage} from "../../mocks/Storage"; +import {Instance as NullLoggerInstance} from "../../logging/NullLogger"; + +export function tests() { + + function createUntrackedRoomMock(roomId, joinedUserIds, invitedUserIds = []) { + return { + isTrackingMembers: false, + isEncrypted: true, + loadMemberList: () => { + const joinedMembers = joinedUserIds.map(userId => {return {membership: "join", roomId, userId};}); + const invitedMembers = invitedUserIds.map(userId => {return {membership: "invite", roomId, userId};}); + const members = joinedMembers.concat(invitedMembers); + const memberMap = members.reduce((map, member) => { + map.set(member.userId, member); + return map; + }, new Map()); + return {members: memberMap, release() {}} + }, + writeIsTrackingMembers(isTrackingMembers) { + if (this.isTrackingMembers !== isTrackingMembers) { + return isTrackingMembers; + } + return undefined; + }, + applyIsTrackingMembersChanges(isTrackingMembers) { + if (isTrackingMembers !== undefined) { + this.isTrackingMembers = isTrackingMembers; + } + }, + } + } + + function createQueryKeysHSApiMock(createKey = (algorithm, userId, deviceId) => `${algorithm}:${userId}:${deviceId}:key`) { + return { + queryKeys(payload) { + const {device_keys: deviceKeys} = payload; + const userKeys = Object.entries(deviceKeys).reduce((userKeys, [userId, deviceIds]) => { + if (deviceIds.length === 0) { + deviceIds = ["device1"]; + } + userKeys[userId] = deviceIds.filter(d => d === "device1").reduce((deviceKeys, deviceId) => { + deviceKeys[deviceId] = { + "algorithms": [ + "m.olm.v1.curve25519-aes-sha2", + "m.megolm.v1.aes-sha2" + ], + "device_id": deviceId, + "keys": { + [`curve25519:${deviceId}`]: createKey("curve25519", userId, deviceId), + [`ed25519:${deviceId}`]: createKey("ed25519", userId, deviceId), + }, + "signatures": { + [userId]: { + [`ed25519:${deviceId}`]: `ed25519:${userId}:${deviceId}:signature` + } + }, + "unsigned": { + "device_display_name": `${userId} Phone` + }, + "user_id": userId + }; + return deviceKeys; + }, {}); + return userKeys; + }, {}); + const response = {device_keys: userKeys}; + return { + async response() { + return response; + } + }; + } + }; + } + const roomId = "!abc:hs.tld"; + + return { + "trackRoom only writes joined members": async assert => { + const storage = await createMockStorage(); + const tracker = new DeviceTracker({ + storage, + getSyncToken: () => "token", + olmUtil: {ed25519_verify: () => {}}, // valid if it does not throw + ownUserId: "@alice:hs.tld", + ownDeviceId: "ABCD", + }); + const room = createUntrackedRoomMock(roomId, ["@alice:hs.tld", "@bob:hs.tld"], ["@charly:hs.tld"]); + await tracker.trackRoom(room, NullLoggerInstance.item); + const txn = await storage.readTxn([storage.storeNames.userIdentities]); + assert.deepEqual(await txn.userIdentities.get("@alice:hs.tld"), { + userId: "@alice:hs.tld", + roomIds: [roomId], + deviceTrackingStatus: TRACKING_STATUS_OUTDATED + }); + assert.deepEqual(await txn.userIdentities.get("@bob:hs.tld"), { + userId: "@bob:hs.tld", + roomIds: [roomId], + deviceTrackingStatus: TRACKING_STATUS_OUTDATED + }); + assert.equal(await txn.userIdentities.get("@charly:hs.tld"), undefined); + }, + "getting devices for tracked room yields correct keys": async assert => { + const storage = await createMockStorage(); + const tracker = new DeviceTracker({ + storage, + getSyncToken: () => "token", + olmUtil: {ed25519_verify: () => {}}, // valid if it does not throw + ownUserId: "@alice:hs.tld", + ownDeviceId: "ABCD", + }); + const room = createUntrackedRoomMock(roomId, ["@alice:hs.tld", "@bob:hs.tld"]); + await tracker.trackRoom(room, NullLoggerInstance.item); + const hsApi = createQueryKeysHSApiMock(); + const devices = await tracker.devicesForRoomMembers(roomId, ["@alice:hs.tld", "@bob:hs.tld"], hsApi, NullLoggerInstance.item); + assert.equal(devices.length, 2); + assert.equal(devices.find(d => d.userId === "@alice:hs.tld").ed25519Key, "ed25519:@alice:hs.tld:device1:key"); + assert.equal(devices.find(d => d.userId === "@bob:hs.tld").ed25519Key, "ed25519:@bob:hs.tld:device1:key"); + }, + } +} From 3d3d590334aa5750b1acb36842a5ca240cce0fd5 Mon Sep 17 00:00:00 2001 From: Bruno Windels <274386+bwindels@users.noreply.github.com> Date: Tue, 31 May 2022 13:39:05 +0200 Subject: [PATCH 236/238] add failing test for device with changed key being returned --- src/matrix/e2ee/DeviceTracker.js | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/src/matrix/e2ee/DeviceTracker.js b/src/matrix/e2ee/DeviceTracker.js index c640b04a..21ec89b8 100644 --- a/src/matrix/e2ee/DeviceTracker.js +++ b/src/matrix/e2ee/DeviceTracker.js @@ -483,5 +483,34 @@ export function tests() { assert.equal(devices.find(d => d.userId === "@alice:hs.tld").ed25519Key, "ed25519:@alice:hs.tld:device1:key"); assert.equal(devices.find(d => d.userId === "@bob:hs.tld").ed25519Key, "ed25519:@bob:hs.tld:device1:key"); }, + "device with changed key is ignored": async assert => { + const storage = await createMockStorage(); + const tracker = new DeviceTracker({ + storage, + getSyncToken: () => "token", + olmUtil: {ed25519_verify: () => {}}, // valid if it does not throw + ownUserId: "@alice:hs.tld", + ownDeviceId: "ABCD", + }); + const room = createUntrackedRoomMock(roomId, ["@alice:hs.tld", "@bob:hs.tld"]); + await tracker.trackRoom(room, NullLoggerInstance.item); + const hsApi = createQueryKeysHSApiMock(); + // query devices first time + await tracker.devicesForRoomMembers(roomId, ["@alice:hs.tld", "@bob:hs.tld"], hsApi, NullLoggerInstance.item); + const txn = await storage.readWriteTxn([storage.storeNames.userIdentities]); + // mark alice as outdated, so keys will be fetched again + tracker.writeDeviceChanges(["@alice:hs.tld"], txn, NullLoggerInstance.item); + await txn.complete(); + const hsApiWithChangedAliceKey = createQueryKeysHSApiMock((algo, userId, deviceId) => { + return `${algo}:${userId}:${deviceId}:${userId === "@alice:hs.tld" ? "newKey" : "key"}`; + }); + const devices = await tracker.devicesForRoomMembers(roomId, ["@alice:hs.tld", "@bob:hs.tld"], hsApiWithChangedAliceKey, NullLoggerInstance.item); + assert.equal(devices.length, 2); + assert.equal(devices.find(d => d.userId === "@alice:hs.tld").ed25519Key, "ed25519:@alice:hs.tld:device1:key"); + assert.equal(devices.find(d => d.userId === "@bob:hs.tld").ed25519Key, "ed25519:@bob:hs.tld:device1:key"); + const txn2 = await storage.readTxn([storage.storeNames.deviceIdentities]); + // also check the modified key was not stored + assert.equal((await txn2.deviceIdentities.get("@alice:hs.tld", "device1")).ed25519Key, "ed25519:@alice:hs.tld:device1:key"); + } } } From bc5164486820761912600f72acbfc132676a2b00 Mon Sep 17 00:00:00 2001 From: Bruno Windels <274386+bwindels@users.noreply.github.com> Date: Tue, 31 May 2022 13:39:23 +0200 Subject: [PATCH 237/238] reassignment is not used later on, remove --- src/matrix/e2ee/DeviceTracker.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/matrix/e2ee/DeviceTracker.js b/src/matrix/e2ee/DeviceTracker.js index 21ec89b8..1bfe63ef 100644 --- a/src/matrix/e2ee/DeviceTracker.js +++ b/src/matrix/e2ee/DeviceTracker.js @@ -214,7 +214,7 @@ export class DeviceTracker { const allDeviceIdentities = []; const deviceIdentitiesToStore = []; // filter out devices that have changed their ed25519 key since last time we queried them - deviceIdentities = await Promise.all(deviceIdentities.map(async deviceIdentity => { + await Promise.all(deviceIdentities.map(async deviceIdentity => { if (knownDeviceIds.includes(deviceIdentity.deviceId)) { const existingDevice = await txn.deviceIdentities.get(deviceIdentity.userId, deviceIdentity.deviceId); if (existingDevice.ed25519Key !== deviceIdentity.ed25519Key) { From c62c8da10b9eb3fda00ea5be46677c0e5d109c4e Mon Sep 17 00:00:00 2001 From: Bruno Windels <274386+bwindels@users.noreply.github.com> Date: Tue, 31 May 2022 13:39:35 +0200 Subject: [PATCH 238/238] fix changed key not being ignored --- src/matrix/e2ee/DeviceTracker.js | 1 + 1 file changed, 1 insertion(+) diff --git a/src/matrix/e2ee/DeviceTracker.js b/src/matrix/e2ee/DeviceTracker.js index 1bfe63ef..f8c3bca8 100644 --- a/src/matrix/e2ee/DeviceTracker.js +++ b/src/matrix/e2ee/DeviceTracker.js @@ -219,6 +219,7 @@ export class DeviceTracker { const existingDevice = await txn.deviceIdentities.get(deviceIdentity.userId, deviceIdentity.deviceId); if (existingDevice.ed25519Key !== deviceIdentity.ed25519Key) { allDeviceIdentities.push(existingDevice); + return; } } allDeviceIdentities.push(deviceIdentity);