forked from mystiq/hydrogen-web
Merge branch 'master' into threading-fallback-reply
This commit is contained in:
commit
3c59004e72
13 changed files with 171 additions and 35 deletions
23
doc/SDK.md
23
doc/SDK.md
|
@ -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
|
||||||
|
|
||||||
|
|
|
@ -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.
|
||||||
|
|
|
@ -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"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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"
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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 = {
|
||||||
|
|
|
@ -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());
|
||||||
|
|
|
@ -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`;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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%;
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
|
@ -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":
|
||||||
|
|
|
@ -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,
|
||||||
|
|
27
src/platform/web/ui/session/room/timeline/LocationView.js
Normal file
27
src/platform/web/ui/session/room/timeline/LocationView.js
Normal 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)
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in a new issue