Compare commits

...
This repository has been archived on 2022-08-19. You can view files and clone it, but cannot push or open issues or pull requests.

27 Commits

Author SHA1 Message Date
Eric Eastwood acaf53de3e More exports 2022-07-21 20:00:16 -05:00
Eric Eastwood 88e24703ff Scope log 2022-07-21 19:59:56 -05:00
Eric Eastwood b54e884b7e Expose error when we fail to createObjectStore 2022-07-21 17:39:42 -05:00
Eric Eastwood c824012968 `this` doesn't work in strict mode which the SDK is exported as
See https://github.com/vector-im/hydrogen-web/pull/373/files#r927145321
2022-07-21 17:23:49 -05:00
Eric Eastwood 871cf1ad80 Revert "Ignore missing events"
This reverts commit 8dc3c13a93.
2022-07-20 02:27:35 -05:00
Eric Eastwood 8dc3c13a93 Ignore missing events 2022-07-20 02:27:18 -05:00
Eric Eastwood 6a6f22047e Merge branch 'master' into madlittlemods/matrix-public-archive-scratch-changes 2022-07-05 06:00:00 -05:00
Eric Eastwood 72300d1b0c Lightbox escape keyboard shortcut also works 2022-06-07 22:35:26 -05:00
Eric Eastwood 5d9dc638ea URL hashes relative to the room of the archive 2022-06-07 19:41:08 -05:00
Eric Eastwood 1a0b1403ef Working lightbox pops up and closes 2022-06-07 17:55:53 -05:00
Eric Eastwood 2d3b78b725 WIP: Make the lightbox open, not working yet 2022-06-07 17:16:58 -05:00
Eric Eastwood ae673862dc Use correct variable in comment 2022-06-06 17:53:26 -05:00
Eric Eastwood c24ac43e72 Merge branch 'master' into madlittlemods/matrix-public-archive-scratch-changes
Conflicts:
	scripts/sdk/base-manifest.json
	scripts/sdk/build.sh
	src/domain/session/room/RoomViewModel.js
	src/platform/web/Platform.js
	src/platform/web/ui/general/html.ts
2022-06-06 15:26:52 -05:00
Eric Eastwood ea2d45cab7 No need to comment this out since linkedom supports it now
See https://github.com/vector-im/hydrogen-web/pull/653#discussion_r805103800

We can allow this to run now since I added support for `setProperty` in `linkedom`  https://github.com/WebReflection/linkedom/pull/114
2022-02-25 01:48:16 -06:00
Eric Eastwood 082d997eed Only try to use window.crypto.subtle in secure contexts to avoid it throwing and stopping all JavaScript
Related to https://github.com/vector-im/hydrogen-web/issues/579

```
Uncaught (in promise) TypeError: Cannot read properties of undefined (reading 'deriveBits')
	at new Crypto
	at new Platform
	at mountHydrogen
```
2022-02-24 12:15:14 -06:00
Eric Eastwood fc89bfdd53 Get rid of duplicate export 2022-02-24 02:44:16 -06:00
Eric Eastwood 3e58619935 Add more SVG elements 2022-02-24 02:43:36 -06:00
Eric Eastwood a4cdde6f53 Use UTC timestamps and add data attribute for easy targeting in tests 2022-02-24 02:41:54 -06:00
Eric Eastwood 6005fcfc55 Add permalink to timestamp 2022-02-24 02:40:37 -06:00
Eric Eastwood 1032f4dbc6 Merge branch 'master' into madlittlemods/matrix-public-archive-scratch-changes
Conflicts:
	scripts/sdk/base-manifest.json
	src/platform/web/parsehtml.js
2022-02-14 15:03:07 -06:00
Eric Eastwood 48825ea30f Use explicit HTML document boilerplate to get consistent results in browser and linkedom (for SSR)
Context:

 - https://github.com/WebReflection/linkedom/issues/106
 - https://github.com/WebReflection/linkedom/pull/108
2022-02-11 19:23:45 -06:00
Eric Eastwood e75f18c87a Support custom RightPanel content 2022-02-10 02:22:04 -06:00
Eric Eastwood 8d0c4e68b6 Some changes to support RoomView with no composer 2022-02-10 01:45:10 -06:00
Eric Eastwood 4eb24db1de Fix reply tiles not showing the new message 2022-02-09 01:50:05 -06:00
Eric Eastwood eda179a154 Remove dom side-effect from rendering 2022-02-04 01:26:18 -06:00
Eric Eastwood 5805ce0310 Remove some scratch changes 2022-02-02 01:17:07 -06:00
Eric Eastwood dcc508c037 Changes added to work on the Matrix public archive
See plan https://docs.google.com/document/d/1wP_TIqmBQjtt862vb2CWWmnmVxTyolcF3J1scuiYMdg/edit#

 1. Trying to make it faster/easier to build `hydrogen.es.js` for local linking and dev in `matrix-public-arhive` project
 1. Some random changes to accomodate using raw `EventEntry`'s
2022-02-02 01:08:54 -06:00
17 changed files with 145 additions and 50 deletions

View File

@ -28,12 +28,14 @@ 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";
import type {History} from "../platform/web/dom/History";
export type Options = {
platform: Platform
logger: ILogger
urlCreator: URLRouter
navigation: Navigation
history: History
emitChange?: (params: any) => void
}
@ -142,4 +144,8 @@ export class ViewModel<O extends Options = Options> extends EventEmitter<{change
get navigation(): Navigation {
return this._options.navigation;
}
get history(): History {
return this._options.history;
}
}

View File

@ -20,7 +20,7 @@ import {RoomViewModel} from "./room/RoomViewModel.js";
import {UnknownRoomViewModel} from "./room/UnknownRoomViewModel.js";
import {InviteViewModel} from "./room/InviteViewModel.js";
import {RoomBeingCreatedViewModel} from "./room/RoomBeingCreatedViewModel.js";
import {LightboxViewModel} from "./room/LightboxViewModel.js";
import {setupLightboxNavigation} from "./room/lightbox-navigation.js";
import {SessionStatusViewModel} from "./SessionStatusViewModel.js";
import {RoomGridViewModel} from "./RoomGridViewModel.js";
import {SettingsViewModel} from "./settings/SettingsViewModel.js";
@ -81,12 +81,12 @@ export class SessionViewModel extends ViewModel {
}));
this._updateCreateRoom(createRoom.get());
const lightbox = this.navigation.observe("lightbox");
this.track(lightbox.subscribe(eventId => {
this._updateLightbox(eventId);
}));
this._updateLightbox(lightbox.get());
setupLightboxNavigation(this, 'lightboxViewModel', (eventId) => {
return {
room,
eventId,
};
});
const rightpanel = this.navigation.observe("right-panel");
this.track(rightpanel.subscribe(() => this._updateRightPanel()));
@ -267,21 +267,6 @@ export class SessionViewModel extends ViewModel {
this.emitChange("activeMiddleViewModel");
}
_updateLightbox(eventId) {
if (this._lightboxViewModel) {
this._lightboxViewModel = this.disposeTracked(this._lightboxViewModel);
}
if (eventId) {
const room = this._roomFromNavigation();
this._lightboxViewModel = this.track(new LightboxViewModel(this.childOptions({eventId, room})));
}
this.emitChange("lightboxViewModel");
}
get lightboxViewModel() {
return this._lightboxViewModel;
}
_roomFromNavigation() {
const roomId = this.navigation.path.get("room")?.value;
const room = this._client.session.rooms.get(roomId);

View File

@ -19,21 +19,25 @@ import {ViewModel} from "../../ViewModel";
export class LightboxViewModel extends ViewModel {
constructor(options) {
super(options);
this._eventId = options.eventId;
this._eventEntry = options.eventEntry;
this._eventId = options.eventId || options.eventEntry.id;
this._unencryptedImageUrl = null;
this._decryptedImage = null;
this._closeUrl = this.urlCreator.urlUntilSegment("room");
this._eventEntry = null;
this._date = null;
this._subscribeToEvent(options.room, options.eventId);
}
_subscribeToEvent(room, eventId) {
const eventObservable = room.observeEvent(eventId);
this.track(eventObservable.subscribe(eventEntry => {
this._loadEvent(room, eventEntry);
}));
this._loadEvent(room, eventObservable.get());
let event = this._eventEntry;
if (!this._eventEntry) {
const eventObservable = room.observeEvent(eventId);
this.track(eventObservable.subscribe(eventEntry => {
this._loadEvent(room, eventEntry);
}));
event = eventObservable.get();
}
this._loadEvent(room, event);
}
async _loadEvent(room, eventEntry) {
@ -92,6 +96,6 @@ export class LightboxViewModel extends ViewModel {
}
close() {
this.platform.history.pushUrl(this.closeUrl);
this.history.pushUrl(this.closeUrl);
}
}

View File

@ -49,6 +49,7 @@ export class RoomViewModel extends ViewModel {
this._room.on("change", this._onRoomChange);
try {
const timeline = await this._room.openTimeline();
console.log('timeline', timeline.entries);
this._tileOptions = this.childOptions({
roomVM: this,
timeline,

View File

@ -0,0 +1,69 @@
/*
Copyright 2022 Bruno Windels <bruno@windels.cloud>
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 {LightboxViewModel} from "./LightboxViewModel.js";
// Store the `LightboxViewModel` under a symbol so no one else can tamper with
// it. This acts like a private field on the class since no one else has the
// symbol to look it up.
let lightboxViewModelSymbol = Symbol('lightboxViewModel');
/**
* Destroys and creates a new the `LightboxViewModel` depending if
* `lightboxChildOptions.eventEntry` or `lightboxChildOptions.eventId` are
* provided.
*/
function updateLightboxViewModel(vm, fieldName, lightboxChildOptions) {
// Remove any existing `LightboxViewModel` before we assemble the new one below
if (vm[lightboxViewModelSymbol]) {
vm[lightboxViewModelSymbol] = vm.disposeTracked(vm[lightboxViewModelSymbol]);
// Let the `LightboxView` know that the `LightboxViewModel` has changed
vm.emitChange(fieldName);
}
// Create the new `LightboxViewModel` if the `eventEntry` exists directly or
// `eventId` which we can load from the store
if (lightboxChildOptions.eventId || lightboxChildOptions.eventEntry) {
vm[lightboxViewModelSymbol] = vm.track(new LightboxViewModel(vm.childOptions(lightboxChildOptions)));
// Let the `LightboxView` know that the `LightboxViewModel` has changed
vm.emitChange(fieldName);
}
}
/**
* Handles updating the `LightboxViewModel` whenever the page URL changes and
* emits changes which the `LightboxView` will use to re-render. This is a
* composable piece of logic to call in an existing `ViewModel`'s constructor.
*/
export function setupLightboxNavigation(vm, fieldName = 'lightboxViewModel', lightboxChildOptionsFunction) {
// On the given `vm`, create a getter at `fieldName` that the
// `LightboxViewModel` is exposed at for usage in the view.
Object.defineProperty(vm, fieldName, {
get: function() {
return vm[lightboxViewModelSymbol];
}
});
// Whenever the page navigates somewhere, keep the `lightboxViewModel` up to date
const lightbox = vm.navigation.observe("lightbox");
vm.track(lightbox.subscribe(eventId => {
updateLightboxViewModel(vm, fieldName, lightboxChildOptionsFunction(eventId));
}));
// Also handle the case where the URL already includes `/lightbox/$eventId` (like
// from page-load)
const initialLightBoxEventId = lightbox.get();
updateLightboxViewModel(vm, fieldName, lightboxChildOptionsFunction(initialLightBoxEventId));
}

View File

@ -49,7 +49,7 @@ export class TimelineViewModel extends ViewModel {
this._showJumpDown = false;
}
/** if this.tiles is empty, call this with undefined for both startTile and endTile */
/** if this._tiles is empty, call this with undefined for both startTile and endTile */
setVisibleTileRange(startTile, endTile) {
// don't clear these once done as they are used to check
// for more tiles once loadAtTop finishes

View File

@ -49,6 +49,10 @@ export class BaseMessageTile extends SimpleTile {
return `https://matrix.to/#/${encodeURIComponent(this.sender)}`;
}
get eventId() {
return this._entry.id;
}
get displayName() {
return this._entry.displayName || this.sender;
}
@ -79,15 +83,15 @@ export class BaseMessageTile extends SimpleTile {
}
get date() {
return this._date && this._date.toLocaleDateString({}, {month: "numeric", day: "numeric"});
return this._date && this._date.toLocaleDateString({}, {month: "numeric", day: "numeric", timeZone: 'UTC'});
}
get time() {
return this._date && this._date.toLocaleTimeString({}, {hour: "numeric", minute: "2-digit"});
return this._date && this._date.toLocaleTimeString({}, {hour: "numeric", minute: "2-digit", timeZone: 'UTC'});
}
get isOwn() {
return this._entry.sender === this._ownMember.userId;
return this._entry.sender === this._ownMember?.userId;
}
get isContinuation() {

View File

@ -25,6 +25,18 @@ export {SessionViewModel} from "./domain/session/SessionViewModel.js";
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 {LightboxView} from "./platform/web/ui/session/room/LightboxView.js";
export {setupLightboxNavigation} from "./domain/session/room/lightbox-navigation.js";
export {RightPanelView} from "./platform/web/ui/session/rightpanel/RightPanelView.js";
export {MediaRepository} from "./matrix/net/MediaRepository";
export {HomeServerApi} from "./matrix/net/HomeServerApi";
export {Storage} from "./matrix/storage/idb/Storage";
export {StorageFactory} from "./matrix/storage/idb/StorageFactory";
export {TilesCollection} from "./domain/session/room/timeline/TilesCollection.js";
export {FragmentIdComparer} from "./matrix/room/timeline/FragmentIdComparer.js";
export {EventEntry} from "./matrix/room/timeline/entries/EventEntry.js";
export {encodeKey, decodeKey, encodeEventIdKey, decodeEventIdKey} from "./matrix/storage/idb/stores/TimelineEventStore";
export {Timeline} from "./matrix/room/timeline/Timeline.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";
@ -62,6 +74,7 @@ export {TextMessageView} from "./platform/web/ui/session/room/timeline/TextMessa
export {VideoView} from "./platform/web/ui/session/room/timeline/VideoView.js";
export {Navigation} from "./domain/navigation/Navigation.js";
export {History} from "./platform/web/dom/History.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";

View File

@ -85,6 +85,7 @@ export class Timeline {
const readerRequest = this._disposables.track(this._timelineReader.readFromEnd(20, txn, log));
try {
const entries = await readerRequest.complete();
console.log('entries', entries)
this._loadContextEntriesWhereNeeded(entries);
this._setupEntries(entries);
} finally {
@ -198,8 +199,10 @@ export class Timeline {
if (!this._localEntries?.hasSubscriptions) {
return;
}
// find any local relations to this new remote event
for (const pee of this._localEntries) {
// find any local relations to these new remote events or maybe these
// new remote events reference one of the other new remote events we have.
const entryList = new ConcatList(entries, this._localEntries);
for (const pee of entryList) {
// this will work because we set relatedEventId when removing remote echos
if (pee.relatedEventId) {
const relationTarget = entries.find(e => e.id === pee.relatedEventId);

View File

@ -54,6 +54,7 @@ async function readRawTimelineEntriesWithTxn(roomId, eventKey, direction, amount
} else {
eventsWithinFragment = await timelineStore.eventsBefore(roomId, eventKey, amount);
}
console.log('readRawTimelineEntriesWithTxn eventsWithinFragment', eventsWithinFragment)
let eventEntries = eventsWithinFragment.map(e => new EventEntry(e, fragmentIdComparer));
entries = directionalConcat(entries, eventEntries, direction);
// prepend or append eventsWithinFragment to entries, and wrap them in EventEntry

View File

@ -34,12 +34,11 @@ interface ServiceWorkerHandler {
async function requestPersistedStorage(): Promise<boolean> {
// don't assume browser so we can run in node with fake-idb
const glob = this;
if (glob?.navigator?.storage?.persist) {
return await glob.navigator.storage.persist();
} else if (glob?.document.requestStorageAccess) {
if (window?.navigator?.storage?.persist) {
return await window.navigator.storage.persist();
} else if (window?.document.requestStorageAccess) {
try {
await glob.document.requestStorageAccess();
await window.document.requestStorageAccess();
return true;
} catch (err) {
return false;

View File

@ -40,20 +40,20 @@ interface TimelineEventEntry {
type TimelineEventStorageEntry = TimelineEventEntry & { key: string, eventIdKey: string };
function encodeKey(roomId: string, fragmentId: number, eventIndex: number): string {
export function encodeKey(roomId: string, fragmentId: number, eventIndex: number): string {
return `${roomId}|${encodeUint32(fragmentId)}|${encodeUint32(eventIndex)}`;
}
function decodeKey(key: string): { roomId: string, eventKey: EventKey } {
export function decodeKey(key: string): { roomId: string, eventKey: EventKey } {
const [roomId, fragmentId, eventIndex] = key.split("|");
return {roomId, eventKey: new EventKey(decodeUint32(fragmentId), decodeUint32(eventIndex))};
}
function encodeEventIdKey(roomId: string, eventId: string): string {
export function encodeEventIdKey(roomId: string, eventId: string): string {
return `${roomId}|${eventId}`;
}
function decodeEventIdKey(eventIdKey: string): { roomId: string, eventId: string } {
export function decodeEventIdKey(eventIdKey: string): { roomId: string, eventId: string } {
const [roomId, eventId] = eventIdKey.split("|");
return {roomId, eventId};
}

View File

@ -80,10 +80,15 @@ export function openDatabase(name: string, createObjectStore: CreateObjectStore,
try {
await createObjectStore(db, txn, oldVersion, version);
} catch (err) {
console.error(`openDatabase: Failed to createObjectStore in database=${name}`, err);
// try aborting on error, if that hasn't been done already
try {
txn.abort();
} catch (err) {}
} catch (err) {
// No-op: `InvalidStateError` is only thrown if the transaction has
// already been committed or aborted. Since we wanted the txn to
// be aborted anyway, it doesn't matter if this fails.
}
}
};
return reqAsPromise(req);

View File

@ -30,10 +30,10 @@ export class History extends BaseObservableValue {
But for SSO, we need to handle <root>/?loginToken=<TOKEN>
Handle that as a special case for now.
*/
if (document.location.search.includes("loginToken")) {
if (document?.location?.search.includes("loginToken")) {
return document.location.search;
}
return document.location.hash;
return document?.location?.hash;
}
/** does not emit */

View File

@ -39,6 +39,8 @@ export class RightPanelView extends TemplateView {
return new MemberListView(vm);
case "member-details":
return new MemberDetailsView(vm);
case "custom":
return new vm.customView(vm);
default:
return new LoadingView();
}

View File

@ -15,6 +15,7 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
import { text } from "../../general/html";
import {TemplateView} from "../../general/TemplateView";
import {Popup} from "../../general/Popup.js";
import {Menu} from "../../general/Menu.js";
@ -58,7 +59,7 @@ export class RoomView extends TemplateView {
new TimelineView(timelineViewModel, this._viewClassForTile) :
new TimelineLoadingView(vm); // vm is just needed for i18n
}),
t.view(bottomView),
bottomView ? t.view(bottomView) : text(''),
])
]);
}

View File

@ -20,7 +20,9 @@ import {ReplyPreviewError, ReplyPreviewView} from "./ReplyPreviewView.js";
export class TextMessageView extends BaseMessageView {
renderMessageBody(t, vm) {
const time = t.time({className: {hidden: !vm.date}}, vm.date + " " + vm.time);
const time = t.a({ href: vm.permaLink, target: "_blank" }, [
t.time({className: {hidden: !vm.date}}, vm.date + " " + vm.time)
]);
const container = t.div({
className: {
"Timeline_messageBody": true,