Merge branch 'master' into threading-fallback-reply

This commit is contained in:
Bruno Windels 2022-01-14 15:43:24 +01:00
commit 3c59004e72
13 changed files with 171 additions and 35 deletions

View file

@ -33,7 +33,20 @@ import {
RoomViewModel, RoomViewModel,
TimelineView TimelineView
} from "hydrogen-view-sdk"; } from "hydrogen-view-sdk";
import assetPaths from "hydrogen-view-sdk/paths/vite"; 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"; import "hydrogen-view-sdk/style.css";
async function main() { async function main() {
@ -84,7 +97,13 @@ main();
## Typescript support ## Typescript support
There is partial typescript support while we are still in the process of converting the Hydrogen codebase to typesccript. Typescript support is not yet available while we're converting the Hydrogen codebase to Typescript.
In your `src` directory, you'll need to add a `.d.ts` (can be called anything, e.g. `deps.d.ts`)
containing this snippet to make Typescript not complain that `hydrogen-view-sdk` doesn't have types:
```ts
declare module "hydrogen-view-sdk";
```
## API Stability ## API Stability

View file

@ -2,8 +2,37 @@
## Use `type` rather than `interface` for named parameters and POJO return values. ## Use `type` rather than `interface` for named parameters and POJO return values.
`type` and `interface` can be used somewhat interchangebly used, but let's use `type` to describe data and `interface` to describe (polymorphic) behaviour. `type` and `interface` can be used somewhat interchangeably, but let's use `type` to describe data and `interface` to describe (polymorphic) behaviour.
Good examples of data are option objects to have named parameters, and POJO (plain old javascript objects) without any methods, just fields. Good examples of data are option objects to have named parameters, and POJO (plain old javascript objects) without any methods, just fields.
Also see [this playground](https://www.typescriptlang.org/play?#code/C4TwDgpgBACghgJwgO2AeTMAlge2QZygF4oBvAKCiqmTgFsIAuKfYBLZAcwG5LqATCABs4IAPzNkAVzoAjCAl4BfcuVCQoAYQAWWIfwzY8hEvCSpDuAlABkZPlQDGOITgTNW7LstWOR+QjMUYHtqKGcCNilHYDcAChxMK3xmIIsk4wBKewcoFRVyPzgArV19KAgAD2AUfkDEYNDqCM9o2IQEjIJmHT0DLvxsijCw-ClIDsSjAkzeEebjEIYAuE5oEgADABJSKeSAOloGJSgsQh29433nVwQlDbnqfKA) Also see [this playground](https://www.typescriptlang.org/play?#code/C4TwDgpgBACghgJwgO2AeTMAlge2QZygF4oBvAKCiqmTgFsIAuKfYBLZAcwG5LqATCABs4IAPzNkAVzoAjCAl4BfcuVCQoAYQAWWIfwzY8hEvCSpDuAlABkZPlQDGOITgTNW7LstWOR+QjMUYHtqKGcCNilHYDcAChxMK3xmIIsk4wBKewcoFRVyPzgArV19KAgAD2AUfkDEYNDqCM9o2IQEjIJmHT0DLvxsijCw-ClIDsSjAkzeEebjEIYAuE5oEgADABJSKeSAOloGJSgsQh29433nVwQlDbnqfKA)
## Use `type foo = { [key: string]: any }` for types that you intend to fill in later.
For instance, if you have a method such as:
```js
function load(options) {
// ...
}
```
and you intend to type options at some later point, do:
```ts
type Options = { [key: string]: any}
```
This makes it much easier to add the necessary type information at a later time.
## Use `object` or `Record<string, any>` to describe a type that accepts any javascript object.
Sometimes a function or method may genuinely need to accept any object; eg:
```js
function encodeBody(body) {
// ...
}
```
In this scenario:
- Use `object` if you know that you will not access any property
- Use `Record<string, any>` if you need to access some property
Both usages prevent the type from accepting primitives (eg: string, boolean...).
If using `Record`, ensure that you have guards to check that the properties really do exist.

View file

@ -45,13 +45,13 @@
"text-encoding": "^0.7.0", "text-encoding": "^0.7.0",
"typescript": "^4.3.5", "typescript": "^4.3.5",
"vite": "^2.6.14", "vite": "^2.6.14",
"xxhashjs": "^0.2.2" "xxhashjs": "^0.2.2",
"bs58": "^4.0.1"
}, },
"dependencies": { "dependencies": {
"@matrix-org/olm": "https://gitlab.matrix.org/api/v4/projects/27/packages/npm/@matrix-org/olm/-/@matrix-org/olm-3.2.3.tgz", "@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", "another-json": "^0.2.0",
"base64-arraybuffer": "^0.2.0", "base64-arraybuffer": "^0.2.0",
"bs58": "^4.0.1",
"dompurify": "^2.3.0" "dompurify": "^2.3.0"
} }
} }

View file

@ -1,15 +1,7 @@
{ {
"name": "hydrogen-view-sdk", "name": "hydrogen-view-sdk",
"description": "Embeddable matrix client library, including view components", "description": "Embeddable matrix client library, including view components",
"version": "0.0.2", "version": "0.0.4",
"main": "./hydrogen.cjs.js", "main": "./hydrogen.es.js",
"exports": { "type": "module"
".": {
"import": "./hydrogen.es.js",
"require": "./hydrogen.cjs.js"
},
"./paths/vite": "./paths/vite.js",
"./style.css": "./style.css"
},
"types": "types/lib.d.ts"
} }

View file

@ -1,10 +1,12 @@
#!/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-assets-config.js
yarn run vite build -c vite.sdk-lib-config.js yarn run vite build -c vite.sdk-lib-config.js
yarn tsc -p tsconfig-declaration.json yarn tsc -p tsconfig-declaration.json
./scripts/sdk/create-manifest.js ./target/package.json ./scripts/sdk/create-manifest.js ./target/package.json
mkdir target/paths mkdir target/paths
./scripts/sdk/transform-paths.js ./src/platform/web/sdk/paths/vite.js ./target/paths/vite.js # 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 cp doc/SDK.md target/README.md
pushd target pushd target
pushd asset-build/assets pushd asset-build/assets

View file

@ -1,7 +1,29 @@
#!/usr/bin/env node #!/usr/bin/env node
const fs = require("fs"); const fs = require("fs");
const appManifest = require("../../package.json") const appManifest = require("../../package.json");
const baseSDKManifest = require("./base-manifest.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
typescript conversion is complete and all imports in the d.ts files
exists.
```
"types": "types/lib.d.ts"
```
*/
const mergeOptions = require('merge-options'); const mergeOptions = require('merge-options');
const manifestExtension = { const manifestExtension = {

View file

@ -25,10 +25,8 @@ export class BaseMediaTile extends BaseMessageTile {
super(options); super(options);
this._decryptedThumbnail = null; this._decryptedThumbnail = null;
this._decryptedFile = null; this._decryptedFile = null;
this._isVisible = false;
this._error = null; this._error = null;
if (!this.isPending) {
this._tryLoadEncryptedThumbnail();
}
} }
get isUploading() { get isUploading() {
@ -60,6 +58,9 @@ export class BaseMediaTile extends BaseMessageTile {
} }
get thumbnailUrl() { get thumbnailUrl() {
if (!this._isVisible) {
return "";
}
if (this._decryptedThumbnail) { if (this._decryptedThumbnail) {
return this._decryptedThumbnail.url; return this._decryptedThumbnail.url;
} else { } else {
@ -85,6 +86,15 @@ export class BaseMediaTile extends BaseMessageTile {
return ""; return "";
} }
notifyVisible() {
super.notifyVisible();
this._isVisible = true;
this.emitChange("thumbnailUrl");
if (!this.isPending) {
this._tryLoadEncryptedThumbnail();
}
}
get width() { get width() {
const info = this._getContent()?.info; const info = this._getContent()?.info;
return Math.round(info?.w * this._scaleFactor()); return Math.round(info?.w * this._scaleFactor());

View file

@ -16,21 +16,43 @@ limitations under the License.
import {BaseMessageTile} from "./BaseMessageTile.js"; import {BaseMessageTile} from "./BaseMessageTile.js";
/*
map urls:
apple: https://developer.apple.com/library/archive/featuredarticles/iPhoneURLScheme_Reference/MapLinks/MapLinks.html
android: https://developers.google.com/maps/documentation/urls/guide
wp: maps:49.275267 -122.988617
https://www.habaneroconsulting.com/stories/insights/2011/opening-native-map-apps-from-the-mobile-browser
*/
export class LocationTile extends BaseMessageTile { export class LocationTile extends BaseMessageTile {
get shape() {
return "location";
}
get mapsLink() { get mapsLink() {
const geoUri = this._getContent().geo_uri; try {
const [lat, long] = geoUri.split(":")[1].split(","); const url = new URL(this._getContent().geo_uri);
return `maps:${lat} ${long}`; if (url.protocol !== "geo:") {
return "";
}
const [locationStr, ...namedParams] = url.pathname.split(";");
const [latStr, longStr] = locationStr.split(",");
const lat = parseFloat(latStr);
const long = parseFloat(longStr);
let uncertainty;
for (const namedParam of namedParams) {
const [name, value] = namedParam.split("=");
if (name === "u") {
uncertainty = parseFloat(value);
}
}
if (this.platform.isIOS) {
return `http://maps.apple.com/?ll=${lat},${long}`;
} else {
let uri = `geo:${lat},${long}`;
if (uncertainty) {
uri = uri + `;u=${uncertainty}`;
}
return uri;
}
} catch {
return "";
}
} }
get label() { get label() {
return `${this.sender} sent their location, click to see it in maps.`; return this.i18n`${this.displayName} sent their location`;
} }
} }

View file

@ -374,6 +374,18 @@ only loads when the top comes into view*/
animation-timing-function: linear; animation-timing-function: linear;
} }
.Timeline_locationLink {
padding: 0px 8px;
border-radius: 16px;
border: 1px solid #e9edf1;
background-color: #f3f8fd;
text-decoration: none;
display: inline-block;
line-height: 2rem;
vertical-align: top;
margin: 1px 4px;
}
.AnnouncementView { .AnnouncementView {
margin: 5px 0; margin: 5px 0;
padding: 5px 10%; padding: 5px 10%;

View file

@ -39,7 +39,6 @@ export interface TimelineViewModel extends IObservableValue {
export type TileView = GapView | AnnouncementView | TextMessageView | export type TileView = GapView | AnnouncementView | TextMessageView |
ImageView | VideoView | FileView | MissingAttachmentView | RedactedView; ImageView | VideoView | FileView | MissingAttachmentView | RedactedView;
function bottom(node: HTMLElement): number { function bottom(node: HTMLElement): number {
return node.offsetTop + node.clientHeight; return node.offsetTop + node.clientHeight;
} }

View file

@ -18,6 +18,7 @@ import {TextMessageView} from "./timeline/TextMessageView.js";
import {ImageView} from "./timeline/ImageView.js"; import {ImageView} from "./timeline/ImageView.js";
import {VideoView} from "./timeline/VideoView.js"; import {VideoView} from "./timeline/VideoView.js";
import {FileView} from "./timeline/FileView.js"; import {FileView} from "./timeline/FileView.js";
import {LocationView} from "./timeline/LocationView.js";
import {MissingAttachmentView} from "./timeline/MissingAttachmentView.js"; import {MissingAttachmentView} from "./timeline/MissingAttachmentView.js";
import {AnnouncementView} from "./timeline/AnnouncementView.js"; import {AnnouncementView} from "./timeline/AnnouncementView.js";
import {RedactedView} from "./timeline/RedactedView.js"; import {RedactedView} from "./timeline/RedactedView.js";
@ -41,6 +42,8 @@ export function viewClassForEntry(entry: SimpleTile): TileViewConstructor | unde
return VideoView; return VideoView;
case "file": case "file":
return FileView; return FileView;
case "location":
return LocationView;
case "missing-attachment": case "missing-attachment":
return MissingAttachmentView; return MissingAttachmentView;
case "redacted": case "redacted":

View file

@ -19,7 +19,6 @@ import {BaseMediaView} from "./BaseMediaView.js";
export class ImageView extends BaseMediaView { export class ImageView extends BaseMediaView {
renderMedia(t, vm) { renderMedia(t, vm) {
const img = t.img({ const img = t.img({
loading: "lazy",
src: vm => vm.thumbnailUrl, src: vm => vm.thumbnailUrl,
alt: vm => vm.label, alt: vm => vm.label,
title: vm => vm.label, title: vm => vm.label,

View file

@ -0,0 +1,27 @@
/*
Copyright 2020 Bruno Windels <bruno@windels.cloud>
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 {BaseMessageView} from "./BaseMessageView.js";
export class LocationView extends BaseMessageView {
renderMessageBody(t, vm) {
return t.p({className: "Timeline_messageBody statusMessage"}, [
t.span(vm.label),
t.a({className: "Timeline_locationLink", href: vm.mapsLink, target: "_blank", rel: "noopener"}, vm.i18n`Click to open in maps`),
t.time(vm.date + " " + vm.time)
]);
}
}