mirror of https://github.com/vector-im/riot-web
Merge branch 'develop' of github.com:matrix-org/matrix-react-sdk into t3chguy/fix/17021
Conflicts: src/components/views/spaces/SpaceCreateMenu.tsxpull/21833/head
commit
2e19c7fd24
112
CHANGELOG.md
112
CHANGELOG.md
|
@ -1,3 +1,115 @@
|
||||||
|
Changes in [3.20.0](https://github.com/matrix-org/matrix-react-sdk/releases/tag/v3.20.0) (2021-05-10)
|
||||||
|
=====================================================================================================
|
||||||
|
[Full Changelog](https://github.com/matrix-org/matrix-react-sdk/compare/v3.20.0-rc.1...v3.20.0)
|
||||||
|
|
||||||
|
* Upgrade to JS SDK 10.1.0
|
||||||
|
* [Release] Don't use the event's metadata to calc the scale of an image
|
||||||
|
[\#6004](https://github.com/matrix-org/matrix-react-sdk/pull/6004)
|
||||||
|
|
||||||
|
Changes in [3.20.0-rc.1](https://github.com/matrix-org/matrix-react-sdk/releases/tag/v3.20.0-rc.1) (2021-05-04)
|
||||||
|
===============================================================================================================
|
||||||
|
[Full Changelog](https://github.com/matrix-org/matrix-react-sdk/compare/v3.19.0...v3.20.0-rc.1)
|
||||||
|
|
||||||
|
* Upgrade to JS SDK 10.1.0-rc.1
|
||||||
|
* Translations update from Weblate
|
||||||
|
[\#5966](https://github.com/matrix-org/matrix-react-sdk/pull/5966)
|
||||||
|
* Fix more space panel layout and hover behaviour issues
|
||||||
|
[\#5965](https://github.com/matrix-org/matrix-react-sdk/pull/5965)
|
||||||
|
* Fix edge case with space panel alignment with subspaces on ff
|
||||||
|
[\#5964](https://github.com/matrix-org/matrix-react-sdk/pull/5964)
|
||||||
|
* Fix saving room pill part to history
|
||||||
|
[\#5951](https://github.com/matrix-org/matrix-react-sdk/pull/5951)
|
||||||
|
* Generate room preview even when minimized
|
||||||
|
[\#5948](https://github.com/matrix-org/matrix-react-sdk/pull/5948)
|
||||||
|
* Another change from recovery passphrase to Security Phrase
|
||||||
|
[\#5934](https://github.com/matrix-org/matrix-react-sdk/pull/5934)
|
||||||
|
* Sort rooms in the add existing to space dialog based on recency
|
||||||
|
[\#5943](https://github.com/matrix-org/matrix-react-sdk/pull/5943)
|
||||||
|
* Inhibit sending RR when context switching to a room
|
||||||
|
[\#5944](https://github.com/matrix-org/matrix-react-sdk/pull/5944)
|
||||||
|
* Prevent room list keyboard handling from landing focus on hidden nodes
|
||||||
|
[\#5950](https://github.com/matrix-org/matrix-react-sdk/pull/5950)
|
||||||
|
* Make the text filter search all spaces instead of just the selected one
|
||||||
|
[\#5942](https://github.com/matrix-org/matrix-react-sdk/pull/5942)
|
||||||
|
* Enable indent rule and fix indent
|
||||||
|
[\#5931](https://github.com/matrix-org/matrix-react-sdk/pull/5931)
|
||||||
|
* Prevent peeking members from reacting
|
||||||
|
[\#5946](https://github.com/matrix-org/matrix-react-sdk/pull/5946)
|
||||||
|
* Disallow inline display maths
|
||||||
|
[\#5939](https://github.com/matrix-org/matrix-react-sdk/pull/5939)
|
||||||
|
* Space creation prompt user to add existing rooms for "Just Me" spaces
|
||||||
|
[\#5923](https://github.com/matrix-org/matrix-react-sdk/pull/5923)
|
||||||
|
* Add test coverage collection script
|
||||||
|
[\#5937](https://github.com/matrix-org/matrix-react-sdk/pull/5937)
|
||||||
|
* Fix joining room using via servers regression
|
||||||
|
[\#5936](https://github.com/matrix-org/matrix-react-sdk/pull/5936)
|
||||||
|
* Revert "Fixes the two Todays problem in Redaction"
|
||||||
|
[\#5938](https://github.com/matrix-org/matrix-react-sdk/pull/5938)
|
||||||
|
* Handle encoded matrix URLs
|
||||||
|
[\#5903](https://github.com/matrix-org/matrix-react-sdk/pull/5903)
|
||||||
|
* Render ignored users setting regardless of if there are any
|
||||||
|
[\#5860](https://github.com/matrix-org/matrix-react-sdk/pull/5860)
|
||||||
|
* Fix inserting trailing colon after mention/pill
|
||||||
|
[\#5830](https://github.com/matrix-org/matrix-react-sdk/pull/5830)
|
||||||
|
* Fixes the two Todays problem in Redaction
|
||||||
|
[\#5917](https://github.com/matrix-org/matrix-react-sdk/pull/5917)
|
||||||
|
* Fix page up/down scrolling only half a page
|
||||||
|
[\#5920](https://github.com/matrix-org/matrix-react-sdk/pull/5920)
|
||||||
|
* Voice messages: Composer controls
|
||||||
|
[\#5935](https://github.com/matrix-org/matrix-react-sdk/pull/5935)
|
||||||
|
* Support MSC3086 asserted identity
|
||||||
|
[\#5886](https://github.com/matrix-org/matrix-react-sdk/pull/5886)
|
||||||
|
* Handle possible edge case with getting stuck in "unsent messages" bar
|
||||||
|
[\#5930](https://github.com/matrix-org/matrix-react-sdk/pull/5930)
|
||||||
|
* Fix suggested rooms not showing up regression from room list optimisation
|
||||||
|
[\#5932](https://github.com/matrix-org/matrix-react-sdk/pull/5932)
|
||||||
|
* Broadcast language change to ElectronPlatform
|
||||||
|
[\#5913](https://github.com/matrix-org/matrix-react-sdk/pull/5913)
|
||||||
|
* Fix VoIP PIP frame color
|
||||||
|
[\#5701](https://github.com/matrix-org/matrix-react-sdk/pull/5701)
|
||||||
|
* Convert some Flow-typed files to TypeScript
|
||||||
|
[\#5912](https://github.com/matrix-org/matrix-react-sdk/pull/5912)
|
||||||
|
* Initial SpaceStore tests work
|
||||||
|
[\#5906](https://github.com/matrix-org/matrix-react-sdk/pull/5906)
|
||||||
|
* Fix issues with space hierarchy in layout and with incompatible servers
|
||||||
|
[\#5926](https://github.com/matrix-org/matrix-react-sdk/pull/5926)
|
||||||
|
* Scale all mxc thumbs using device pixel ratio for hidpi
|
||||||
|
[\#5928](https://github.com/matrix-org/matrix-react-sdk/pull/5928)
|
||||||
|
* Fix add existing to space dialog no longer showing rooms for public spaces
|
||||||
|
[\#5918](https://github.com/matrix-org/matrix-react-sdk/pull/5918)
|
||||||
|
* Disable spaces context switching for when exploring a space
|
||||||
|
[\#5924](https://github.com/matrix-org/matrix-react-sdk/pull/5924)
|
||||||
|
* Autofocus search box in the add existing to space dialog
|
||||||
|
[\#5921](https://github.com/matrix-org/matrix-react-sdk/pull/5921)
|
||||||
|
* Use label element in add existing to space dialog for easier hit target
|
||||||
|
[\#5922](https://github.com/matrix-org/matrix-react-sdk/pull/5922)
|
||||||
|
* Dynamic max and min zoom in the new ImageView
|
||||||
|
[\#5916](https://github.com/matrix-org/matrix-react-sdk/pull/5916)
|
||||||
|
* Improve message error states
|
||||||
|
[\#5897](https://github.com/matrix-org/matrix-react-sdk/pull/5897)
|
||||||
|
* Check for null room in `VisibilityProvider`
|
||||||
|
[\#5914](https://github.com/matrix-org/matrix-react-sdk/pull/5914)
|
||||||
|
* Add unit tests for various collection-based utility functions
|
||||||
|
[\#5910](https://github.com/matrix-org/matrix-react-sdk/pull/5910)
|
||||||
|
* Spaces visual fixes
|
||||||
|
[\#5909](https://github.com/matrix-org/matrix-react-sdk/pull/5909)
|
||||||
|
* Remove reliance on DOM API to generated message preview
|
||||||
|
[\#5908](https://github.com/matrix-org/matrix-react-sdk/pull/5908)
|
||||||
|
* Expand upon voice message event & include overall waveform
|
||||||
|
[\#5888](https://github.com/matrix-org/matrix-react-sdk/pull/5888)
|
||||||
|
* Use floats for image background opacity
|
||||||
|
[\#5905](https://github.com/matrix-org/matrix-react-sdk/pull/5905)
|
||||||
|
* Show invites to spaces at the top of the space panel
|
||||||
|
[\#5902](https://github.com/matrix-org/matrix-react-sdk/pull/5902)
|
||||||
|
* Improve edge cases with spaces context switching
|
||||||
|
[\#5899](https://github.com/matrix-org/matrix-react-sdk/pull/5899)
|
||||||
|
* Fix spaces notification dots wrongly including upgraded (hidden) rooms
|
||||||
|
[\#5900](https://github.com/matrix-org/matrix-react-sdk/pull/5900)
|
||||||
|
* Iterate the spaces face pile design
|
||||||
|
[\#5898](https://github.com/matrix-org/matrix-react-sdk/pull/5898)
|
||||||
|
* Fix alignment issue with nested spaces being cut off wrong
|
||||||
|
[\#5890](https://github.com/matrix-org/matrix-react-sdk/pull/5890)
|
||||||
|
|
||||||
Changes in [3.19.0](https://github.com/matrix-org/matrix-react-sdk/releases/tag/v3.19.0) (2021-04-26)
|
Changes in [3.19.0](https://github.com/matrix-org/matrix-react-sdk/releases/tag/v3.19.0) (2021-04-26)
|
||||||
=====================================================================================================
|
=====================================================================================================
|
||||||
[Full Changelog](https://github.com/matrix-org/matrix-react-sdk/compare/v3.19.0-rc.1...v3.19.0)
|
[Full Changelog](https://github.com/matrix-org/matrix-react-sdk/compare/v3.19.0-rc.1...v3.19.0)
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "matrix-react-sdk",
|
"name": "matrix-react-sdk",
|
||||||
"version": "3.19.0",
|
"version": "3.20.0",
|
||||||
"description": "SDK for matrix.org using React",
|
"description": "SDK for matrix.org using React",
|
||||||
"author": "matrix.org",
|
"author": "matrix.org",
|
||||||
"repository": {
|
"repository": {
|
||||||
|
@ -58,7 +58,7 @@
|
||||||
"blueimp-canvas-to-blob": "^3.28.0",
|
"blueimp-canvas-to-blob": "^3.28.0",
|
||||||
"browser-encrypt-attachment": "^0.3.0",
|
"browser-encrypt-attachment": "^0.3.0",
|
||||||
"browser-request": "^0.3.3",
|
"browser-request": "^0.3.3",
|
||||||
"cheerio": "^1.0.0-rc.5",
|
"cheerio": "^1.0.0-rc.9",
|
||||||
"classnames": "^2.2.6",
|
"classnames": "^2.2.6",
|
||||||
"commonmark": "^0.29.3",
|
"commonmark": "^0.29.3",
|
||||||
"counterpart": "^0.18.6",
|
"counterpart": "^0.18.6",
|
||||||
|
@ -97,7 +97,7 @@
|
||||||
"react-transition-group": "^4.4.1",
|
"react-transition-group": "^4.4.1",
|
||||||
"resize-observer-polyfill": "^1.5.1",
|
"resize-observer-polyfill": "^1.5.1",
|
||||||
"rfc4648": "^1.4.0",
|
"rfc4648": "^1.4.0",
|
||||||
"sanitize-html": "github:apostrophecms/sanitize-html#3c7f93f2058f696f5359e3e58d464161647226db",
|
"sanitize-html": "^2.3.2",
|
||||||
"tar-js": "^0.3.0",
|
"tar-js": "^0.3.0",
|
||||||
"text-encoding-utf-8": "^1.0.2",
|
"text-encoding-utf-8": "^1.0.2",
|
||||||
"url": "^0.11.0",
|
"url": "^0.11.0",
|
||||||
|
@ -132,11 +132,12 @@
|
||||||
"@types/modernizr": "^3.5.3",
|
"@types/modernizr": "^3.5.3",
|
||||||
"@types/node": "^14.14.22",
|
"@types/node": "^14.14.22",
|
||||||
"@types/pako": "^1.0.1",
|
"@types/pako": "^1.0.1",
|
||||||
|
"@types/parse5": "^6.0.0",
|
||||||
"@types/qrcode": "^1.3.5",
|
"@types/qrcode": "^1.3.5",
|
||||||
"@types/react": "^16.9",
|
"@types/react": "^16.9",
|
||||||
"@types/react-dom": "^16.9.10",
|
"@types/react-dom": "^16.9.10",
|
||||||
"@types/react-transition-group": "^4.4.0",
|
"@types/react-transition-group": "^4.4.0",
|
||||||
"@types/sanitize-html": "^1.27.0",
|
"@types/sanitize-html": "^2.3.1",
|
||||||
"@types/zxcvbn": "^4.4.0",
|
"@types/zxcvbn": "^4.4.0",
|
||||||
"@typescript-eslint/eslint-plugin": "^4.14.0",
|
"@typescript-eslint/eslint-plugin": "^4.14.0",
|
||||||
"@typescript-eslint/parser": "^4.14.0",
|
"@typescript-eslint/parser": "^4.14.0",
|
||||||
|
|
|
@ -237,7 +237,6 @@ $activeBorderColor: $secondary-fg-color;
|
||||||
|
|
||||||
.mx_SpacePanel_badgeContainer {
|
.mx_SpacePanel_badgeContainer {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
height: 16px;
|
|
||||||
|
|
||||||
// Create a flexbox to make aligning dot badges easier
|
// Create a flexbox to make aligning dot badges easier
|
||||||
display: flex;
|
display: flex;
|
||||||
|
@ -249,23 +248,37 @@ $activeBorderColor: $secondary-fg-color;
|
||||||
|
|
||||||
.mx_NotificationBadge_dot {
|
.mx_NotificationBadge_dot {
|
||||||
// make the smaller dot occupy the same width for centering
|
// make the smaller dot occupy the same width for centering
|
||||||
margin-left: 7px;
|
margin: 0 7px;
|
||||||
margin-right: 7px;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
&.collapsed {
|
&.collapsed {
|
||||||
.mx_SpaceButton {
|
.mx_SpaceButton {
|
||||||
.mx_SpacePanel_badgeContainer {
|
.mx_SpacePanel_badgeContainer {
|
||||||
right: -3px;
|
right: 0;
|
||||||
top: -3px;
|
top: 0;
|
||||||
|
|
||||||
|
.mx_NotificationBadge {
|
||||||
|
background-clip: padding-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mx_NotificationBadge_dot {
|
||||||
|
margin: 0 -1px 0 0;
|
||||||
|
border: 3px solid $groupFilterPanel-bg-color;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mx_NotificationBadge_2char,
|
||||||
|
.mx_NotificationBadge_3char {
|
||||||
|
margin: -5px -5px 0 0;
|
||||||
|
border: 2px solid $groupFilterPanel-bg-color;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
&.mx_SpaceButton_active .mx_SpacePanel_badgeContainer {
|
&.mx_SpaceButton_active .mx_SpacePanel_badgeContainer {
|
||||||
// when we draw the selection border we move the relative bounds of our parent
|
// when we draw the selection border we move the relative bounds of our parent
|
||||||
// so update our position within the bounds of the parent to maintain position overall
|
// so update our position within the bounds of the parent to maintain position overall
|
||||||
right: -6px;
|
right: -3px;
|
||||||
top: -6px;
|
top: -3px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -103,6 +103,10 @@ $SpaceRoomViewInnerWidth: 428px;
|
||||||
padding: 8px 22px;
|
padding: 8px 22px;
|
||||||
margin-left: 16px;
|
margin-left: 16px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
input.mx_AccessibleButton {
|
||||||
|
border: none; // override default styles
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.mx_Field {
|
.mx_Field {
|
||||||
|
|
|
@ -46,11 +46,11 @@ limitations under the License.
|
||||||
mask-image: url('$(res)/img/element-icons/trashcan.svg');
|
mask-image: url('$(res)/img/element-icons/trashcan.svg');
|
||||||
}
|
}
|
||||||
|
|
||||||
.mx_VoiceRecordComposerTile_recording.mx_VoiceMessagePrimaryContainer {
|
.mx_MessageComposer_row .mx_VoiceMessagePrimaryContainer {
|
||||||
// Note: remaining class properties are in the PlayerContainer CSS.
|
// Note: remaining class properties are in the PlayerContainer CSS.
|
||||||
|
|
||||||
margin: 6px; // force the composer area to put a gutter around us
|
margin: 6px; // force the composer area to put a gutter around us
|
||||||
margin-right: 12px; // isolate from stop button
|
margin-right: 12px; // isolate from stop/send button
|
||||||
|
|
||||||
position: relative; // important for the live circle
|
position: relative; // important for the live circle
|
||||||
|
|
||||||
|
|
|
@ -422,8 +422,12 @@ export function bodyToHtml(content: IContent, highlights: string[], opts: IOpts
|
||||||
safeBody = sanitizeHtml(formattedBody, sanitizeParams);
|
safeBody = sanitizeHtml(formattedBody, sanitizeParams);
|
||||||
|
|
||||||
if (SettingsStore.getValue("feature_latex_maths")) {
|
if (SettingsStore.getValue("feature_latex_maths")) {
|
||||||
const phtml = cheerio.load(safeBody,
|
const phtml = cheerio.load(safeBody, {
|
||||||
{ _useHtmlParser2: true, decodeEntities: false })
|
// @ts-ignore: The `_useHtmlParser2` internal option is the
|
||||||
|
// simplest way to both parse and render using `htmlparser2`.
|
||||||
|
_useHtmlParser2: true,
|
||||||
|
decodeEntities: false,
|
||||||
|
});
|
||||||
// @ts-ignore - The types for `replaceWith` wrongly expect
|
// @ts-ignore - The types for `replaceWith` wrongly expect
|
||||||
// Cheerio instance to be returned.
|
// Cheerio instance to be returned.
|
||||||
phtml('div, span[data-mx-maths!=""]').replaceWith(function(i, e) {
|
phtml('div, span[data-mx-maths!=""]').replaceWith(function(i, e) {
|
||||||
|
@ -431,6 +435,7 @@ export function bodyToHtml(content: IContent, highlights: string[], opts: IOpts
|
||||||
AllHtmlEntities.decode(phtml(e).attr('data-mx-maths')),
|
AllHtmlEntities.decode(phtml(e).attr('data-mx-maths')),
|
||||||
{
|
{
|
||||||
throwOnError: false,
|
throwOnError: false,
|
||||||
|
// @ts-ignore - `e` can be an Element, not just a Node
|
||||||
displayMode: e.name == 'div',
|
displayMode: e.name == 'div',
|
||||||
output: "htmlAndMathml",
|
output: "htmlAndMathml",
|
||||||
});
|
});
|
||||||
|
|
|
@ -38,7 +38,7 @@ import {isPermalinkHost, parsePermalink} from "./utils/permalinks/Permalinks";
|
||||||
import {inviteUsersToRoom} from "./RoomInvite";
|
import {inviteUsersToRoom} from "./RoomInvite";
|
||||||
import { WidgetType } from "./widgets/WidgetType";
|
import { WidgetType } from "./widgets/WidgetType";
|
||||||
import { Jitsi } from "./widgets/Jitsi";
|
import { Jitsi } from "./widgets/Jitsi";
|
||||||
import { parseFragment as parseHtml } from "parse5";
|
import { parseFragment as parseHtml, Element as ChildElement } from "parse5";
|
||||||
import BugReportDialog from "./components/views/dialogs/BugReportDialog";
|
import BugReportDialog from "./components/views/dialogs/BugReportDialog";
|
||||||
import { ensureDMExists } from "./createRoom";
|
import { ensureDMExists } from "./createRoom";
|
||||||
import { ViewUserPayload } from "./dispatcher/payloads/ViewUserPayload";
|
import { ViewUserPayload } from "./dispatcher/payloads/ViewUserPayload";
|
||||||
|
@ -856,7 +856,7 @@ export const Commands = [
|
||||||
// some superfast regex over the text so we don't have to.
|
// some superfast regex over the text so we don't have to.
|
||||||
const embed = parseHtml(widgetUrl);
|
const embed = parseHtml(widgetUrl);
|
||||||
if (embed && embed.childNodes && embed.childNodes.length === 1) {
|
if (embed && embed.childNodes && embed.childNodes.length === 1) {
|
||||||
const iframe = embed.childNodes[0];
|
const iframe = embed.childNodes[0] as ChildElement;
|
||||||
if (iframe.tagName.toLowerCase() === 'iframe' && iframe.attrs) {
|
if (iframe.tagName.toLowerCase() === 'iframe' && iframe.attrs) {
|
||||||
const srcAttr = iframe.attrs.find(a => a.name === 'src');
|
const srcAttr = iframe.attrs.find(a => a.name === 'src');
|
||||||
console.log("Pulling URL out of iframe (embed code)");
|
console.log("Pulling URL out of iframe (embed code)");
|
||||||
|
|
|
@ -544,11 +544,13 @@ export default class MessagePanel extends React.Component {
|
||||||
}
|
}
|
||||||
if (!grouper) {
|
if (!grouper) {
|
||||||
const wantTile = this._shouldShowEvent(mxEv);
|
const wantTile = this._shouldShowEvent(mxEv);
|
||||||
|
const isGrouped = false;
|
||||||
if (wantTile) {
|
if (wantTile) {
|
||||||
// make sure we unpack the array returned by _getTilesForEvent,
|
// make sure we unpack the array returned by _getTilesForEvent,
|
||||||
// otherwise react will auto-generate keys and we will end up
|
// otherwise react will auto-generate keys and we will end up
|
||||||
// replacing all of the DOM elements every time we paginate.
|
// replacing all of the DOM elements every time we paginate.
|
||||||
ret.push(...this._getTilesForEvent(prevEvent, mxEv, last, nextEvent, nextTile));
|
ret.push(...this._getTilesForEvent(prevEvent, mxEv, last, isGrouped,
|
||||||
|
nextEvent, nextTile));
|
||||||
prevEvent = mxEv;
|
prevEvent = mxEv;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -564,7 +566,7 @@ export default class MessagePanel extends React.Component {
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
_getTilesForEvent(prevEvent, mxEv, last, nextEvent, nextEventWithTile) {
|
_getTilesForEvent(prevEvent, mxEv, last, isGrouped=false, nextEvent, nextEventWithTile) {
|
||||||
const TileErrorBoundary = sdk.getComponent('messages.TileErrorBoundary');
|
const TileErrorBoundary = sdk.getComponent('messages.TileErrorBoundary');
|
||||||
const EventTile = sdk.getComponent('rooms.EventTile');
|
const EventTile = sdk.getComponent('rooms.EventTile');
|
||||||
const DateSeparator = sdk.getComponent('messages.DateSeparator');
|
const DateSeparator = sdk.getComponent('messages.DateSeparator');
|
||||||
|
@ -584,7 +586,7 @@ export default class MessagePanel extends React.Component {
|
||||||
|
|
||||||
// do we need a date separator since the last event?
|
// do we need a date separator since the last event?
|
||||||
const wantsDateSeparator = this._wantsDateSeparator(prevEvent, eventDate);
|
const wantsDateSeparator = this._wantsDateSeparator(prevEvent, eventDate);
|
||||||
if (wantsDateSeparator) {
|
if (wantsDateSeparator && !isGrouped) {
|
||||||
const dateSeparator = <li key={ts1}><DateSeparator key={ts1} ts={ts1} /></li>;
|
const dateSeparator = <li key={ts1}><DateSeparator key={ts1} ts={ts1} /></li>;
|
||||||
ret.push(dateSeparator);
|
ret.push(dateSeparator);
|
||||||
}
|
}
|
||||||
|
@ -968,9 +970,9 @@ class CreationGrouper {
|
||||||
|
|
||||||
const DateSeparator = sdk.getComponent('messages.DateSeparator');
|
const DateSeparator = sdk.getComponent('messages.DateSeparator');
|
||||||
const EventListSummary = sdk.getComponent('views.elements.EventListSummary');
|
const EventListSummary = sdk.getComponent('views.elements.EventListSummary');
|
||||||
|
|
||||||
const panel = this.panel;
|
const panel = this.panel;
|
||||||
const ret = [];
|
const ret = [];
|
||||||
|
const isGrouped = true;
|
||||||
const createEvent = this.createEvent;
|
const createEvent = this.createEvent;
|
||||||
const lastShownEvent = this.lastShownEvent;
|
const lastShownEvent = this.lastShownEvent;
|
||||||
|
|
||||||
|
@ -984,12 +986,12 @@ class CreationGrouper {
|
||||||
// If this m.room.create event should be shown (room upgrade) then show it before the summary
|
// If this m.room.create event should be shown (room upgrade) then show it before the summary
|
||||||
if (panel._shouldShowEvent(createEvent)) {
|
if (panel._shouldShowEvent(createEvent)) {
|
||||||
// pass in the createEvent as prevEvent as well so no extra DateSeparator is rendered
|
// pass in the createEvent as prevEvent as well so no extra DateSeparator is rendered
|
||||||
ret.push(...panel._getTilesForEvent(createEvent, createEvent, false));
|
ret.push(...panel._getTilesForEvent(createEvent, createEvent));
|
||||||
}
|
}
|
||||||
|
|
||||||
for (const ejected of this.ejectedEvents) {
|
for (const ejected of this.ejectedEvents) {
|
||||||
ret.push(...panel._getTilesForEvent(
|
ret.push(...panel._getTilesForEvent(
|
||||||
createEvent, ejected, createEvent === lastShownEvent,
|
createEvent, ejected, createEvent === lastShownEvent, isGrouped,
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -998,7 +1000,7 @@ class CreationGrouper {
|
||||||
// of EventListSummary, render each member event as if the previous
|
// of EventListSummary, render each member event as if the previous
|
||||||
// one was itself. This way, the timestamp of the previous event === the
|
// one was itself. This way, the timestamp of the previous event === the
|
||||||
// timestamp of the current event, and no DateSeparator is inserted.
|
// timestamp of the current event, and no DateSeparator is inserted.
|
||||||
return panel._getTilesForEvent(e, e, e === lastShownEvent);
|
return panel._getTilesForEvent(e, e, e === lastShownEvent, isGrouped);
|
||||||
}).reduce((a, b) => a.concat(b), []);
|
}).reduce((a, b) => a.concat(b), []);
|
||||||
// Get sender profile from the latest event in the summary as the m.room.create doesn't contain one
|
// Get sender profile from the latest event in the summary as the m.room.create doesn't contain one
|
||||||
const ev = this.events[this.events.length - 1];
|
const ev = this.events[this.events.length - 1];
|
||||||
|
@ -1083,7 +1085,7 @@ class RedactionGrouper {
|
||||||
|
|
||||||
const DateSeparator = sdk.getComponent('messages.DateSeparator');
|
const DateSeparator = sdk.getComponent('messages.DateSeparator');
|
||||||
const EventListSummary = sdk.getComponent('views.elements.EventListSummary');
|
const EventListSummary = sdk.getComponent('views.elements.EventListSummary');
|
||||||
|
const isGrouped = true;
|
||||||
const panel = this.panel;
|
const panel = this.panel;
|
||||||
const ret = [];
|
const ret = [];
|
||||||
const lastShownEvent = this.lastShownEvent;
|
const lastShownEvent = this.lastShownEvent;
|
||||||
|
@ -1103,7 +1105,8 @@ class RedactionGrouper {
|
||||||
let eventTiles = this.events.map((e, i) => {
|
let eventTiles = this.events.map((e, i) => {
|
||||||
senders.add(e.sender);
|
senders.add(e.sender);
|
||||||
const prevEvent = i === 0 ? this.prevEvent : this.events[i - 1];
|
const prevEvent = i === 0 ? this.prevEvent : this.events[i - 1];
|
||||||
return panel._getTilesForEvent(prevEvent, e, e === lastShownEvent, this.nextEvent, this.nextEventTile);
|
return panel._getTilesForEvent(
|
||||||
|
prevEvent, e, e === lastShownEvent, isGrouped, this.nextEvent, this.nextEventTile);
|
||||||
}).reduce((a, b) => a.concat(b), []);
|
}).reduce((a, b) => a.concat(b), []);
|
||||||
|
|
||||||
if (eventTiles.length === 0) {
|
if (eventTiles.length === 0) {
|
||||||
|
@ -1182,7 +1185,7 @@ class MemberGrouper {
|
||||||
|
|
||||||
const DateSeparator = sdk.getComponent('messages.DateSeparator');
|
const DateSeparator = sdk.getComponent('messages.DateSeparator');
|
||||||
const MemberEventListSummary = sdk.getComponent('views.elements.MemberEventListSummary');
|
const MemberEventListSummary = sdk.getComponent('views.elements.MemberEventListSummary');
|
||||||
|
const isGrouped = true;
|
||||||
const panel = this.panel;
|
const panel = this.panel;
|
||||||
const lastShownEvent = this.lastShownEvent;
|
const lastShownEvent = this.lastShownEvent;
|
||||||
const ret = [];
|
const ret = [];
|
||||||
|
@ -1215,7 +1218,7 @@ class MemberGrouper {
|
||||||
// of MemberEventListSummary, render each member event as if the previous
|
// of MemberEventListSummary, render each member event as if the previous
|
||||||
// one was itself. This way, the timestamp of the previous event === the
|
// one was itself. This way, the timestamp of the previous event === the
|
||||||
// timestamp of the current event, and no DateSeparator is inserted.
|
// timestamp of the current event, and no DateSeparator is inserted.
|
||||||
return panel._getTilesForEvent(e, e, e === lastShownEvent);
|
return panel._getTilesForEvent(e, e, e === lastShownEvent, isGrouped);
|
||||||
}).reduce((a, b) => a.concat(b), []);
|
}).reduce((a, b) => a.concat(b), []);
|
||||||
|
|
||||||
if (eventTiles.length === 0) {
|
if (eventTiles.length === 0) {
|
||||||
|
|
|
@ -27,8 +27,8 @@ import { Action } from "../../dispatcher/actions";
|
||||||
import RoomListStore from "../../stores/room-list/RoomListStore";
|
import RoomListStore from "../../stores/room-list/RoomListStore";
|
||||||
import { NameFilterCondition } from "../../stores/room-list/filters/NameFilterCondition";
|
import { NameFilterCondition } from "../../stores/room-list/filters/NameFilterCondition";
|
||||||
import { getKeyBindingsManager, RoomListAction } from "../../KeyBindingsManager";
|
import { getKeyBindingsManager, RoomListAction } from "../../KeyBindingsManager";
|
||||||
import {replaceableComponent} from "../../utils/replaceableComponent";
|
import { replaceableComponent } from "../../utils/replaceableComponent";
|
||||||
import SpaceStore, {UPDATE_SELECTED_SPACE, UPDATE_TOP_LEVEL_SPACES} from "../../stores/SpaceStore";
|
import SpaceStore, { UPDATE_SELECTED_SPACE, UPDATE_TOP_LEVEL_SPACES } from "../../stores/SpaceStore";
|
||||||
|
|
||||||
interface IProps {
|
interface IProps {
|
||||||
isMinimized: boolean;
|
isMinimized: boolean;
|
||||||
|
|
|
@ -430,7 +430,9 @@ const SpaceSetupFirstRooms = ({ space, title, description, onFinished }) => {
|
||||||
/>;
|
/>;
|
||||||
});
|
});
|
||||||
|
|
||||||
const onNextClick = async () => {
|
const onNextClick = async (ev) => {
|
||||||
|
ev.preventDefault();
|
||||||
|
if (busy) return;
|
||||||
setError("");
|
setError("");
|
||||||
setBusy(true);
|
setBusy(true);
|
||||||
try {
|
try {
|
||||||
|
@ -455,7 +457,10 @@ const SpaceSetupFirstRooms = ({ space, title, description, onFinished }) => {
|
||||||
setBusy(false);
|
setBusy(false);
|
||||||
};
|
};
|
||||||
|
|
||||||
let onClick = onFinished;
|
let onClick = (ev) => {
|
||||||
|
ev.preventDefault();
|
||||||
|
onFinished();
|
||||||
|
};
|
||||||
let buttonLabel = _t("Skip for now");
|
let buttonLabel = _t("Skip for now");
|
||||||
if (roomNames.some(name => name.trim())) {
|
if (roomNames.some(name => name.trim())) {
|
||||||
onClick = onNextClick;
|
onClick = onNextClick;
|
||||||
|
@ -467,16 +472,20 @@ const SpaceSetupFirstRooms = ({ space, title, description, onFinished }) => {
|
||||||
<div className="mx_SpaceRoomView_description">{ description }</div>
|
<div className="mx_SpaceRoomView_description">{ description }</div>
|
||||||
|
|
||||||
{ error && <div className="mx_SpaceRoomView_errorText">{ error }</div> }
|
{ error && <div className="mx_SpaceRoomView_errorText">{ error }</div> }
|
||||||
{ fields }
|
<form onSubmit={onClick} id="mx_SpaceSetupFirstRooms">
|
||||||
|
{ fields }
|
||||||
|
</form>
|
||||||
|
|
||||||
<div className="mx_SpaceRoomView_buttons">
|
<div className="mx_SpaceRoomView_buttons">
|
||||||
<AccessibleButton
|
<AccessibleButton
|
||||||
kind="primary"
|
kind="primary"
|
||||||
disabled={busy}
|
disabled={busy}
|
||||||
onClick={onClick}
|
onClick={onClick}
|
||||||
>
|
element="input"
|
||||||
{ buttonLabel }
|
type="submit"
|
||||||
</AccessibleButton>
|
form="mx_SpaceSetupFirstRooms"
|
||||||
|
value={buttonLabel}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>;
|
</div>;
|
||||||
};
|
};
|
||||||
|
@ -624,7 +633,9 @@ const SpaceSetupPrivateInvite = ({ space, onFinished }) => {
|
||||||
/>;
|
/>;
|
||||||
});
|
});
|
||||||
|
|
||||||
const onNextClick = async () => {
|
const onNextClick = async (ev) => {
|
||||||
|
ev.preventDefault();
|
||||||
|
if (busy) return;
|
||||||
setError("");
|
setError("");
|
||||||
for (let i = 0; i < fieldRefs.length; i++) {
|
for (let i = 0; i < fieldRefs.length; i++) {
|
||||||
const fieldRef = fieldRefs[i];
|
const fieldRef = fieldRefs[i];
|
||||||
|
@ -658,7 +669,10 @@ const SpaceSetupPrivateInvite = ({ space, onFinished }) => {
|
||||||
setBusy(false);
|
setBusy(false);
|
||||||
};
|
};
|
||||||
|
|
||||||
let onClick = onFinished;
|
let onClick = (ev) => {
|
||||||
|
ev.preventDefault();
|
||||||
|
onFinished();
|
||||||
|
};
|
||||||
let buttonLabel = _t("Skip for now");
|
let buttonLabel = _t("Skip for now");
|
||||||
if (emailAddresses.some(name => name.trim())) {
|
if (emailAddresses.some(name => name.trim())) {
|
||||||
onClick = onNextClick;
|
onClick = onNextClick;
|
||||||
|
@ -683,7 +697,9 @@ const SpaceSetupPrivateInvite = ({ space, onFinished }) => {
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{ error && <div className="mx_SpaceRoomView_errorText">{ error }</div> }
|
{ error && <div className="mx_SpaceRoomView_errorText">{ error }</div> }
|
||||||
{ fields }
|
<form onSubmit={onClick} id="mx_SpaceSetupPrivateInvite">
|
||||||
|
{ fields }
|
||||||
|
</form>
|
||||||
|
|
||||||
<div className="mx_SpaceRoomView_inviteTeammates_buttons">
|
<div className="mx_SpaceRoomView_inviteTeammates_buttons">
|
||||||
<AccessibleButton
|
<AccessibleButton
|
||||||
|
@ -695,9 +711,15 @@ const SpaceSetupPrivateInvite = ({ space, onFinished }) => {
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="mx_SpaceRoomView_buttons">
|
<div className="mx_SpaceRoomView_buttons">
|
||||||
<AccessibleButton kind="primary" disabled={busy} onClick={onClick}>
|
<AccessibleButton
|
||||||
{ buttonLabel }
|
kind="primary"
|
||||||
</AccessibleButton>
|
disabled={busy}
|
||||||
|
onClick={onClick}
|
||||||
|
element="input"
|
||||||
|
type="submit"
|
||||||
|
form="mx_SpaceSetupPrivateInvite"
|
||||||
|
value={buttonLabel}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>;
|
</div>;
|
||||||
};
|
};
|
||||||
|
|
|
@ -78,8 +78,10 @@ export default class MessageContextMenu extends React.Component {
|
||||||
|
|
||||||
// We explicitly decline to show the redact option on ACL events as it has a potential
|
// We explicitly decline to show the redact option on ACL events as it has a potential
|
||||||
// to obliterate the room - https://github.com/matrix-org/synapse/issues/4042
|
// to obliterate the room - https://github.com/matrix-org/synapse/issues/4042
|
||||||
|
// Similarly for encryption events, since redacting them "breaks everything"
|
||||||
const canRedact = room.currentState.maySendRedactionForEvent(this.props.mxEvent, cli.credentials.userId)
|
const canRedact = room.currentState.maySendRedactionForEvent(this.props.mxEvent, cli.credentials.userId)
|
||||||
&& this.props.mxEvent.getType() !== EventType.RoomServerAcl;
|
&& this.props.mxEvent.getType() !== EventType.RoomServerAcl
|
||||||
|
&& this.props.mxEvent.getType() !== EventType.RoomEncryption;
|
||||||
let canPin = room.currentState.mayClientSendStateEvent('m.room.pinned_events', cli);
|
let canPin = room.currentState.mayClientSendStateEvent('m.room.pinned_events', cli);
|
||||||
|
|
||||||
// HACK: Intentionally say we can't pin if the user doesn't want to use the functionality
|
// HACK: Intentionally say we can't pin if the user doesn't want to use the functionality
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
/*
|
/*
|
||||||
Copyright 2019 New Vector Ltd
|
Copyright 2019, 2021 The Matrix.org Foundation C.I.C.
|
||||||
Copyright 2019 Michael Telatynski <7t3chguy@gmail.com>
|
Copyright 2019 Michael Telatynski <7t3chguy@gmail.com>
|
||||||
|
|
||||||
Licensed under the Apache License, Version 2.0 (the "License");
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
@ -16,20 +16,23 @@ limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import PropTypes from 'prop-types';
|
|
||||||
import * as sdk from '../../../index';
|
import * as sdk from '../../../index';
|
||||||
import { _t } from '../../../languageHandler';
|
import { _t } from '../../../languageHandler';
|
||||||
import filesize from "filesize";
|
import filesize from "filesize";
|
||||||
import {replaceableComponent} from "../../../utils/replaceableComponent";
|
import { replaceableComponent } from "../../../utils/replaceableComponent";
|
||||||
|
import { getBlobSafeMimeType } from '../../../utils/blobs';
|
||||||
|
|
||||||
|
interface IProps {
|
||||||
|
file: File;
|
||||||
|
currentIndex: number;
|
||||||
|
totalFiles?: number;
|
||||||
|
onFinished: (uploadConfirmed: boolean, uploadAll?: boolean) => void;
|
||||||
|
}
|
||||||
|
|
||||||
@replaceableComponent("views.dialogs.UploadConfirmDialog")
|
@replaceableComponent("views.dialogs.UploadConfirmDialog")
|
||||||
export default class UploadConfirmDialog extends React.Component {
|
export default class UploadConfirmDialog extends React.Component<IProps> {
|
||||||
static propTypes = {
|
private objectUrl: string;
|
||||||
file: PropTypes.object.isRequired,
|
private mimeType: string;
|
||||||
currentIndex: PropTypes.number,
|
|
||||||
totalFiles: PropTypes.number,
|
|
||||||
onFinished: PropTypes.func.isRequired,
|
|
||||||
}
|
|
||||||
|
|
||||||
static defaultProps = {
|
static defaultProps = {
|
||||||
totalFiles: 1,
|
totalFiles: 1,
|
||||||
|
@ -38,22 +41,28 @@ export default class UploadConfirmDialog extends React.Component {
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
super(props);
|
super(props);
|
||||||
|
|
||||||
this._objectUrl = URL.createObjectURL(props.file);
|
// Create a fresh `Blob` for previewing (even though `File` already is
|
||||||
|
// one) so we can adjust the MIME type if needed.
|
||||||
|
this.mimeType = getBlobSafeMimeType(props.file.type);
|
||||||
|
const blob = new Blob([props.file], { type:
|
||||||
|
this.mimeType,
|
||||||
|
});
|
||||||
|
this.objectUrl = URL.createObjectURL(blob);
|
||||||
}
|
}
|
||||||
|
|
||||||
componentWillUnmount() {
|
componentWillUnmount() {
|
||||||
if (this._objectUrl) URL.revokeObjectURL(this._objectUrl);
|
if (this.objectUrl) URL.revokeObjectURL(this.objectUrl);
|
||||||
}
|
}
|
||||||
|
|
||||||
_onCancelClick = () => {
|
private onCancelClick = () => {
|
||||||
this.props.onFinished(false);
|
this.props.onFinished(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
_onUploadClick = () => {
|
private onUploadClick = () => {
|
||||||
this.props.onFinished(true);
|
this.props.onFinished(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
_onUploadAllClick = () => {
|
private onUploadAllClick = () => {
|
||||||
this.props.onFinished(true, true);
|
this.props.onFinished(true, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -75,10 +84,10 @@ export default class UploadConfirmDialog extends React.Component {
|
||||||
}
|
}
|
||||||
|
|
||||||
let preview;
|
let preview;
|
||||||
if (this.props.file.type.startsWith('image/')) {
|
if (this.mimeType.startsWith('image/')) {
|
||||||
preview = <div className="mx_UploadConfirmDialog_previewOuter">
|
preview = <div className="mx_UploadConfirmDialog_previewOuter">
|
||||||
<div className="mx_UploadConfirmDialog_previewInner">
|
<div className="mx_UploadConfirmDialog_previewInner">
|
||||||
<div><img className="mx_UploadConfirmDialog_imagePreview" src={this._objectUrl} /></div>
|
<div><img className="mx_UploadConfirmDialog_imagePreview" src={this.objectUrl} /></div>
|
||||||
<div>{this.props.file.name} ({filesize(this.props.file.size)})</div>
|
<div>{this.props.file.name} ({filesize(this.props.file.size)})</div>
|
||||||
</div>
|
</div>
|
||||||
</div>;
|
</div>;
|
||||||
|
@ -95,7 +104,7 @@ export default class UploadConfirmDialog extends React.Component {
|
||||||
|
|
||||||
let uploadAllButton;
|
let uploadAllButton;
|
||||||
if (this.props.currentIndex + 1 < this.props.totalFiles) {
|
if (this.props.currentIndex + 1 < this.props.totalFiles) {
|
||||||
uploadAllButton = <button onClick={this._onUploadAllClick}>
|
uploadAllButton = <button onClick={this.onUploadAllClick}>
|
||||||
{_t("Upload all")}
|
{_t("Upload all")}
|
||||||
</button>;
|
</button>;
|
||||||
}
|
}
|
||||||
|
@ -103,7 +112,7 @@ export default class UploadConfirmDialog extends React.Component {
|
||||||
return (
|
return (
|
||||||
<BaseDialog className='mx_UploadConfirmDialog'
|
<BaseDialog className='mx_UploadConfirmDialog'
|
||||||
fixedWidth={false}
|
fixedWidth={false}
|
||||||
onFinished={this._onCancelClick}
|
onFinished={this.onCancelClick}
|
||||||
title={title}
|
title={title}
|
||||||
contentId='mx_Dialog_content'
|
contentId='mx_Dialog_content'
|
||||||
>
|
>
|
||||||
|
@ -113,7 +122,7 @@ export default class UploadConfirmDialog extends React.Component {
|
||||||
|
|
||||||
<DialogButtons primaryButton={_t('Upload')}
|
<DialogButtons primaryButton={_t('Upload')}
|
||||||
hasCancel={false}
|
hasCancel={false}
|
||||||
onPrimaryButtonClick={this._onUploadClick}
|
onPrimaryButtonClick={this.onUploadClick}
|
||||||
focus={true}
|
focus={true}
|
||||||
>
|
>
|
||||||
{uploadAllButton}
|
{uploadAllButton}
|
|
@ -49,6 +49,18 @@ const inPlaceOf = (elementRect) => ({
|
||||||
});
|
});
|
||||||
|
|
||||||
const validServer = withValidation({
|
const validServer = withValidation({
|
||||||
|
deriveData: async ({ value }) => {
|
||||||
|
try {
|
||||||
|
// check if we can successfully load this server's room directory
|
||||||
|
await MatrixClientPeg.get().publicRooms({
|
||||||
|
limit: 1,
|
||||||
|
server: value,
|
||||||
|
});
|
||||||
|
return {};
|
||||||
|
} catch (error) {
|
||||||
|
return { error };
|
||||||
|
}
|
||||||
|
},
|
||||||
rules: [
|
rules: [
|
||||||
{
|
{
|
||||||
key: "required",
|
key: "required",
|
||||||
|
@ -57,21 +69,11 @@ const validServer = withValidation({
|
||||||
}, {
|
}, {
|
||||||
key: "available",
|
key: "available",
|
||||||
final: true,
|
final: true,
|
||||||
test: async ({ value }) => {
|
test: async (_, { error }) => !error,
|
||||||
try {
|
|
||||||
const opts = {
|
|
||||||
limit: 1,
|
|
||||||
server: value,
|
|
||||||
};
|
|
||||||
// check if we can successfully load this server's room directory
|
|
||||||
await MatrixClientPeg.get().publicRooms(opts);
|
|
||||||
return true;
|
|
||||||
} catch (e) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
valid: () => _t("Looks good"),
|
valid: () => _t("Looks good"),
|
||||||
invalid: () => _t("Can't find this server or its room list"),
|
invalid: ({ error }) => error.errcode === "M_FORBIDDEN"
|
||||||
|
? _t("You are not allowed to view this server's rooms list")
|
||||||
|
: _t("Can't find this server or its room list"),
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
});
|
});
|
||||||
|
|
|
@ -108,8 +108,6 @@ export default class ImageView extends React.Component<IProps, IState> {
|
||||||
window.addEventListener("resize", this.calculateZoom);
|
window.addEventListener("resize", this.calculateZoom);
|
||||||
// After the image loads for the first time we want to calculate the zoom
|
// After the image loads for the first time we want to calculate the zoom
|
||||||
this.image.current.addEventListener("load", this.calculateZoom);
|
this.image.current.addEventListener("load", this.calculateZoom);
|
||||||
// Try to precalculate the zoom from width and height props
|
|
||||||
this.calculateZoom();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
componentWillUnmount() {
|
componentWillUnmount() {
|
||||||
|
@ -122,11 +120,8 @@ export default class ImageView extends React.Component<IProps, IState> {
|
||||||
const image = this.image.current;
|
const image = this.image.current;
|
||||||
const imageWrapper = this.imageWrapper.current;
|
const imageWrapper = this.imageWrapper.current;
|
||||||
|
|
||||||
const width = this.props.width || image.naturalWidth;
|
const zoomX = imageWrapper.clientWidth / image.naturalWidth;
|
||||||
const height = this.props.height || image.naturalHeight;
|
const zoomY = imageWrapper.clientHeight / image.naturalHeight;
|
||||||
|
|
||||||
const zoomX = imageWrapper.clientWidth / width;
|
|
||||||
const zoomY = imageWrapper.clientHeight / height;
|
|
||||||
|
|
||||||
// If the image is smaller in both dimensions set its the zoom to 1 to
|
// If the image is smaller in both dimensions set its the zoom to 1 to
|
||||||
// display it in its original size
|
// display it in its original size
|
||||||
|
|
|
@ -187,9 +187,15 @@ function DeviceItem({userId, device}: {userId: string, device: IDevice}) {
|
||||||
verifyDevice(cli.getUser(userId), device);
|
verifyDevice(cli.getUser(userId), device);
|
||||||
};
|
};
|
||||||
|
|
||||||
const deviceName = device.ambiguous ?
|
let deviceName;
|
||||||
(device.getDisplayName() ? device.getDisplayName() : "") + " (" + device.deviceId + ")" :
|
if (!device.getDisplayName()?.trim()) {
|
||||||
device.getDisplayName();
|
deviceName = device.deviceId;
|
||||||
|
} else {
|
||||||
|
deviceName = device.ambiguous ?
|
||||||
|
device.getDisplayName() + " (" + device.deviceId + ")" :
|
||||||
|
device.getDisplayName();
|
||||||
|
}
|
||||||
|
|
||||||
let trustedLabel = null;
|
let trustedLabel = null;
|
||||||
if (userTrust.isVerified()) trustedLabel = isVerified ? _t("Trusted") : _t("Not trusted");
|
if (userTrust.isVerified()) trustedLabel = isVerified ? _t("Trusted") : _t("Not trusted");
|
||||||
|
|
||||||
|
|
|
@ -16,7 +16,7 @@ limitations under the License.
|
||||||
*/
|
*/
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import * as sdk from '../../../index';
|
import * as sdk from '../../../index';
|
||||||
import {_t} from '../../../languageHandler';
|
import {_t, _td} from '../../../languageHandler';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import dis from '../../../dispatcher/dispatcher';
|
import dis from '../../../dispatcher/dispatcher';
|
||||||
import EditorModel from '../../../editor/model';
|
import EditorModel from '../../../editor/model';
|
||||||
|
@ -24,16 +24,18 @@ import {getCaretOffsetAndText} from '../../../editor/dom';
|
||||||
import {htmlSerializeIfNeeded, textSerialize, containsEmote, stripEmoteCommand} from '../../../editor/serialize';
|
import {htmlSerializeIfNeeded, textSerialize, containsEmote, stripEmoteCommand} from '../../../editor/serialize';
|
||||||
import {findEditableEvent} from '../../../utils/EventUtils';
|
import {findEditableEvent} from '../../../utils/EventUtils';
|
||||||
import {parseEvent} from '../../../editor/deserialize';
|
import {parseEvent} from '../../../editor/deserialize';
|
||||||
import {PartCreator} from '../../../editor/parts';
|
import {CommandPartCreator} from '../../../editor/parts';
|
||||||
import EditorStateTransfer from '../../../utils/EditorStateTransfer';
|
import EditorStateTransfer from '../../../utils/EditorStateTransfer';
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
import {EventStatus} from 'matrix-js-sdk/src/models/event';
|
import {EventStatus} from 'matrix-js-sdk/src/models/event';
|
||||||
import BasicMessageComposer from "./BasicMessageComposer";
|
import BasicMessageComposer from "./BasicMessageComposer";
|
||||||
import MatrixClientContext from "../../../contexts/MatrixClientContext";
|
import MatrixClientContext from "../../../contexts/MatrixClientContext";
|
||||||
|
import {CommandCategories, getCommand} from '../../../SlashCommands';
|
||||||
import {Action} from "../../../dispatcher/actions";
|
import {Action} from "../../../dispatcher/actions";
|
||||||
import CountlyAnalytics from "../../../CountlyAnalytics";
|
import CountlyAnalytics from "../../../CountlyAnalytics";
|
||||||
import {getKeyBindingsManager, MessageComposerAction} from '../../../KeyBindingsManager';
|
import {getKeyBindingsManager, MessageComposerAction} from '../../../KeyBindingsManager';
|
||||||
import {replaceableComponent} from "../../../utils/replaceableComponent";
|
import {replaceableComponent} from "../../../utils/replaceableComponent";
|
||||||
|
import Modal from '../../../Modal';
|
||||||
|
|
||||||
function _isReply(mxEvent) {
|
function _isReply(mxEvent) {
|
||||||
const relatesTo = mxEvent.getContent()["m.relates_to"];
|
const relatesTo = mxEvent.getContent()["m.relates_to"];
|
||||||
|
@ -178,6 +180,22 @@ export default class EditMessageComposer extends React.Component {
|
||||||
dis.fire(Action.FocusComposer);
|
dis.fire(Action.FocusComposer);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_isSlashCommand() {
|
||||||
|
const parts = this.model.parts;
|
||||||
|
const firstPart = parts[0];
|
||||||
|
if (firstPart) {
|
||||||
|
if (firstPart.type === "command" && firstPart.text.startsWith("/") && !firstPart.text.startsWith("//")) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (firstPart.text.startsWith("/") && !firstPart.text.startsWith("//")
|
||||||
|
&& (firstPart.type === "plain" || firstPart.type === "pill-candidate")) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
_isContentModified(newContent) {
|
_isContentModified(newContent) {
|
||||||
// if nothing has changed then bail
|
// if nothing has changed then bail
|
||||||
const oldContent = this.props.editState.getEvent().getContent();
|
const oldContent = this.props.editState.getEvent().getContent();
|
||||||
|
@ -190,19 +208,112 @@ export default class EditMessageComposer extends React.Component {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
_sendEdit = () => {
|
_getSlashCommand() {
|
||||||
|
const commandText = this.model.parts.reduce((text, part) => {
|
||||||
|
// use mxid to textify user pills in a command
|
||||||
|
if (part.type === "user-pill") {
|
||||||
|
return text + part.resourceId;
|
||||||
|
}
|
||||||
|
return text + part.text;
|
||||||
|
}, "");
|
||||||
|
const {cmd, args} = getCommand(commandText);
|
||||||
|
return [cmd, args, commandText];
|
||||||
|
}
|
||||||
|
|
||||||
|
async _runSlashCommand(cmd, args, roomId) {
|
||||||
|
const result = cmd.run(roomId, args);
|
||||||
|
let messageContent;
|
||||||
|
let error = result.error;
|
||||||
|
if (result.promise) {
|
||||||
|
try {
|
||||||
|
if (cmd.category === CommandCategories.messages) {
|
||||||
|
messageContent = await result.promise;
|
||||||
|
} else {
|
||||||
|
await result.promise;
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
error = err;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (error) {
|
||||||
|
console.error("Command failure: %s", error);
|
||||||
|
const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
|
||||||
|
// assume the error is a server error when the command is async
|
||||||
|
const isServerError = !!result.promise;
|
||||||
|
const title = isServerError ? _td("Server error") : _td("Command error");
|
||||||
|
|
||||||
|
let errText;
|
||||||
|
if (typeof error === 'string') {
|
||||||
|
errText = error;
|
||||||
|
} else if (error.message) {
|
||||||
|
errText = error.message;
|
||||||
|
} else {
|
||||||
|
errText = _t("Server unavailable, overloaded, or something else went wrong.");
|
||||||
|
}
|
||||||
|
|
||||||
|
Modal.createTrackedDialog(title, '', ErrorDialog, {
|
||||||
|
title: _t(title),
|
||||||
|
description: errText,
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
console.log("Command success.");
|
||||||
|
if (messageContent) return messageContent;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_sendEdit = async () => {
|
||||||
const startTime = CountlyAnalytics.getTimestamp();
|
const startTime = CountlyAnalytics.getTimestamp();
|
||||||
const editedEvent = this.props.editState.getEvent();
|
const editedEvent = this.props.editState.getEvent();
|
||||||
const editContent = createEditContent(this.model, editedEvent);
|
const editContent = createEditContent(this.model, editedEvent);
|
||||||
const newContent = editContent["m.new_content"];
|
const newContent = editContent["m.new_content"];
|
||||||
|
let shouldSend = true;
|
||||||
|
|
||||||
// If content is modified then send an updated event into the room
|
// If content is modified then send an updated event into the room
|
||||||
if (this._isContentModified(newContent)) {
|
if (this._isContentModified(newContent)) {
|
||||||
const roomId = editedEvent.getRoomId();
|
const roomId = editedEvent.getRoomId();
|
||||||
this._cancelPreviousPendingEdit();
|
if (!containsEmote(this.model) && this._isSlashCommand()) {
|
||||||
const prom = this.context.sendMessage(roomId, editContent);
|
const [cmd, args, commandText] = this._getSlashCommand();
|
||||||
dis.dispatch({action: "message_sent"});
|
if (cmd) {
|
||||||
CountlyAnalytics.instance.trackSendMessage(startTime, prom, roomId, true, false, editContent);
|
if (cmd.category === CommandCategories.messages) {
|
||||||
|
editContent["m.new_content"] = await this._runSlashCommand(cmd, args, roomId);
|
||||||
|
} else {
|
||||||
|
this._runSlashCommand(cmd, args, roomId);
|
||||||
|
shouldSend = false;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// ask the user if their unknown command should be sent as a message
|
||||||
|
const QuestionDialog = sdk.getComponent("dialogs.QuestionDialog");
|
||||||
|
const {finished} = Modal.createTrackedDialog("Unknown command", "", QuestionDialog, {
|
||||||
|
title: _t("Unknown Command"),
|
||||||
|
description: <div>
|
||||||
|
<p>
|
||||||
|
{ _t("Unrecognised command: %(commandText)s", {commandText}) }
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
{ _t("You can use <code>/help</code> to list available commands. " +
|
||||||
|
"Did you mean to send this as a message?", {}, {
|
||||||
|
code: t => <code>{ t }</code>,
|
||||||
|
}) }
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
{ _t("Hint: Begin your message with <code>//</code> to start it with a slash.", {}, {
|
||||||
|
code: t => <code>{ t }</code>,
|
||||||
|
}) }
|
||||||
|
</p>
|
||||||
|
</div>,
|
||||||
|
button: _t('Send as message'),
|
||||||
|
});
|
||||||
|
const [sendAnyway] = await finished;
|
||||||
|
// if !sendAnyway bail to let the user edit the composer and try again
|
||||||
|
if (!sendAnyway) return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (shouldSend) {
|
||||||
|
this._cancelPreviousPendingEdit();
|
||||||
|
const prom = this.context.sendMessage(roomId, editContent);
|
||||||
|
dis.dispatch({action: "message_sent"});
|
||||||
|
CountlyAnalytics.instance.trackSendMessage(startTime, prom, roomId, true, false, editContent);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// close the event editing and focus composer
|
// close the event editing and focus composer
|
||||||
|
@ -240,7 +351,7 @@ export default class EditMessageComposer extends React.Component {
|
||||||
_createEditorModel() {
|
_createEditorModel() {
|
||||||
const {editState} = this.props;
|
const {editState} = this.props;
|
||||||
const room = this._getRoom();
|
const room = this._getRoom();
|
||||||
const partCreator = new PartCreator(room, this.context);
|
const partCreator = new CommandPartCreator(room, this.context);
|
||||||
let parts;
|
let parts;
|
||||||
if (editState.hasEditorState()) {
|
if (editState.hasEditorState()) {
|
||||||
// if restoring state from a previous editor,
|
// if restoring state from a previous editor,
|
||||||
|
|
|
@ -32,17 +32,11 @@ interface IProps {
|
||||||
setTopic(topic: string): void;
|
setTopic(topic: string): void;
|
||||||
}
|
}
|
||||||
|
|
||||||
const SpaceBasicSettings = ({
|
export const SpaceAvatar = ({
|
||||||
avatarUrl,
|
avatarUrl,
|
||||||
avatarDisabled = false,
|
avatarDisabled = false,
|
||||||
setAvatar,
|
setAvatar,
|
||||||
name = "",
|
}: Pick<IProps, "avatarUrl" | "avatarDisabled" | "setAvatar">) => {
|
||||||
nameDisabled = false,
|
|
||||||
setName,
|
|
||||||
topic = "",
|
|
||||||
topicDisabled = false,
|
|
||||||
setTopic,
|
|
||||||
}: IProps) => {
|
|
||||||
const avatarUploadRef = useRef<HTMLInputElement>();
|
const avatarUploadRef = useRef<HTMLInputElement>();
|
||||||
const [avatar, setAvatarDataUrl] = useState(avatarUrl); // avatar data url cache
|
const [avatar, setAvatarDataUrl] = useState(avatarUrl); // avatar data url cache
|
||||||
|
|
||||||
|
@ -81,20 +75,34 @@ const SpaceBasicSettings = ({
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return <div className="mx_SpaceBasicSettings_avatarContainer">
|
||||||
|
{ avatarSection }
|
||||||
|
<input type="file" ref={avatarUploadRef} onChange={(e) => {
|
||||||
|
if (!e.target.files?.length) return;
|
||||||
|
const file = e.target.files[0];
|
||||||
|
setAvatar(file);
|
||||||
|
const reader = new FileReader();
|
||||||
|
reader.onload = (ev) => {
|
||||||
|
setAvatarDataUrl(ev.target.result as string);
|
||||||
|
};
|
||||||
|
reader.readAsDataURL(file);
|
||||||
|
}} accept="image/*" />
|
||||||
|
</div>;
|
||||||
|
};
|
||||||
|
|
||||||
|
const SpaceBasicSettings = ({
|
||||||
|
avatarUrl,
|
||||||
|
avatarDisabled = false,
|
||||||
|
setAvatar,
|
||||||
|
name = "",
|
||||||
|
nameDisabled = false,
|
||||||
|
setName,
|
||||||
|
topic = "",
|
||||||
|
topicDisabled = false,
|
||||||
|
setTopic,
|
||||||
|
}: IProps) => {
|
||||||
return <div className="mx_SpaceBasicSettings">
|
return <div className="mx_SpaceBasicSettings">
|
||||||
<div className="mx_SpaceBasicSettings_avatarContainer">
|
<SpaceAvatar avatarUrl={avatarUrl} avatarDisabled={avatarDisabled} setAvatar={setAvatar} />
|
||||||
{ avatarSection }
|
|
||||||
<input type="file" ref={avatarUploadRef} onChange={(e) => {
|
|
||||||
if (!e.target.files?.length) return;
|
|
||||||
const file = e.target.files[0];
|
|
||||||
setAvatar(file);
|
|
||||||
const reader = new FileReader();
|
|
||||||
reader.onload = (ev) => {
|
|
||||||
setAvatarDataUrl(ev.target.result as string);
|
|
||||||
};
|
|
||||||
reader.readAsDataURL(file);
|
|
||||||
}} accept="image/*" />
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<Field
|
<Field
|
||||||
name="spaceName"
|
name="spaceName"
|
||||||
|
|
|
@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
|
||||||
limitations under the License.
|
limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import React, {useContext, useState} from "react";
|
import React, {useContext, useRef, useState} from "react";
|
||||||
import classNames from "classnames";
|
import classNames from "classnames";
|
||||||
import {EventType, RoomType, RoomCreateTypeField} from "matrix-js-sdk/src/@types/event";
|
import {EventType, RoomType, RoomCreateTypeField} from "matrix-js-sdk/src/@types/event";
|
||||||
import FocusLock from "react-focus-lock";
|
import FocusLock from "react-focus-lock";
|
||||||
|
@ -24,12 +24,14 @@ import AccessibleTooltipButton from "../elements/AccessibleTooltipButton";
|
||||||
import {ChevronFace, ContextMenu} from "../../structures/ContextMenu";
|
import {ChevronFace, ContextMenu} from "../../structures/ContextMenu";
|
||||||
import createRoom, {IStateEvent, Preset} from "../../../createRoom";
|
import createRoom, {IStateEvent, Preset} from "../../../createRoom";
|
||||||
import MatrixClientContext from "../../../contexts/MatrixClientContext";
|
import MatrixClientContext from "../../../contexts/MatrixClientContext";
|
||||||
import SpaceBasicSettings from "./SpaceBasicSettings";
|
import {SpaceAvatar} from "./SpaceBasicSettings";
|
||||||
import AccessibleButton from "../elements/AccessibleButton";
|
import AccessibleButton from "../elements/AccessibleButton";
|
||||||
import {BetaPill} from "../beta/BetaCard";
|
import {BetaPill} from "../beta/BetaCard";
|
||||||
import defaultDispatcher from "../../../dispatcher/dispatcher";
|
import defaultDispatcher from "../../../dispatcher/dispatcher";
|
||||||
import {Action} from "../../../dispatcher/actions";
|
import {Action} from "../../../dispatcher/actions";
|
||||||
import {USER_LABS_TAB} from "../dialogs/UserSettingsDialog";
|
import {USER_LABS_TAB} from "../dialogs/UserSettingsDialog";
|
||||||
|
import Field from "../elements/Field";
|
||||||
|
import withValidation from "../elements/Validation";
|
||||||
|
|
||||||
const SpaceCreateMenuType = ({ title, description, className, onClick }) => {
|
const SpaceCreateMenuType = ({ title, description, className, onClick }) => {
|
||||||
return (
|
return (
|
||||||
|
@ -45,17 +47,39 @@ enum Visibility {
|
||||||
Private,
|
Private,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const spaceNameValidator = withValidation({
|
||||||
|
rules: [
|
||||||
|
{
|
||||||
|
key: "required",
|
||||||
|
test: async ({ value }) => !!value,
|
||||||
|
invalid: () => _t("Please enter a name for the space"),
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
||||||
|
|
||||||
const SpaceCreateMenu = ({ onFinished }) => {
|
const SpaceCreateMenu = ({ onFinished }) => {
|
||||||
const cli = useContext(MatrixClientContext);
|
const cli = useContext(MatrixClientContext);
|
||||||
const [visibility, setVisibility] = useState<Visibility>(null);
|
const [visibility, setVisibility] = useState<Visibility>(null);
|
||||||
const [name, setName] = useState("");
|
|
||||||
const [avatar, setAvatar] = useState<File>(null);
|
|
||||||
const [topic, setTopic] = useState<string>("");
|
|
||||||
const [busy, setBusy] = useState<boolean>(false);
|
const [busy, setBusy] = useState<boolean>(false);
|
||||||
|
|
||||||
const onSpaceCreateClick = async () => {
|
const [name, setName] = useState("");
|
||||||
|
const spaceNameField = useRef<Field>();
|
||||||
|
const [avatar, setAvatar] = useState<File>(null);
|
||||||
|
const [topic, setTopic] = useState<string>("");
|
||||||
|
|
||||||
|
const onSpaceCreateClick = async (e) => {
|
||||||
|
e.preventDefault();
|
||||||
if (busy) return;
|
if (busy) return;
|
||||||
|
|
||||||
setBusy(true);
|
setBusy(true);
|
||||||
|
// require & validate the space name field
|
||||||
|
if (!await spaceNameField.current.validate({ allowEmpty: false })) {
|
||||||
|
spaceNameField.current.focus();
|
||||||
|
spaceNameField.current.validate({ allowEmpty: false, focused: true });
|
||||||
|
setBusy(false);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const initialState: IStateEvent[] = [
|
const initialState: IStateEvent[] = [
|
||||||
{
|
{
|
||||||
type: EventType.RoomHistoryVisibility,
|
type: EventType.RoomHistoryVisibility,
|
||||||
|
@ -150,9 +174,30 @@ const SpaceCreateMenu = ({ onFinished }) => {
|
||||||
}
|
}
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<SpaceBasicSettings setAvatar={setAvatar} name={name} setName={setName} topic={topic} setTopic={setTopic} />
|
<form className="mx_SpaceBasicSettings" onSubmit={onSpaceCreateClick}>
|
||||||
|
<SpaceAvatar setAvatar={setAvatar} />
|
||||||
|
|
||||||
<AccessibleButton kind="primary" onClick={onSpaceCreateClick} disabled={!name || busy}>
|
<Field
|
||||||
|
name="spaceName"
|
||||||
|
label={_t("Name")}
|
||||||
|
autoFocus={true}
|
||||||
|
value={name}
|
||||||
|
onChange={ev => setName(ev.target.value)}
|
||||||
|
ref={spaceNameField}
|
||||||
|
onValidate={spaceNameValidator}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<Field
|
||||||
|
name="spaceTopic"
|
||||||
|
element="textarea"
|
||||||
|
label={_t("Description")}
|
||||||
|
value={topic}
|
||||||
|
onChange={ev => setTopic(ev.target.value)}
|
||||||
|
rows={3}
|
||||||
|
/>
|
||||||
|
</form>
|
||||||
|
|
||||||
|
<AccessibleButton kind="primary" onClick={onSpaceCreateClick} disabled={busy}>
|
||||||
{ busy ? _t("Creating...") : _t("Create") }
|
{ busy ? _t("Creating...") : _t("Create") }
|
||||||
</AccessibleButton>
|
</AccessibleButton>
|
||||||
</React.Fragment>;
|
</React.Fragment>;
|
||||||
|
|
|
@ -26,13 +26,11 @@ import {SpaceItem} from "./SpaceTreeLevel";
|
||||||
import AccessibleTooltipButton from "../elements/AccessibleTooltipButton";
|
import AccessibleTooltipButton from "../elements/AccessibleTooltipButton";
|
||||||
import {useEventEmitter} from "../../../hooks/useEventEmitter";
|
import {useEventEmitter} from "../../../hooks/useEventEmitter";
|
||||||
import SpaceStore, {
|
import SpaceStore, {
|
||||||
HOME_SPACE,
|
|
||||||
UPDATE_INVITED_SPACES,
|
UPDATE_INVITED_SPACES,
|
||||||
UPDATE_SELECTED_SPACE,
|
UPDATE_SELECTED_SPACE,
|
||||||
UPDATE_TOP_LEVEL_SPACES,
|
UPDATE_TOP_LEVEL_SPACES,
|
||||||
} from "../../../stores/SpaceStore";
|
} from "../../../stores/SpaceStore";
|
||||||
import AutoHideScrollbar from "../../structures/AutoHideScrollbar";
|
import AutoHideScrollbar from "../../structures/AutoHideScrollbar";
|
||||||
import {SpaceNotificationState} from "../../../stores/notifications/SpaceNotificationState";
|
|
||||||
import NotificationBadge from "../rooms/NotificationBadge";
|
import NotificationBadge from "../rooms/NotificationBadge";
|
||||||
import {
|
import {
|
||||||
RovingAccessibleButton,
|
RovingAccessibleButton,
|
||||||
|
@ -40,13 +38,15 @@ import {
|
||||||
RovingTabIndexProvider,
|
RovingTabIndexProvider,
|
||||||
} from "../../../accessibility/RovingTabIndex";
|
} from "../../../accessibility/RovingTabIndex";
|
||||||
import {Key} from "../../../Keyboard";
|
import {Key} from "../../../Keyboard";
|
||||||
|
import {RoomNotificationStateStore} from "../../../stores/notifications/RoomNotificationStateStore";
|
||||||
|
import {NotificationState} from "../../../stores/notifications/NotificationState";
|
||||||
|
|
||||||
interface IButtonProps {
|
interface IButtonProps {
|
||||||
space?: Room;
|
space?: Room;
|
||||||
className?: string;
|
className?: string;
|
||||||
selected?: boolean;
|
selected?: boolean;
|
||||||
tooltip?: string;
|
tooltip?: string;
|
||||||
notificationState?: SpaceNotificationState;
|
notificationState?: NotificationState;
|
||||||
isNarrow?: boolean;
|
isNarrow?: boolean;
|
||||||
onClick(): void;
|
onClick(): void;
|
||||||
}
|
}
|
||||||
|
@ -212,8 +212,8 @@ const SpacePanel = () => {
|
||||||
className="mx_SpaceButton_home"
|
className="mx_SpaceButton_home"
|
||||||
onClick={() => SpaceStore.instance.setActiveSpace(null)}
|
onClick={() => SpaceStore.instance.setActiveSpace(null)}
|
||||||
selected={!activeSpace}
|
selected={!activeSpace}
|
||||||
tooltip={_t("Home")}
|
tooltip={_t("All rooms")}
|
||||||
notificationState={SpaceStore.instance.getNotificationState(HOME_SPACE)}
|
notificationState={RoomNotificationStateStore.instance.globalState}
|
||||||
isNarrow={isPanelCollapsed}
|
isNarrow={isPanelCollapsed}
|
||||||
/>
|
/>
|
||||||
{ invites.map(s => <SpaceItem
|
{ invites.map(s => <SpaceItem
|
||||||
|
|
|
@ -116,14 +116,22 @@ export function htmlSerializeIfNeeded(model: EditorModel, {forceHTML = false} =
|
||||||
const parser = new Markdown(md);
|
const parser = new Markdown(md);
|
||||||
if (!parser.isPlainText() || forceHTML) {
|
if (!parser.isPlainText() || forceHTML) {
|
||||||
// feed Markdown output to HTML parser
|
// feed Markdown output to HTML parser
|
||||||
const phtml = cheerio.load(parser.toHTML(),
|
const phtml = cheerio.load(parser.toHTML(), {
|
||||||
{ _useHtmlParser2: true, decodeEntities: false });
|
// @ts-ignore: The `_useHtmlParser2` internal option is the
|
||||||
|
// simplest way to both parse and render using `htmlparser2`.
|
||||||
|
_useHtmlParser2: true,
|
||||||
|
decodeEntities: false,
|
||||||
|
});
|
||||||
|
|
||||||
if (SettingsStore.getValue("feature_latex_maths")) {
|
if (SettingsStore.getValue("feature_latex_maths")) {
|
||||||
// original Markdown without LaTeX replacements
|
// original Markdown without LaTeX replacements
|
||||||
const parserOrig = new Markdown(orig);
|
const parserOrig = new Markdown(orig);
|
||||||
const phtmlOrig = cheerio.load(parserOrig.toHTML(),
|
const phtmlOrig = cheerio.load(parserOrig.toHTML(), {
|
||||||
{ _useHtmlParser2: true, decodeEntities: false });
|
// @ts-ignore: The `_useHtmlParser2` internal option is the
|
||||||
|
// simplest way to both parse and render using `htmlparser2`.
|
||||||
|
_useHtmlParser2: true,
|
||||||
|
decodeEntities: false,
|
||||||
|
});
|
||||||
|
|
||||||
// since maths delimiters are handled before Markdown,
|
// since maths delimiters are handled before Markdown,
|
||||||
// code blocks could contain mangled content.
|
// code blocks could contain mangled content.
|
||||||
|
|
|
@ -1003,6 +1003,7 @@
|
||||||
"Upload": "Upload",
|
"Upload": "Upload",
|
||||||
"Name": "Name",
|
"Name": "Name",
|
||||||
"Description": "Description",
|
"Description": "Description",
|
||||||
|
"Please enter a name for the space": "Please enter a name for the space",
|
||||||
"Create a space": "Create a space",
|
"Create a space": "Create a space",
|
||||||
"Spaces are a new way to group rooms and people. To join an existing space you'll need an invite.": "Spaces are a new way to group rooms and people. To join an existing space you'll need an invite.",
|
"Spaces are a new way to group rooms and people. To join an existing space you'll need an invite.": "Spaces are a new way to group rooms and people. To join an existing space you'll need an invite.",
|
||||||
"Public": "Public",
|
"Public": "Public",
|
||||||
|
@ -1019,7 +1020,7 @@
|
||||||
"Create": "Create",
|
"Create": "Create",
|
||||||
"Expand space panel": "Expand space panel",
|
"Expand space panel": "Expand space panel",
|
||||||
"Collapse space panel": "Collapse space panel",
|
"Collapse space panel": "Collapse space panel",
|
||||||
"Home": "Home",
|
"All rooms": "All rooms",
|
||||||
"Click to copy": "Click to copy",
|
"Click to copy": "Click to copy",
|
||||||
"Copied!": "Copied!",
|
"Copied!": "Copied!",
|
||||||
"Failed to copy": "Failed to copy",
|
"Failed to copy": "Failed to copy",
|
||||||
|
@ -1448,6 +1449,13 @@
|
||||||
"Someone is using an unknown session": "Someone is using an unknown session",
|
"Someone is using an unknown session": "Someone is using an unknown session",
|
||||||
"This room is end-to-end encrypted": "This room is end-to-end encrypted",
|
"This room is end-to-end encrypted": "This room is end-to-end encrypted",
|
||||||
"Everyone in this room is verified": "Everyone in this room is verified",
|
"Everyone in this room is verified": "Everyone in this room is verified",
|
||||||
|
"Server error": "Server error",
|
||||||
|
"Server unavailable, overloaded, or something else went wrong.": "Server unavailable, overloaded, or something else went wrong.",
|
||||||
|
"Unknown Command": "Unknown Command",
|
||||||
|
"Unrecognised command: %(commandText)s": "Unrecognised command: %(commandText)s",
|
||||||
|
"You can use <code>/help</code> to list available commands. Did you mean to send this as a message?": "You can use <code>/help</code> to list available commands. Did you mean to send this as a message?",
|
||||||
|
"Hint: Begin your message with <code>//</code> to start it with a slash.": "Hint: Begin your message with <code>//</code> to start it with a slash.",
|
||||||
|
"Send as message": "Send as message",
|
||||||
"Edit message": "Edit message",
|
"Edit message": "Edit message",
|
||||||
"Mod": "Mod",
|
"Mod": "Mod",
|
||||||
"This event could not be displayed": "This event could not be displayed",
|
"This event could not be displayed": "This event could not be displayed",
|
||||||
|
@ -1638,13 +1646,6 @@
|
||||||
"This Room": "This Room",
|
"This Room": "This Room",
|
||||||
"All Rooms": "All Rooms",
|
"All Rooms": "All Rooms",
|
||||||
"Search…": "Search…",
|
"Search…": "Search…",
|
||||||
"Server error": "Server error",
|
|
||||||
"Server unavailable, overloaded, or something else went wrong.": "Server unavailable, overloaded, or something else went wrong.",
|
|
||||||
"Unknown Command": "Unknown Command",
|
|
||||||
"Unrecognised command: %(commandText)s": "Unrecognised command: %(commandText)s",
|
|
||||||
"You can use <code>/help</code> to list available commands. Did you mean to send this as a message?": "You can use <code>/help</code> to list available commands. Did you mean to send this as a message?",
|
|
||||||
"Hint: Begin your message with <code>//</code> to start it with a slash.": "Hint: Begin your message with <code>//</code> to start it with a slash.",
|
|
||||||
"Send as message": "Send as message",
|
|
||||||
"Failed to connect to integration manager": "Failed to connect to integration manager",
|
"Failed to connect to integration manager": "Failed to connect to integration manager",
|
||||||
"You don't currently have any stickerpacks enabled": "You don't currently have any stickerpacks enabled",
|
"You don't currently have any stickerpacks enabled": "You don't currently have any stickerpacks enabled",
|
||||||
"Add some now": "Add some now",
|
"Add some now": "Add some now",
|
||||||
|
@ -2023,10 +2024,11 @@
|
||||||
"Continue with %(provider)s": "Continue with %(provider)s",
|
"Continue with %(provider)s": "Continue with %(provider)s",
|
||||||
"Sign in with single sign-on": "Sign in with single sign-on",
|
"Sign in with single sign-on": "Sign in with single sign-on",
|
||||||
"And %(count)s more...|other": "And %(count)s more...",
|
"And %(count)s more...|other": "And %(count)s more...",
|
||||||
|
"Home": "Home",
|
||||||
"Enter a server name": "Enter a server name",
|
"Enter a server name": "Enter a server name",
|
||||||
"Looks good": "Looks good",
|
"Looks good": "Looks good",
|
||||||
|
"You are not allowed to view this server's rooms list": "You are not allowed to view this server's rooms list",
|
||||||
"Can't find this server or its room list": "Can't find this server or its room list",
|
"Can't find this server or its room list": "Can't find this server or its room list",
|
||||||
"All rooms": "All rooms",
|
|
||||||
"Your server": "Your server",
|
"Your server": "Your server",
|
||||||
"Are you sure you want to remove <b>%(serverName)s</b>": "Are you sure you want to remove <b>%(serverName)s</b>",
|
"Are you sure you want to remove <b>%(serverName)s</b>": "Are you sure you want to remove <b>%(serverName)s</b>",
|
||||||
"Remove server": "Remove server",
|
"Remove server": "Remove server",
|
||||||
|
|
|
@ -31,28 +31,23 @@ import {RoomNotificationStateStore} from "./notifications/RoomNotificationStateS
|
||||||
import {DefaultTagID} from "./room-list/models";
|
import {DefaultTagID} from "./room-list/models";
|
||||||
import {EnhancedMap, mapDiff} from "../utils/maps";
|
import {EnhancedMap, mapDiff} from "../utils/maps";
|
||||||
import {setHasDiff} from "../utils/sets";
|
import {setHasDiff} from "../utils/sets";
|
||||||
import {objectDiff} from "../utils/objects";
|
|
||||||
import {arrayHasDiff} from "../utils/arrays";
|
|
||||||
import {ISpaceSummaryEvent, ISpaceSummaryRoom} from "../components/structures/SpaceRoomDirectory";
|
import {ISpaceSummaryEvent, ISpaceSummaryRoom} from "../components/structures/SpaceRoomDirectory";
|
||||||
import RoomViewStore from "./RoomViewStore";
|
import RoomViewStore from "./RoomViewStore";
|
||||||
|
|
||||||
type SpaceKey = string | symbol;
|
|
||||||
|
|
||||||
interface IState {}
|
interface IState {}
|
||||||
|
|
||||||
const ACTIVE_SPACE_LS_KEY = "mx_active_space";
|
const ACTIVE_SPACE_LS_KEY = "mx_active_space";
|
||||||
|
|
||||||
export const HOME_SPACE = Symbol("home-space");
|
|
||||||
export const SUGGESTED_ROOMS = Symbol("suggested-rooms");
|
export const SUGGESTED_ROOMS = Symbol("suggested-rooms");
|
||||||
|
|
||||||
export const UPDATE_TOP_LEVEL_SPACES = Symbol("top-level-spaces");
|
export const UPDATE_TOP_LEVEL_SPACES = Symbol("top-level-spaces");
|
||||||
export const UPDATE_INVITED_SPACES = Symbol("invited-spaces");
|
export const UPDATE_INVITED_SPACES = Symbol("invited-spaces");
|
||||||
export const UPDATE_SELECTED_SPACE = Symbol("selected-space");
|
export const UPDATE_SELECTED_SPACE = Symbol("selected-space");
|
||||||
// Space Room ID/HOME_SPACE will be emitted when a Space's children change
|
// Space Room ID will be emitted when a Space's children change
|
||||||
|
|
||||||
const MAX_SUGGESTED_ROOMS = 20;
|
const MAX_SUGGESTED_ROOMS = 20;
|
||||||
|
|
||||||
const getSpaceContextKey = (space?: Room) => `mx_space_context_${space?.roomId || "home_space"}`;
|
const getSpaceContextKey = (space?: Room) => `mx_space_context_${space?.roomId || "ALL_ROOMS"}`;
|
||||||
|
|
||||||
const partitionSpacesAndRooms = (arr: Room[]): [Room[], Room[]] => { // [spaces, rooms]
|
const partitionSpacesAndRooms = (arr: Room[]): [Room[], Room[]] => { // [spaces, rooms]
|
||||||
return arr.reduce((result, room: Room) => {
|
return arr.reduce((result, room: Room) => {
|
||||||
|
@ -86,15 +81,13 @@ export class SpaceStoreClass extends AsyncStoreWithClient<IState> {
|
||||||
|
|
||||||
// The spaces representing the roots of the various tree-like hierarchies
|
// The spaces representing the roots of the various tree-like hierarchies
|
||||||
private rootSpaces: Room[] = [];
|
private rootSpaces: Room[] = [];
|
||||||
// The list of rooms not present in any currently joined spaces
|
|
||||||
private orphanedRooms = new Set<string>();
|
|
||||||
// Map from room ID to set of spaces which list it as a child
|
// Map from room ID to set of spaces which list it as a child
|
||||||
private parentMap = new EnhancedMap<string, Set<string>>();
|
private parentMap = new EnhancedMap<string, Set<string>>();
|
||||||
// Map from space key to SpaceNotificationState instance representing that space
|
// Map from spaceId to SpaceNotificationState instance representing that space
|
||||||
private notificationStateMap = new Map<SpaceKey, SpaceNotificationState>();
|
private notificationStateMap = new Map<string, SpaceNotificationState>();
|
||||||
// Map from space key to Set of room IDs that should be shown as part of that space's filter
|
// Map from space key to Set of room IDs that should be shown as part of that space's filter
|
||||||
private spaceFilteredRooms = new Map<string | symbol, Set<string>>();
|
private spaceFilteredRooms = new Map<string, Set<string>>();
|
||||||
// The space currently selected in the Space Panel - if null then `Home` is selected
|
// The space currently selected in the Space Panel - if null then All Rooms is selected
|
||||||
private _activeSpace?: Room = null;
|
private _activeSpace?: Room = null;
|
||||||
private _suggestedRooms: ISpaceSummaryRoom[] = [];
|
private _suggestedRooms: ISpaceSummaryRoom[] = [];
|
||||||
private _invitedSpaces = new Set<Room>();
|
private _invitedSpaces = new Set<Room>();
|
||||||
|
@ -244,7 +237,10 @@ export class SpaceStoreClass extends AsyncStoreWithClient<IState> {
|
||||||
}
|
}
|
||||||
|
|
||||||
public getSpaceFilteredRoomIds = (space: Room | null): Set<string> => {
|
public getSpaceFilteredRoomIds = (space: Room | null): Set<string> => {
|
||||||
return this.spaceFilteredRooms.get(space?.roomId || HOME_SPACE) || new Set();
|
if (!space) {
|
||||||
|
return new Set(this.matrixClient.getVisibleRooms().map(r => r.roomId));
|
||||||
|
}
|
||||||
|
return this.spaceFilteredRooms.get(space.roomId) || new Set();
|
||||||
};
|
};
|
||||||
|
|
||||||
private rebuild = throttle(() => {
|
private rebuild = throttle(() => {
|
||||||
|
@ -275,7 +271,7 @@ export class SpaceStoreClass extends AsyncStoreWithClient<IState> {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
const [rootSpaces, orphanedRooms] = partitionSpacesAndRooms(Array.from(unseenChildren));
|
const [rootSpaces] = partitionSpacesAndRooms(Array.from(unseenChildren));
|
||||||
|
|
||||||
// somewhat algorithm to handle full-cycles
|
// somewhat algorithm to handle full-cycles
|
||||||
const detachedNodes = new Set<Room>(spaces);
|
const detachedNodes = new Set<Room>(spaces);
|
||||||
|
@ -316,7 +312,6 @@ export class SpaceStoreClass extends AsyncStoreWithClient<IState> {
|
||||||
// rootSpaces.push(space);
|
// rootSpaces.push(space);
|
||||||
// });
|
// });
|
||||||
|
|
||||||
this.orphanedRooms = new Set(orphanedRooms);
|
|
||||||
this.rootSpaces = rootSpaces;
|
this.rootSpaces = rootSpaces;
|
||||||
this.parentMap = backrefs;
|
this.parentMap = backrefs;
|
||||||
|
|
||||||
|
@ -337,25 +332,6 @@ export class SpaceStoreClass extends AsyncStoreWithClient<IState> {
|
||||||
this.rebuild();
|
this.rebuild();
|
||||||
}
|
}
|
||||||
|
|
||||||
private showInHomeSpace = (room: Room) => {
|
|
||||||
if (room.isSpaceRoom()) return false;
|
|
||||||
return !this.parentMap.get(room.roomId)?.size // put all orphaned rooms in the Home Space
|
|
||||||
|| DMRoomMap.shared().getUserIdForRoomId(room.roomId) // put all DMs in the Home Space
|
|
||||||
|| RoomListStore.instance.getTagsForRoom(room).includes(DefaultTagID.Favourite) // show all favourites
|
|
||||||
};
|
|
||||||
|
|
||||||
// Update a given room due to its tag changing (e.g DM-ness or Fav-ness)
|
|
||||||
// This can only change whether it shows up in the HOME_SPACE or not
|
|
||||||
private onRoomUpdate = (room: Room) => {
|
|
||||||
if (this.showInHomeSpace(room)) {
|
|
||||||
this.spaceFilteredRooms.get(HOME_SPACE)?.add(room.roomId);
|
|
||||||
this.emit(HOME_SPACE);
|
|
||||||
} else if (!this.orphanedRooms.has(room.roomId)) {
|
|
||||||
this.spaceFilteredRooms.get(HOME_SPACE)?.delete(room.roomId);
|
|
||||||
this.emit(HOME_SPACE);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
private onSpaceMembersChange = (ev: MatrixEvent) => {
|
private onSpaceMembersChange = (ev: MatrixEvent) => {
|
||||||
// skip this update if we do not have a DM with this user
|
// skip this update if we do not have a DM with this user
|
||||||
if (DMRoomMap.shared().getDMRoomsForUserId(ev.getStateKey()).length < 1) return;
|
if (DMRoomMap.shared().getDMRoomsForUserId(ev.getStateKey()).length < 1) return;
|
||||||
|
@ -369,16 +345,6 @@ export class SpaceStoreClass extends AsyncStoreWithClient<IState> {
|
||||||
const oldFilteredRooms = this.spaceFilteredRooms;
|
const oldFilteredRooms = this.spaceFilteredRooms;
|
||||||
this.spaceFilteredRooms = new Map();
|
this.spaceFilteredRooms = new Map();
|
||||||
|
|
||||||
// put all room invites in the Home Space
|
|
||||||
const invites = visibleRooms.filter(r => !r.isSpaceRoom() && r.getMyMembership() === "invite");
|
|
||||||
this.spaceFilteredRooms.set(HOME_SPACE, new Set<string>(invites.map(room => room.roomId)));
|
|
||||||
|
|
||||||
visibleRooms.forEach(room => {
|
|
||||||
if (this.showInHomeSpace(room)) {
|
|
||||||
this.spaceFilteredRooms.get(HOME_SPACE).add(room.roomId);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
this.rootSpaces.forEach(s => {
|
this.rootSpaces.forEach(s => {
|
||||||
// traverse each space tree in DFS to build up the supersets as you go up,
|
// traverse each space tree in DFS to build up the supersets as you go up,
|
||||||
// reusing results from like subtrees.
|
// reusing results from like subtrees.
|
||||||
|
@ -425,13 +391,8 @@ export class SpaceStoreClass extends AsyncStoreWithClient<IState> {
|
||||||
// Update NotificationStates
|
// Update NotificationStates
|
||||||
this.getNotificationState(s)?.setRooms(visibleRooms.filter(room => {
|
this.getNotificationState(s)?.setRooms(visibleRooms.filter(room => {
|
||||||
if (roomIds.has(room.roomId)) {
|
if (roomIds.has(room.roomId)) {
|
||||||
// Don't aggregate notifications for DMs except in the Home Space
|
return !DMRoomMap.shared().getUserIdForRoomId(room.roomId)
|
||||||
if (s !== HOME_SPACE) {
|
|| RoomListStore.instance.getTagsForRoom(room).includes(DefaultTagID.Favourite);
|
||||||
return !DMRoomMap.shared().getUserIdForRoomId(room.roomId)
|
|
||||||
|| RoomListStore.instance.getTagsForRoom(room).includes(DefaultTagID.Favourite);
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
|
@ -513,8 +474,6 @@ export class SpaceStoreClass extends AsyncStoreWithClient<IState> {
|
||||||
// TODO confirm this after implementing parenting behaviour
|
// TODO confirm this after implementing parenting behaviour
|
||||||
if (room.isSpaceRoom()) {
|
if (room.isSpaceRoom()) {
|
||||||
this.onSpaceUpdate();
|
this.onSpaceUpdate();
|
||||||
} else {
|
|
||||||
this.onRoomUpdate(room);
|
|
||||||
}
|
}
|
||||||
this.emit(room.roomId);
|
this.emit(room.roomId);
|
||||||
break;
|
break;
|
||||||
|
@ -527,38 +486,8 @@ export class SpaceStoreClass extends AsyncStoreWithClient<IState> {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
private onRoomAccountData = (ev: MatrixEvent, room: Room, lastEvent?: MatrixEvent) => {
|
|
||||||
if (ev.getType() === EventType.Tag && !room.isSpaceRoom()) {
|
|
||||||
// If the room was in favourites and now isn't or the opposite then update its position in the trees
|
|
||||||
const oldTags = lastEvent?.getContent()?.tags || {};
|
|
||||||
const newTags = ev.getContent()?.tags || {};
|
|
||||||
if (!!oldTags[DefaultTagID.Favourite] !== !!newTags[DefaultTagID.Favourite]) {
|
|
||||||
this.onRoomUpdate(room);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private onAccountData = (ev: MatrixEvent, lastEvent: MatrixEvent) => {
|
|
||||||
if (ev.getType() === EventType.Direct) {
|
|
||||||
const lastContent = lastEvent.getContent();
|
|
||||||
const content = ev.getContent();
|
|
||||||
|
|
||||||
const diff = objectDiff<Record<string, string[]>>(lastContent, content);
|
|
||||||
// filter out keys which changed by reference only by checking whether the sets differ
|
|
||||||
const changed = diff.changed.filter(k => arrayHasDiff(lastContent[k], content[k]));
|
|
||||||
// DM tag changes, refresh relevant rooms
|
|
||||||
new Set([...diff.added, ...diff.removed, ...changed]).forEach(roomId => {
|
|
||||||
const room = this.matrixClient?.getRoom(roomId);
|
|
||||||
if (room) {
|
|
||||||
this.onRoomUpdate(room);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
protected async reset() {
|
protected async reset() {
|
||||||
this.rootSpaces = [];
|
this.rootSpaces = [];
|
||||||
this.orphanedRooms = new Set();
|
|
||||||
this.parentMap = new EnhancedMap();
|
this.parentMap = new EnhancedMap();
|
||||||
this.notificationStateMap = new Map();
|
this.notificationStateMap = new Map();
|
||||||
this.spaceFilteredRooms = new Map();
|
this.spaceFilteredRooms = new Map();
|
||||||
|
@ -573,8 +502,6 @@ export class SpaceStoreClass extends AsyncStoreWithClient<IState> {
|
||||||
this.matrixClient.removeListener("Room", this.onRoom);
|
this.matrixClient.removeListener("Room", this.onRoom);
|
||||||
this.matrixClient.removeListener("Room.myMembership", this.onRoom);
|
this.matrixClient.removeListener("Room.myMembership", this.onRoom);
|
||||||
this.matrixClient.removeListener("RoomState.events", this.onRoomState);
|
this.matrixClient.removeListener("RoomState.events", this.onRoomState);
|
||||||
this.matrixClient.removeListener("Room.accountData", this.onRoomAccountData);
|
|
||||||
this.matrixClient.removeListener("accountData", this.onAccountData);
|
|
||||||
}
|
}
|
||||||
await this.reset();
|
await this.reset();
|
||||||
}
|
}
|
||||||
|
@ -584,8 +511,6 @@ export class SpaceStoreClass extends AsyncStoreWithClient<IState> {
|
||||||
this.matrixClient.on("Room", this.onRoom);
|
this.matrixClient.on("Room", this.onRoom);
|
||||||
this.matrixClient.on("Room.myMembership", this.onRoom);
|
this.matrixClient.on("Room.myMembership", this.onRoom);
|
||||||
this.matrixClient.on("RoomState.events", this.onRoomState);
|
this.matrixClient.on("RoomState.events", this.onRoomState);
|
||||||
this.matrixClient.on("Room.accountData", this.onRoomAccountData);
|
|
||||||
this.matrixClient.on("accountData", this.onAccountData);
|
|
||||||
|
|
||||||
await this.onSpaceUpdate(); // trigger an initial update
|
await this.onSpaceUpdate(); // trigger an initial update
|
||||||
|
|
||||||
|
@ -610,7 +535,7 @@ export class SpaceStoreClass extends AsyncStoreWithClient<IState> {
|
||||||
// Don't context switch when navigating to the space room
|
// Don't context switch when navigating to the space room
|
||||||
// as it will cause you to end up in the wrong room
|
// as it will cause you to end up in the wrong room
|
||||||
this.setActiveSpace(room, false);
|
this.setActiveSpace(room, false);
|
||||||
} else if (!this.getSpaceFilteredRoomIds(this.activeSpace).has(roomId)) {
|
} else if (this.activeSpace && !this.getSpaceFilteredRoomIds(this.activeSpace).has(roomId)) {
|
||||||
this.switchToRelatedSpace(roomId);
|
this.switchToRelatedSpace(roomId);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -628,7 +553,7 @@ export class SpaceStoreClass extends AsyncStoreWithClient<IState> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public getNotificationState(key: SpaceKey): SpaceNotificationState {
|
public getNotificationState(key: string): SpaceNotificationState {
|
||||||
if (this.notificationStateMap.has(key)) {
|
if (this.notificationStateMap.has(key)) {
|
||||||
return this.notificationStateMap.get(key);
|
return this.notificationStateMap.get(key);
|
||||||
}
|
}
|
||||||
|
|
|
@ -680,7 +680,7 @@ export class RoomListStoreClass extends AsyncStoreWithClient<IState> {
|
||||||
promise = this.recalculatePrefiltering();
|
promise = this.recalculatePrefiltering();
|
||||||
} else {
|
} else {
|
||||||
this.filterConditions.push(filter);
|
this.filterConditions.push(filter);
|
||||||
// Runtime filters with spaces disable prefiltering for the search all spaces effect
|
// Runtime filters with spaces disable prefiltering for the search all spaces feature
|
||||||
if (SettingsStore.getValue("feature_spaces")) {
|
if (SettingsStore.getValue("feature_spaces")) {
|
||||||
// this has to be awaited so that `setKnownRooms` is called in time for the `addFilterCondition` below
|
// this has to be awaited so that `setKnownRooms` is called in time for the `addFilterCondition` below
|
||||||
// this way the runtime filters are only evaluated on one dataset and not both.
|
// this way the runtime filters are only evaluated on one dataset and not both.
|
||||||
|
@ -712,10 +712,10 @@ export class RoomListStoreClass extends AsyncStoreWithClient<IState> {
|
||||||
|
|
||||||
if (this.algorithm) {
|
if (this.algorithm) {
|
||||||
this.algorithm.removeFilterCondition(filter);
|
this.algorithm.removeFilterCondition(filter);
|
||||||
// Runtime filters with spaces disable prefiltering for the search all spaces effect
|
}
|
||||||
if (SettingsStore.getValue("feature_spaces")) {
|
// Runtime filters with spaces disable prefiltering for the search all spaces feature
|
||||||
promise = this.recalculatePrefiltering();
|
if (SettingsStore.getValue("feature_spaces")) {
|
||||||
}
|
promise = this.recalculatePrefiltering();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
idx = this.prefilterConditions.indexOf(filter);
|
idx = this.prefilterConditions.indexOf(filter);
|
||||||
|
|
|
@ -24,26 +24,34 @@ import SpaceStore, { UPDATE_SELECTED_SPACE } from "../SpaceStore";
|
||||||
* Watches for changes in spaces to manage the filter on the provided RoomListStore
|
* Watches for changes in spaces to manage the filter on the provided RoomListStore
|
||||||
*/
|
*/
|
||||||
export class SpaceWatcher {
|
export class SpaceWatcher {
|
||||||
private filter = new SpaceFilterCondition();
|
private filter: SpaceFilterCondition;
|
||||||
private activeSpace: Room = SpaceStore.instance.activeSpace;
|
private activeSpace: Room = SpaceStore.instance.activeSpace;
|
||||||
|
|
||||||
constructor(private store: RoomListStoreClass) {
|
constructor(private store: RoomListStoreClass) {
|
||||||
this.updateFilter(); // get the filter into a consistent state
|
|
||||||
store.addFilter(this.filter);
|
|
||||||
SpaceStore.instance.on(UPDATE_SELECTED_SPACE, this.onSelectedSpaceUpdated);
|
SpaceStore.instance.on(UPDATE_SELECTED_SPACE, this.onSelectedSpaceUpdated);
|
||||||
}
|
}
|
||||||
|
|
||||||
private onSelectedSpaceUpdated = (activeSpace: Room) => {
|
private onSelectedSpaceUpdated = (activeSpace?: Room) => {
|
||||||
this.activeSpace = activeSpace;
|
this.activeSpace = activeSpace;
|
||||||
this.updateFilter();
|
|
||||||
|
if (this.filter) {
|
||||||
|
if (activeSpace) {
|
||||||
|
this.updateFilter();
|
||||||
|
} else {
|
||||||
|
this.store.removeFilter(this.filter);
|
||||||
|
this.filter = null;
|
||||||
|
}
|
||||||
|
} else if (activeSpace) {
|
||||||
|
this.filter = new SpaceFilterCondition();
|
||||||
|
this.updateFilter();
|
||||||
|
this.store.addFilter(this.filter);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
private updateFilter = () => {
|
private updateFilter = () => {
|
||||||
if (this.activeSpace) {
|
SpaceStore.instance.traverseSpace(this.activeSpace.roomId, roomId => {
|
||||||
SpaceStore.instance.traverseSpace(this.activeSpace.roomId, roomId => {
|
this.store.matrixClient?.getRoom(roomId)?.loadMembersIfNeeded();
|
||||||
this.store.matrixClient?.getRoom(roomId)?.loadMembersIfNeeded();
|
});
|
||||||
});
|
|
||||||
}
|
|
||||||
this.filter.updateSpace(this.activeSpace);
|
this.filter.updateSpace(this.activeSpace);
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
@ -19,7 +19,7 @@ import { Room } from "matrix-js-sdk/src/models/room";
|
||||||
|
|
||||||
import { FILTER_CHANGED, FilterKind, IFilterCondition } from "./IFilterCondition";
|
import { FILTER_CHANGED, FilterKind, IFilterCondition } from "./IFilterCondition";
|
||||||
import { IDestroyable } from "../../../utils/IDestroyable";
|
import { IDestroyable } from "../../../utils/IDestroyable";
|
||||||
import SpaceStore, {HOME_SPACE} from "../../SpaceStore";
|
import SpaceStore from "../../SpaceStore";
|
||||||
import { setHasDiff } from "../../../utils/sets";
|
import { setHasDiff } from "../../../utils/sets";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -55,10 +55,12 @@ export class SpaceFilterCondition extends EventEmitter implements IFilterConditi
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
private getSpaceEventKey = (space: Room | null) => space ? space.roomId : HOME_SPACE;
|
private getSpaceEventKey = (space: Room) => space.roomId;
|
||||||
|
|
||||||
public updateSpace(space: Room) {
|
public updateSpace(space: Room) {
|
||||||
SpaceStore.instance.off(this.getSpaceEventKey(this.space), this.onStoreUpdate);
|
if (this.space) {
|
||||||
|
SpaceStore.instance.off(this.getSpaceEventKey(this.space), this.onStoreUpdate);
|
||||||
|
}
|
||||||
SpaceStore.instance.on(this.getSpaceEventKey(this.space = space), this.onStoreUpdate);
|
SpaceStore.instance.on(this.getSpaceEventKey(this.space = space), this.onStoreUpdate);
|
||||||
this.onStoreUpdate(); // initial update from the change to the space
|
this.onStoreUpdate(); // initial update from the change to the space
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,63 +17,8 @@ limitations under the License.
|
||||||
// Pull in the encryption lib so that we can decrypt attachments.
|
// Pull in the encryption lib so that we can decrypt attachments.
|
||||||
import encrypt from 'browser-encrypt-attachment';
|
import encrypt from 'browser-encrypt-attachment';
|
||||||
import {mediaFromContent} from "../customisations/Media";
|
import {mediaFromContent} from "../customisations/Media";
|
||||||
import {IEncryptedFile} from "../customisations/models/IMediaEventContent";
|
import { IEncryptedFile } from "../customisations/models/IMediaEventContent";
|
||||||
|
import { getBlobSafeMimeType } from "./blobs";
|
||||||
// WARNING: We have to be very careful about what mime-types we allow into blobs,
|
|
||||||
// as for performance reasons these are now rendered via URL.createObjectURL()
|
|
||||||
// rather than by converting into data: URIs.
|
|
||||||
//
|
|
||||||
// This means that the content is rendered using the origin of the script which
|
|
||||||
// called createObjectURL(), and so if the content contains any scripting then it
|
|
||||||
// will pose a XSS vulnerability when the browser renders it. This is particularly
|
|
||||||
// bad if the user right-clicks the URI and pastes it into a new window or tab,
|
|
||||||
// as the blob will then execute with access to Element's full JS environment(!)
|
|
||||||
//
|
|
||||||
// See https://github.com/matrix-org/matrix-react-sdk/pull/1820#issuecomment-385210647
|
|
||||||
// for details.
|
|
||||||
//
|
|
||||||
// We mitigate this by only allowing mime-types into blobs which we know don't
|
|
||||||
// contain any scripting, and instantiate all others as application/octet-stream
|
|
||||||
// regardless of what mime-type the event claimed. Even if the payload itself
|
|
||||||
// is some malicious HTML, the fact we instantiate it with a media mimetype or
|
|
||||||
// application/octet-stream means the browser doesn't try to render it as such.
|
|
||||||
//
|
|
||||||
// One interesting edge case is image/svg+xml, which empirically *is* rendered
|
|
||||||
// correctly if the blob is set to the src attribute of an img tag (for thumbnails)
|
|
||||||
// *even if the mimetype is application/octet-stream*. However, empirically JS
|
|
||||||
// in the SVG isn't executed in this scenario, so we seem to be okay.
|
|
||||||
//
|
|
||||||
// Tested on Chrome 65 and Firefox 60
|
|
||||||
//
|
|
||||||
// The list below is taken mainly from
|
|
||||||
// https://developer.mozilla.org/en-US/docs/Web/HTML/Supported_media_formats
|
|
||||||
// N.B. Matrix doesn't currently specify which mimetypes are valid in given
|
|
||||||
// events, so we pick the ones which HTML5 browsers should be able to display
|
|
||||||
//
|
|
||||||
// For the record, mime-types which must NEVER enter this list below include:
|
|
||||||
// text/html, text/xhtml, image/svg, image/svg+xml, image/pdf, and similar.
|
|
||||||
|
|
||||||
const ALLOWED_BLOB_MIMETYPES = [
|
|
||||||
'image/jpeg',
|
|
||||||
'image/gif',
|
|
||||||
'image/png',
|
|
||||||
|
|
||||||
'video/mp4',
|
|
||||||
'video/webm',
|
|
||||||
'video/ogg',
|
|
||||||
|
|
||||||
'audio/mp4',
|
|
||||||
'audio/webm',
|
|
||||||
'audio/aac',
|
|
||||||
'audio/mpeg',
|
|
||||||
'audio/ogg',
|
|
||||||
'audio/wave',
|
|
||||||
'audio/wav',
|
|
||||||
'audio/x-wav',
|
|
||||||
'audio/x-pn-wav',
|
|
||||||
'audio/flac',
|
|
||||||
'audio/x-flac',
|
|
||||||
];
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Decrypt a file attached to a matrix event.
|
* Decrypt a file attached to a matrix event.
|
||||||
|
@ -100,9 +45,7 @@ export function decryptFile(file: IEncryptedFile): Promise<Blob> {
|
||||||
// browser (e.g. by copying the URI into a new tab or window.)
|
// browser (e.g. by copying the URI into a new tab or window.)
|
||||||
// See warning at top of file.
|
// See warning at top of file.
|
||||||
let mimetype = file.mimetype ? file.mimetype.split(";")[0].trim() : '';
|
let mimetype = file.mimetype ? file.mimetype.split(";")[0].trim() : '';
|
||||||
if (!ALLOWED_BLOB_MIMETYPES.includes(mimetype)) {
|
mimetype = getBlobSafeMimeType(mimetype);
|
||||||
mimetype = 'application/octet-stream';
|
|
||||||
}
|
|
||||||
|
|
||||||
return new Blob([dataArray], {type: mimetype});
|
return new Blob([dataArray], {type: mimetype});
|
||||||
});
|
});
|
||||||
|
|
|
@ -0,0 +1,78 @@
|
||||||
|
/*
|
||||||
|
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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
// WARNING: We have to be very careful about what mime-types we allow into blobs,
|
||||||
|
// as for performance reasons these are now rendered via URL.createObjectURL()
|
||||||
|
// rather than by converting into data: URIs.
|
||||||
|
//
|
||||||
|
// This means that the content is rendered using the origin of the script which
|
||||||
|
// called createObjectURL(), and so if the content contains any scripting then it
|
||||||
|
// will pose a XSS vulnerability when the browser renders it. This is particularly
|
||||||
|
// bad if the user right-clicks the URI and pastes it into a new window or tab,
|
||||||
|
// as the blob will then execute with access to Element's full JS environment(!)
|
||||||
|
//
|
||||||
|
// See https://github.com/matrix-org/matrix-react-sdk/pull/1820#issuecomment-385210647
|
||||||
|
// for details.
|
||||||
|
//
|
||||||
|
// We mitigate this by only allowing mime-types into blobs which we know don't
|
||||||
|
// contain any scripting, and instantiate all others as application/octet-stream
|
||||||
|
// regardless of what mime-type the event claimed. Even if the payload itself
|
||||||
|
// is some malicious HTML, the fact we instantiate it with a media mimetype or
|
||||||
|
// application/octet-stream means the browser doesn't try to render it as such.
|
||||||
|
//
|
||||||
|
// One interesting edge case is image/svg+xml, which empirically *is* rendered
|
||||||
|
// correctly if the blob is set to the src attribute of an img tag (for thumbnails)
|
||||||
|
// *even if the mimetype is application/octet-stream*. However, empirically JS
|
||||||
|
// in the SVG isn't executed in this scenario, so we seem to be okay.
|
||||||
|
//
|
||||||
|
// Tested on Chrome 65 and Firefox 60
|
||||||
|
//
|
||||||
|
// The list below is taken mainly from
|
||||||
|
// https://developer.mozilla.org/en-US/docs/Web/HTML/Supported_media_formats
|
||||||
|
// N.B. Matrix doesn't currently specify which mimetypes are valid in given
|
||||||
|
// events, so we pick the ones which HTML5 browsers should be able to display
|
||||||
|
//
|
||||||
|
// For the record, mime-types which must NEVER enter this list below include:
|
||||||
|
// text/html, text/xhtml, image/svg, image/svg+xml, image/pdf, and similar.
|
||||||
|
|
||||||
|
const ALLOWED_BLOB_MIMETYPES = [
|
||||||
|
'image/jpeg',
|
||||||
|
'image/gif',
|
||||||
|
'image/png',
|
||||||
|
|
||||||
|
'video/mp4',
|
||||||
|
'video/webm',
|
||||||
|
'video/ogg',
|
||||||
|
|
||||||
|
'audio/mp4',
|
||||||
|
'audio/webm',
|
||||||
|
'audio/aac',
|
||||||
|
'audio/mpeg',
|
||||||
|
'audio/ogg',
|
||||||
|
'audio/wave',
|
||||||
|
'audio/wav',
|
||||||
|
'audio/x-wav',
|
||||||
|
'audio/x-pn-wav',
|
||||||
|
'audio/flac',
|
||||||
|
'audio/x-flac',
|
||||||
|
];
|
||||||
|
|
||||||
|
export function getBlobSafeMimeType(mimetype: string): string {
|
||||||
|
if (!ALLOWED_BLOB_MIMETYPES.includes(mimetype)) {
|
||||||
|
return 'application/octet-stream';
|
||||||
|
}
|
||||||
|
return mimetype;
|
||||||
|
}
|
|
@ -77,7 +77,7 @@ describe('MessagePanel', function() {
|
||||||
DMRoomMap.makeShared();
|
DMRoomMap.makeShared();
|
||||||
});
|
});
|
||||||
|
|
||||||
afterEach(function() {
|
afterEach(function () {
|
||||||
clock.uninstall();
|
clock.uninstall();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -88,7 +88,21 @@ describe('MessagePanel', function() {
|
||||||
events.push(test_utils.mkMessage(
|
events.push(test_utils.mkMessage(
|
||||||
{
|
{
|
||||||
event: true, room: "!room:id", user: "@user:id",
|
event: true, room: "!room:id", user: "@user:id",
|
||||||
ts: ts0 + i*1000,
|
ts: ts0 + i * 1000,
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
return events;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Just to avoid breaking Dateseparator tests that might run at 00hrs
|
||||||
|
function mkOneDayEvents() {
|
||||||
|
const events = [];
|
||||||
|
const ts0 = Date.parse('09 May 2004 00:12:00 GMT');
|
||||||
|
for (let i = 0; i < 10; i++) {
|
||||||
|
events.push(test_utils.mkMessage(
|
||||||
|
{
|
||||||
|
event: true, room: "!room:id", user: "@user:id",
|
||||||
|
ts: ts0 + i * 1000,
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
return events;
|
return events;
|
||||||
|
@ -104,7 +118,7 @@ describe('MessagePanel', function() {
|
||||||
let i = 0;
|
let i = 0;
|
||||||
events.push(test_utils.mkMessage({
|
events.push(test_utils.mkMessage({
|
||||||
event: true, room: "!room:id", user: "@user:id",
|
event: true, room: "!room:id", user: "@user:id",
|
||||||
ts: ts0 + ++i*1000,
|
ts: ts0 + ++i * 1000,
|
||||||
}));
|
}));
|
||||||
|
|
||||||
for (i = 0; i < 10; i++) {
|
for (i = 0; i < 10; i++) {
|
||||||
|
@ -151,7 +165,7 @@ describe('MessagePanel', function() {
|
||||||
},
|
},
|
||||||
getMxcAvatarUrl: () => 'mxc://avatar.url/image.png',
|
getMxcAvatarUrl: () => 'mxc://avatar.url/image.png',
|
||||||
},
|
},
|
||||||
ts: ts0 + i*1000,
|
ts: ts0 + i * 1000,
|
||||||
mship: 'join',
|
mship: 'join',
|
||||||
prevMship: 'join',
|
prevMship: 'join',
|
||||||
name: 'A user',
|
name: 'A user',
|
||||||
|
@ -250,7 +264,6 @@ describe('MessagePanel', function() {
|
||||||
}),
|
}),
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
function isReadMarkerVisible(rmContainer) {
|
function isReadMarkerVisible(rmContainer) {
|
||||||
return rmContainer && rmContainer.children.length > 0;
|
return rmContainer && rmContainer.children.length > 0;
|
||||||
}
|
}
|
||||||
|
@ -437,4 +450,17 @@ describe('MessagePanel', function() {
|
||||||
// read marker should be hidden given props and at the last event
|
// read marker should be hidden given props and at the last event
|
||||||
expect(isReadMarkerVisible(rm)).toBeFalsy();
|
expect(isReadMarkerVisible(rm)).toBeFalsy();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should render Date separators for the events', function () {
|
||||||
|
const events = mkOneDayEvents();
|
||||||
|
const res = mount(
|
||||||
|
<WrappedMessagePanel
|
||||||
|
className="cls"
|
||||||
|
events={events}
|
||||||
|
/>,
|
||||||
|
);
|
||||||
|
const Dates = res.find(sdk.getComponent('messages.DateSeparator'));
|
||||||
|
|
||||||
|
expect(Dates.length).toEqual(1);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -101,6 +101,7 @@ const invite1 = "!invite1:server";
|
||||||
const invite2 = "!invite2:server";
|
const invite2 = "!invite2:server";
|
||||||
const room1 = "!room1:server";
|
const room1 = "!room1:server";
|
||||||
const room2 = "!room2:server";
|
const room2 = "!room2:server";
|
||||||
|
const room3 = "!room3:server";
|
||||||
const space1 = "!space1:server";
|
const space1 = "!space1:server";
|
||||||
const space2 = "!space2:server";
|
const space2 = "!space2:server";
|
||||||
const space3 = "!space3:server";
|
const space3 = "!space3:server";
|
||||||
|
@ -361,8 +362,8 @@ describe("SpaceStore", () => {
|
||||||
expect(store.getSpaceFilteredRoomIds(null).has(invite2)).toBeTruthy();
|
expect(store.getSpaceFilteredRoomIds(null).has(invite2)).toBeTruthy();
|
||||||
});
|
});
|
||||||
|
|
||||||
it("home space does not contain rooms/low priority from rooms within spaces", () => {
|
it("home space does contain rooms/low priority even if they are also shown in a space", () => {
|
||||||
expect(store.getSpaceFilteredRoomIds(null).has(room1)).toBeFalsy();
|
expect(store.getSpaceFilteredRoomIds(null).has(room1)).toBeTruthy();
|
||||||
});
|
});
|
||||||
|
|
||||||
it("space contains child rooms", () => {
|
it("space contains child rooms", () => {
|
||||||
|
@ -614,8 +615,8 @@ describe("SpaceStore", () => {
|
||||||
|
|
||||||
describe("space auto switching tests", () => {
|
describe("space auto switching tests", () => {
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
[room1, room2, orphan1].forEach(mkRoom);
|
[room1, room2, room3, orphan1].forEach(mkRoom);
|
||||||
mkSpace(space1, [room1, room2]);
|
mkSpace(space1, [room1, room2, room3]);
|
||||||
mkSpace(space2, [room1, room2]);
|
mkSpace(space2, [room1, room2]);
|
||||||
|
|
||||||
client.getRoom(room2).currentState.getStateEvents.mockImplementation(mockStateEventImplementation([
|
client.getRoom(room2).currentState.getStateEvents.mockImplementation(mockStateEventImplementation([
|
||||||
|
@ -641,15 +642,15 @@ describe("SpaceStore", () => {
|
||||||
|
|
||||||
it("switch to canonical parent space for room", async () => {
|
it("switch to canonical parent space for room", async () => {
|
||||||
viewRoom(room1);
|
viewRoom(room1);
|
||||||
await store.setActiveSpace(null, false);
|
await store.setActiveSpace(client.getRoom(space2), false);
|
||||||
viewRoom(room2);
|
viewRoom(room2);
|
||||||
expect(store.activeSpace).toBe(client.getRoom(space2));
|
expect(store.activeSpace).toBe(client.getRoom(space2));
|
||||||
});
|
});
|
||||||
|
|
||||||
it("switch to first containing space for room", async () => {
|
it("switch to first containing space for room", async () => {
|
||||||
viewRoom(room2);
|
viewRoom(room2);
|
||||||
await store.setActiveSpace(null, false);
|
await store.setActiveSpace(client.getRoom(space2), false);
|
||||||
viewRoom(room1);
|
viewRoom(room3);
|
||||||
expect(store.activeSpace).toBe(client.getRoom(space1));
|
expect(store.activeSpace).toBe(client.getRoom(space1));
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -659,6 +660,13 @@ describe("SpaceStore", () => {
|
||||||
viewRoom(orphan1);
|
viewRoom(orphan1);
|
||||||
expect(store.activeSpace).toBeNull();
|
expect(store.activeSpace).toBeNull();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("when switching rooms in the all rooms home space don't switch to related space", async () => {
|
||||||
|
viewRoom(room2);
|
||||||
|
await store.setActiveSpace(null, false);
|
||||||
|
viewRoom(room1);
|
||||||
|
expect(store.activeSpace).toBeNull();
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("traverseSpace", () => {
|
describe("traverseSpace", () => {
|
||||||
|
|
190
yarn.lock
190
yarn.lock
|
@ -1594,6 +1594,11 @@
|
||||||
resolved "https://registry.yarnpkg.com/@types/parse-json/-/parse-json-4.0.0.tgz#2f8bb441434d163b35fb8ffdccd7138927ffb8c0"
|
resolved "https://registry.yarnpkg.com/@types/parse-json/-/parse-json-4.0.0.tgz#2f8bb441434d163b35fb8ffdccd7138927ffb8c0"
|
||||||
integrity sha512-//oorEZjL6sbPcKUaCdIGlIUeH26mgzimjBB77G6XRgnDl/L5wOnpyBGRe/Mmf5CVW3PwEBE1NjiMZ/ssFh4wA==
|
integrity sha512-//oorEZjL6sbPcKUaCdIGlIUeH26mgzimjBB77G6XRgnDl/L5wOnpyBGRe/Mmf5CVW3PwEBE1NjiMZ/ssFh4wA==
|
||||||
|
|
||||||
|
"@types/parse5@^6.0.0":
|
||||||
|
version "6.0.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/@types/parse5/-/parse5-6.0.0.tgz#38590dc2c3cf5717154064e3ee9b6947ee21b299"
|
||||||
|
integrity sha512-oPwPSj4a1wu9rsXTEGIJz91ISU725t0BmSnUhb57sI+M8XEmvUop84lzuiYdq0Y5M6xLY8DBPg0C2xEQKLyvBA==
|
||||||
|
|
||||||
"@types/prettier@^2.0.0":
|
"@types/prettier@^2.0.0":
|
||||||
version "2.1.6"
|
version "2.1.6"
|
||||||
resolved "https://registry.yarnpkg.com/@types/prettier/-/prettier-2.1.6.tgz#f4b1efa784e8db479cdb8b14403e2144b1e9ff03"
|
resolved "https://registry.yarnpkg.com/@types/prettier/-/prettier-2.1.6.tgz#f4b1efa784e8db479cdb8b14403e2144b1e9ff03"
|
||||||
|
@ -1633,12 +1638,12 @@
|
||||||
"@types/prop-types" "*"
|
"@types/prop-types" "*"
|
||||||
csstype "^3.0.2"
|
csstype "^3.0.2"
|
||||||
|
|
||||||
"@types/sanitize-html@^1.27.0":
|
"@types/sanitize-html@^2.3.1":
|
||||||
version "1.27.0"
|
version "2.3.1"
|
||||||
resolved "https://registry.yarnpkg.com/@types/sanitize-html/-/sanitize-html-1.27.0.tgz#77702dc856f16efecc005014c1d2e45b1f2cbc56"
|
resolved "https://registry.yarnpkg.com/@types/sanitize-html/-/sanitize-html-2.3.1.tgz#094d696b83b7394b016e96342bbffa6a028795ce"
|
||||||
integrity sha512-j7Vnh3P7W4ZcoRsHNO2HpwA2m1d0c2+l39xqSQqH0+WlfcvKypgZp45eCC7NJ75ZyXPxNb2PSbIL6LtZ6E0Qbw==
|
integrity sha512-+UT/XRluJuCunRftwO6OzG6WOBgJ+J3sROIoSJWX+7PB2FtTJTEJLrHCcNwzCQc0r60bej3WAbaigK+VZtZCGw==
|
||||||
dependencies:
|
dependencies:
|
||||||
htmlparser2 "^4.1.0"
|
htmlparser2 "^6.0.0"
|
||||||
|
|
||||||
"@types/stack-utils@^1.0.1":
|
"@types/stack-utils@^1.0.1":
|
||||||
version "1.0.1"
|
version "1.0.1"
|
||||||
|
@ -2396,29 +2401,29 @@ chardet@^0.7.0:
|
||||||
resolved "https://registry.yarnpkg.com/chardet/-/chardet-0.7.0.tgz#90094849f0937f2eedc2425d0d28a9e5f0cbad9e"
|
resolved "https://registry.yarnpkg.com/chardet/-/chardet-0.7.0.tgz#90094849f0937f2eedc2425d0d28a9e5f0cbad9e"
|
||||||
integrity sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA==
|
integrity sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA==
|
||||||
|
|
||||||
cheerio-select-tmp@^0.1.0:
|
cheerio-select@^1.4.0:
|
||||||
version "0.1.1"
|
version "1.4.0"
|
||||||
resolved "https://registry.yarnpkg.com/cheerio-select-tmp/-/cheerio-select-tmp-0.1.1.tgz#55bbef02a4771710195ad736d5e346763ca4e646"
|
resolved "https://registry.yarnpkg.com/cheerio-select/-/cheerio-select-1.4.0.tgz#3a16f21e37a2ef0f211d6d1aa4eff054bb22cdc9"
|
||||||
integrity sha512-YYs5JvbpU19VYJyj+F7oYrIE2BOll1/hRU7rEy/5+v9BzkSo3bK81iAeeQEMI92vRIxz677m72UmJUiVwwgjfQ==
|
integrity sha512-sobR3Yqz27L553Qa7cK6rtJlMDbiKPdNywtR95Sj/YgfpLfy0u6CGJuaBKe5YE/vTc23SCRKxWSdlon/w6I/Ew==
|
||||||
dependencies:
|
dependencies:
|
||||||
css-select "^3.1.2"
|
css-select "^4.1.2"
|
||||||
css-what "^4.0.0"
|
css-what "^5.0.0"
|
||||||
domelementtype "^2.1.0"
|
domelementtype "^2.2.0"
|
||||||
domhandler "^4.0.0"
|
domhandler "^4.2.0"
|
||||||
domutils "^2.4.4"
|
domutils "^2.6.0"
|
||||||
|
|
||||||
cheerio@^1.0.0-rc.3, cheerio@^1.0.0-rc.5:
|
cheerio@^1.0.0-rc.3, cheerio@^1.0.0-rc.9:
|
||||||
version "1.0.0-rc.5"
|
version "1.0.0-rc.9"
|
||||||
resolved "https://registry.yarnpkg.com/cheerio/-/cheerio-1.0.0-rc.5.tgz#88907e1828674e8f9fee375188b27dadd4f0fa2f"
|
resolved "https://registry.yarnpkg.com/cheerio/-/cheerio-1.0.0-rc.9.tgz#a3ae6b7ce7af80675302ff836f628e7cb786a67f"
|
||||||
integrity sha512-yoqps/VCaZgN4pfXtenwHROTp8NG6/Hlt4Jpz2FEP0ZJQ+ZUkVDd0hAPDNKhj3nakpfPt/CNs57yEtxD1bXQiw==
|
integrity sha512-QF6XVdrLONO6DXRF5iaolY+odmhj2CLj+xzNod7INPWMi/x9X4SOylH0S/vaPpX+AUU6t04s34SQNh7DbkuCng==
|
||||||
dependencies:
|
dependencies:
|
||||||
cheerio-select-tmp "^0.1.0"
|
cheerio-select "^1.4.0"
|
||||||
dom-serializer "~1.2.0"
|
dom-serializer "^1.3.1"
|
||||||
domhandler "^4.0.0"
|
domhandler "^4.2.0"
|
||||||
entities "~2.1.0"
|
htmlparser2 "^6.1.0"
|
||||||
htmlparser2 "^6.0.0"
|
parse5 "^6.0.1"
|
||||||
parse5 "^6.0.0"
|
parse5-htmlparser2-tree-adapter "^6.0.1"
|
||||||
parse5-htmlparser2-tree-adapter "^6.0.0"
|
tslib "^2.2.0"
|
||||||
|
|
||||||
chokidar@^3.4.0, chokidar@^3.5.1:
|
chokidar@^3.4.0, chokidar@^3.5.1:
|
||||||
version "3.5.1"
|
version "3.5.1"
|
||||||
|
@ -2700,21 +2705,21 @@ cross-spawn@^7.0.0, cross-spawn@^7.0.2:
|
||||||
shebang-command "^2.0.0"
|
shebang-command "^2.0.0"
|
||||||
which "^2.0.1"
|
which "^2.0.1"
|
||||||
|
|
||||||
css-select@^3.1.2:
|
css-select@^4.1.2:
|
||||||
version "3.1.2"
|
version "4.1.2"
|
||||||
resolved "https://registry.yarnpkg.com/css-select/-/css-select-3.1.2.tgz#d52cbdc6fee379fba97fb0d3925abbd18af2d9d8"
|
resolved "https://registry.yarnpkg.com/css-select/-/css-select-4.1.2.tgz#8b52b6714ed3a80d8221ec971c543f3b12653286"
|
||||||
integrity sha512-qmss1EihSuBNWNNhHjxzxSfJoFBM/lERB/Q4EnsJQQC62R2evJDW481091oAdOr9uh46/0n4nrg0It5cAnj1RA==
|
integrity sha512-nu5ye2Hg/4ISq4XqdLY2bEatAcLIdt3OYGFc9Tm9n7VSlFBcfRv0gBNksHRgSdUDQGtN3XrZ94ztW+NfzkFSUw==
|
||||||
dependencies:
|
dependencies:
|
||||||
boolbase "^1.0.0"
|
boolbase "^1.0.0"
|
||||||
css-what "^4.0.0"
|
css-what "^5.0.0"
|
||||||
domhandler "^4.0.0"
|
domhandler "^4.2.0"
|
||||||
domutils "^2.4.3"
|
domutils "^2.6.0"
|
||||||
nth-check "^2.0.0"
|
nth-check "^2.0.0"
|
||||||
|
|
||||||
css-what@^4.0.0:
|
css-what@^5.0.0:
|
||||||
version "4.0.0"
|
version "5.0.0"
|
||||||
resolved "https://registry.yarnpkg.com/css-what/-/css-what-4.0.0.tgz#35e73761cab2eeb3d3661126b23d7aa0e8432233"
|
resolved "https://registry.yarnpkg.com/css-what/-/css-what-5.0.0.tgz#f0bf4f8bac07582722346ab243f6a35b512cfc47"
|
||||||
integrity sha512-teijzG7kwYfNVsUh2H/YN62xW3KK9YhXEgSlbxMlcyjPNvdKJqFx5lrwlJgoFP1ZHlB89iGDlo/JyshKeRhv5A==
|
integrity sha512-qxyKHQvgKwzwDWC/rGbT821eJalfupxYW2qbSJSAtdSTimsr/MlaGONoNLllaUPZWf8QnbcKM/kPVYUQuEKAFA==
|
||||||
|
|
||||||
cssesc@^3.0.0:
|
cssesc@^3.0.0:
|
||||||
version "3.0.0"
|
version "3.0.0"
|
||||||
|
@ -2920,9 +2925,9 @@ doctrine@^3.0.0:
|
||||||
esutils "^2.0.2"
|
esutils "^2.0.2"
|
||||||
|
|
||||||
dom-helpers@^5.0.1:
|
dom-helpers@^5.0.1:
|
||||||
version "5.2.0"
|
version "5.2.1"
|
||||||
resolved "https://registry.yarnpkg.com/dom-helpers/-/dom-helpers-5.2.0.tgz#57fd054c5f8f34c52a3eeffdb7e7e93cd357d95b"
|
resolved "https://registry.yarnpkg.com/dom-helpers/-/dom-helpers-5.2.1.tgz#d9400536b2bf8225ad98fe052e029451ac40e902"
|
||||||
integrity sha512-Ru5o9+V8CpunKnz5LGgWXkmrH/20cGKwcHwS4m73zIvs54CN9epEmT/HLqFJW3kXpakAFkEdzgy1hzlJe3E4OQ==
|
integrity sha512-nRCa7CK3VTrM2NmGkIy4cbK7IZlgBE/PYMn55rrXefr5xXDP0LdtfPnblFDoVdcAfslJ7or6iqAUnx0CCGIWQA==
|
||||||
dependencies:
|
dependencies:
|
||||||
"@babel/runtime" "^7.8.7"
|
"@babel/runtime" "^7.8.7"
|
||||||
csstype "^3.0.2"
|
csstype "^3.0.2"
|
||||||
|
@ -2935,10 +2940,10 @@ dom-serializer@0:
|
||||||
domelementtype "^2.0.1"
|
domelementtype "^2.0.1"
|
||||||
entities "^2.0.0"
|
entities "^2.0.0"
|
||||||
|
|
||||||
dom-serializer@^1.0.1, dom-serializer@~1.2.0:
|
dom-serializer@^1.0.1, dom-serializer@^1.3.1:
|
||||||
version "1.2.0"
|
version "1.3.1"
|
||||||
resolved "https://registry.yarnpkg.com/dom-serializer/-/dom-serializer-1.2.0.tgz#3433d9136aeb3c627981daa385fc7f32d27c48f1"
|
resolved "https://registry.yarnpkg.com/dom-serializer/-/dom-serializer-1.3.1.tgz#d845a1565d7c041a95e5dab62184ab41e3a519be"
|
||||||
integrity sha512-n6kZFH/KlCrqs/1GHMOd5i2fd/beQHuehKdWvNNffbGHTr/almdhuVvTVFb3V7fglz+nC50fFusu3lY33h12pA==
|
integrity sha512-Pv2ZluG5ife96udGgEDovOOOA5UELkltfJpnIExPrAk1LTvecolUGn6lIaoLh86d83GiB86CjzciMd9BuRB71Q==
|
||||||
dependencies:
|
dependencies:
|
||||||
domelementtype "^2.0.1"
|
domelementtype "^2.0.1"
|
||||||
domhandler "^4.0.0"
|
domhandler "^4.0.0"
|
||||||
|
@ -2949,10 +2954,10 @@ domelementtype@1, domelementtype@^1.3.1:
|
||||||
resolved "https://registry.yarnpkg.com/domelementtype/-/domelementtype-1.3.1.tgz#d048c44b37b0d10a7f2a3d5fee3f4333d790481f"
|
resolved "https://registry.yarnpkg.com/domelementtype/-/domelementtype-1.3.1.tgz#d048c44b37b0d10a7f2a3d5fee3f4333d790481f"
|
||||||
integrity sha512-BSKB+TSpMpFI/HOxCNr1O8aMOTZ8hT3pM3GQ0w/mWRmkhEDSFJkkyzz4XQsBV44BChwGkrDfMyjVD0eA2aFV3w==
|
integrity sha512-BSKB+TSpMpFI/HOxCNr1O8aMOTZ8hT3pM3GQ0w/mWRmkhEDSFJkkyzz4XQsBV44BChwGkrDfMyjVD0eA2aFV3w==
|
||||||
|
|
||||||
domelementtype@^2.0.1, domelementtype@^2.1.0:
|
domelementtype@^2.0.1, domelementtype@^2.2.0:
|
||||||
version "2.1.0"
|
version "2.2.0"
|
||||||
resolved "https://registry.yarnpkg.com/domelementtype/-/domelementtype-2.1.0.tgz#a851c080a6d1c3d94344aed151d99f669edf585e"
|
resolved "https://registry.yarnpkg.com/domelementtype/-/domelementtype-2.2.0.tgz#9a0b6c2782ed6a1c7323d42267183df9bd8b1d57"
|
||||||
integrity sha512-LsTgx/L5VpD+Q8lmsXSHW2WpA+eBlZ9HPf3erD1IoPF00/3JKHZ3BknUVA2QGDNu69ZNmyFmCWBSO45XjYKC5w==
|
integrity sha512-DtBMo82pv1dFtUmHyr48beiuq792Sxohr+8Hm9zoxklYPfa6n0Z3Byjj2IV7bmr2IyqClnqEQhfgHJJ5QF0R5A==
|
||||||
|
|
||||||
domexception@^2.0.1:
|
domexception@^2.0.1:
|
||||||
version "2.0.1"
|
version "2.0.1"
|
||||||
|
@ -2968,19 +2973,12 @@ domhandler@^2.3.0:
|
||||||
dependencies:
|
dependencies:
|
||||||
domelementtype "1"
|
domelementtype "1"
|
||||||
|
|
||||||
domhandler@^3.0.0:
|
domhandler@^4.0.0, domhandler@^4.2.0:
|
||||||
version "3.3.0"
|
version "4.2.0"
|
||||||
resolved "https://registry.yarnpkg.com/domhandler/-/domhandler-3.3.0.tgz#6db7ea46e4617eb15cf875df68b2b8524ce0037a"
|
resolved "https://registry.yarnpkg.com/domhandler/-/domhandler-4.2.0.tgz#f9768a5f034be60a89a27c2e4d0f74eba0d8b059"
|
||||||
integrity sha512-J1C5rIANUbuYK+FuFL98650rihynUOEzRLxW+90bKZRWB6A1X1Tf82GxR1qAWLyfNPRvjqfip3Q5tdYlmAa9lA==
|
integrity sha512-zk7sgt970kzPks2Bf+dwT/PLzghLnsivb9CcxkvR8Mzr66Olr0Ofd8neSbglHJHaHa2MadfoSdNlKYAaafmWfA==
|
||||||
dependencies:
|
dependencies:
|
||||||
domelementtype "^2.0.1"
|
domelementtype "^2.2.0"
|
||||||
|
|
||||||
domhandler@^4.0.0:
|
|
||||||
version "4.0.0"
|
|
||||||
resolved "https://registry.yarnpkg.com/domhandler/-/domhandler-4.0.0.tgz#01ea7821de996d85f69029e81fa873c21833098e"
|
|
||||||
integrity sha512-KPTbnGQ1JeEMQyO1iYXoagsI6so/C96HZiFyByU3T6iAzpXn8EGEvct6unm1ZGoed8ByO2oirxgwxBmqKF9haA==
|
|
||||||
dependencies:
|
|
||||||
domelementtype "^2.1.0"
|
|
||||||
|
|
||||||
domutils@^1.5.1:
|
domutils@^1.5.1:
|
||||||
version "1.7.0"
|
version "1.7.0"
|
||||||
|
@ -2990,14 +2988,14 @@ domutils@^1.5.1:
|
||||||
dom-serializer "0"
|
dom-serializer "0"
|
||||||
domelementtype "1"
|
domelementtype "1"
|
||||||
|
|
||||||
domutils@^2.0.0, domutils@^2.4.3, domutils@^2.4.4:
|
domutils@^2.4.4, domutils@^2.5.2, domutils@^2.6.0:
|
||||||
version "2.4.4"
|
version "2.6.0"
|
||||||
resolved "https://registry.yarnpkg.com/domutils/-/domutils-2.4.4.tgz#282739c4b150d022d34699797369aad8d19bbbd3"
|
resolved "https://registry.yarnpkg.com/domutils/-/domutils-2.6.0.tgz#2e15c04185d43fb16ae7057cb76433c6edb938b7"
|
||||||
integrity sha512-jBC0vOsECI4OMdD0GC9mGn7NXPLb+Qt6KW1YDQzeQYRUFKmNG8lh7mO5HiELfr+lLQE7loDVI4QcAxV80HS+RA==
|
integrity sha512-y0BezHuy4MDYxh6OvolXYsH+1EMGmFbwv5FKW7ovwMG6zTPWqNPq3WF9ayZssFq+UlKdffGLbOEaghNdaOm1WA==
|
||||||
dependencies:
|
dependencies:
|
||||||
dom-serializer "^1.0.1"
|
dom-serializer "^1.0.1"
|
||||||
domelementtype "^2.0.1"
|
domelementtype "^2.2.0"
|
||||||
domhandler "^4.0.0"
|
domhandler "^4.2.0"
|
||||||
|
|
||||||
ecc-jsbn@~0.1.1:
|
ecc-jsbn@~0.1.1:
|
||||||
version "0.1.2"
|
version "0.1.2"
|
||||||
|
@ -3063,7 +3061,7 @@ entities@^1.1.1:
|
||||||
resolved "https://registry.yarnpkg.com/entities/-/entities-1.1.2.tgz#bdfa735299664dfafd34529ed4f8522a275fea56"
|
resolved "https://registry.yarnpkg.com/entities/-/entities-1.1.2.tgz#bdfa735299664dfafd34529ed4f8522a275fea56"
|
||||||
integrity sha512-f2LZMYl1Fzu7YSBKg+RoROelpOaNrcGmE9AZubeDfrCEia483oW4MI4VyFd5VNHIgQ/7qm1I0wUHK1eJnn2y2w==
|
integrity sha512-f2LZMYl1Fzu7YSBKg+RoROelpOaNrcGmE9AZubeDfrCEia483oW4MI4VyFd5VNHIgQ/7qm1I0wUHK1eJnn2y2w==
|
||||||
|
|
||||||
entities@^2.0.0, entities@~2.1.0:
|
entities@^2.0.0:
|
||||||
version "2.1.0"
|
version "2.1.0"
|
||||||
resolved "https://registry.yarnpkg.com/entities/-/entities-2.1.0.tgz#992d3129cf7df6870b96c57858c249a120f8b8b5"
|
resolved "https://registry.yarnpkg.com/entities/-/entities-2.1.0.tgz#992d3129cf7df6870b96c57858c249a120f8b8b5"
|
||||||
integrity sha512-hCx1oky9PFrJ611mf0ifBLBRW8lUUVRlFolb5gWRfIELabBlbp9xZvrqZLZAs+NxFnbfQoeGd8wDkygjg7U85w==
|
integrity sha512-hCx1oky9PFrJ611mf0ifBLBRW8lUUVRlFolb5gWRfIELabBlbp9xZvrqZLZAs+NxFnbfQoeGd8wDkygjg7U85w==
|
||||||
|
@ -4221,9 +4219,9 @@ hoist-non-react-statics@^3.3.0:
|
||||||
react-is "^16.7.0"
|
react-is "^16.7.0"
|
||||||
|
|
||||||
hosted-git-info@^2.1.4:
|
hosted-git-info@^2.1.4:
|
||||||
version "2.8.8"
|
version "2.8.9"
|
||||||
resolved "https://registry.yarnpkg.com/hosted-git-info/-/hosted-git-info-2.8.8.tgz#7539bd4bc1e0e0a895815a2e0262420b12858488"
|
resolved "https://registry.yarnpkg.com/hosted-git-info/-/hosted-git-info-2.8.9.tgz#dffc0bf9a21c02209090f2aa69429e1414daf3f9"
|
||||||
integrity sha512-f/wzC2QaWBs7t9IYqB4T3sR1xviIViXJRJTWBlx2Gf3g0Xi5vI7Yy4koXQ1c9OYDGHN9sBy1DQ2AB8fqZBWhUg==
|
integrity sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw==
|
||||||
|
|
||||||
hosted-git-info@^3.0.6:
|
hosted-git-info@^3.0.6:
|
||||||
version "3.0.7"
|
version "3.0.7"
|
||||||
|
@ -4273,16 +4271,6 @@ htmlparser2@^3.10.0:
|
||||||
inherits "^2.0.1"
|
inherits "^2.0.1"
|
||||||
readable-stream "^3.1.1"
|
readable-stream "^3.1.1"
|
||||||
|
|
||||||
htmlparser2@^4.1.0:
|
|
||||||
version "4.1.0"
|
|
||||||
resolved "https://registry.yarnpkg.com/htmlparser2/-/htmlparser2-4.1.0.tgz#9a4ef161f2e4625ebf7dfbe6c0a2f52d18a59e78"
|
|
||||||
integrity sha512-4zDq1a1zhE4gQso/c5LP1OtrhYTncXNSpvJYtWJBtXAETPlMfi3IFNjGuQbYLuVY4ZR0QMqRVvo4Pdy9KLyP8Q==
|
|
||||||
dependencies:
|
|
||||||
domelementtype "^2.0.1"
|
|
||||||
domhandler "^3.0.0"
|
|
||||||
domutils "^2.0.0"
|
|
||||||
entities "^2.0.0"
|
|
||||||
|
|
||||||
htmlparser2@^6.0.0:
|
htmlparser2@^6.0.0:
|
||||||
version "6.0.0"
|
version "6.0.0"
|
||||||
resolved "https://registry.yarnpkg.com/htmlparser2/-/htmlparser2-6.0.0.tgz#c2da005030390908ca4c91e5629e418e0665ac01"
|
resolved "https://registry.yarnpkg.com/htmlparser2/-/htmlparser2-6.0.0.tgz#c2da005030390908ca4c91e5629e418e0665ac01"
|
||||||
|
@ -4293,6 +4281,16 @@ htmlparser2@^6.0.0:
|
||||||
domutils "^2.4.4"
|
domutils "^2.4.4"
|
||||||
entities "^2.0.0"
|
entities "^2.0.0"
|
||||||
|
|
||||||
|
htmlparser2@^6.1.0:
|
||||||
|
version "6.1.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/htmlparser2/-/htmlparser2-6.1.0.tgz#c4d762b6c3371a05dbe65e94ae43a9f845fb8fb7"
|
||||||
|
integrity sha512-gyyPk6rgonLFEDGoeRgQNaEUvdJ4ktTmmUh/h2t7s+M8oPpIPxgNACWa+6ESR57kXstwqPiCut0V8NRpcwgU7A==
|
||||||
|
dependencies:
|
||||||
|
domelementtype "^2.0.1"
|
||||||
|
domhandler "^4.0.0"
|
||||||
|
domutils "^2.5.2"
|
||||||
|
entities "^2.0.0"
|
||||||
|
|
||||||
http-signature@~1.2.0:
|
http-signature@~1.2.0:
|
||||||
version "1.2.0"
|
version "1.2.0"
|
||||||
resolved "https://registry.yarnpkg.com/http-signature/-/http-signature-1.2.0.tgz#9aecd925114772f3d95b65a60abb8f7c18fbace1"
|
resolved "https://registry.yarnpkg.com/http-signature/-/http-signature-1.2.0.tgz#9aecd925114772f3d95b65a60abb8f7c18fbace1"
|
||||||
|
@ -5672,8 +5670,8 @@ mathml-tag-names@^2.1.3:
|
||||||
integrity sha512-APMBEanjybaPzUrfqU0IMU5I0AswKMH7k8OTLs0vvV4KZpExkTkY87nR/zpbuTPj+gARop7aGUbl11pnDfW6xg==
|
integrity sha512-APMBEanjybaPzUrfqU0IMU5I0AswKMH7k8OTLs0vvV4KZpExkTkY87nR/zpbuTPj+gARop7aGUbl11pnDfW6xg==
|
||||||
|
|
||||||
"matrix-js-sdk@github:matrix-org/matrix-js-sdk#develop":
|
"matrix-js-sdk@github:matrix-org/matrix-js-sdk#develop":
|
||||||
version "10.0.0"
|
version "10.1.0"
|
||||||
resolved "https://codeload.github.com/matrix-org/matrix-js-sdk/tar.gz/c8f69c0b7937b9064938c134d708c4d064b71315"
|
resolved "https://codeload.github.com/matrix-org/matrix-js-sdk/tar.gz/2d73805ca3d8c5a140fe05e574f826696de1656a"
|
||||||
dependencies:
|
dependencies:
|
||||||
"@babel/runtime" "^7.12.5"
|
"@babel/runtime" "^7.12.5"
|
||||||
another-json "^0.2.0"
|
another-json "^0.2.0"
|
||||||
|
@ -6302,7 +6300,12 @@ parse-json@^5.0.0:
|
||||||
json-parse-even-better-errors "^2.3.0"
|
json-parse-even-better-errors "^2.3.0"
|
||||||
lines-and-columns "^1.1.6"
|
lines-and-columns "^1.1.6"
|
||||||
|
|
||||||
parse5-htmlparser2-tree-adapter@^6.0.0:
|
parse-srcset@^1.0.2:
|
||||||
|
version "1.0.2"
|
||||||
|
resolved "https://registry.yarnpkg.com/parse-srcset/-/parse-srcset-1.0.2.tgz#f2bd221f6cc970a938d88556abc589caaaa2bde1"
|
||||||
|
integrity sha1-8r0iH2zJcKk42IVWq8WJyqqiveE=
|
||||||
|
|
||||||
|
parse5-htmlparser2-tree-adapter@^6.0.1:
|
||||||
version "6.0.1"
|
version "6.0.1"
|
||||||
resolved "https://registry.yarnpkg.com/parse5-htmlparser2-tree-adapter/-/parse5-htmlparser2-tree-adapter-6.0.1.tgz#2cdf9ad823321140370d4dbf5d3e92c7c8ddc6e6"
|
resolved "https://registry.yarnpkg.com/parse5-htmlparser2-tree-adapter/-/parse5-htmlparser2-tree-adapter-6.0.1.tgz#2cdf9ad823321140370d4dbf5d3e92c7c8ddc6e6"
|
||||||
integrity sha512-qPuWvbLgvDGilKc5BoicRovlT4MtYT6JfJyBOMDsKoiT+GiuP5qyrPCnR9HcPECIJJmZh5jRndyNThnhhb/vlA==
|
integrity sha512-qPuWvbLgvDGilKc5BoicRovlT4MtYT6JfJyBOMDsKoiT+GiuP5qyrPCnR9HcPECIJJmZh5jRndyNThnhhb/vlA==
|
||||||
|
@ -6314,7 +6317,7 @@ parse5@5.1.1:
|
||||||
resolved "https://registry.yarnpkg.com/parse5/-/parse5-5.1.1.tgz#f68e4e5ba1852ac2cadc00f4555fff6c2abb6178"
|
resolved "https://registry.yarnpkg.com/parse5/-/parse5-5.1.1.tgz#f68e4e5ba1852ac2cadc00f4555fff6c2abb6178"
|
||||||
integrity sha512-ugq4DFI0Ptb+WWjAdOK16+u/nHfiIrcE+sh8kZMaM0WllQKLI9rOUq6c2b7cwPkXdzfQESqvoqK6ug7U/Yyzug==
|
integrity sha512-ugq4DFI0Ptb+WWjAdOK16+u/nHfiIrcE+sh8kZMaM0WllQKLI9rOUq6c2b7cwPkXdzfQESqvoqK6ug7U/Yyzug==
|
||||||
|
|
||||||
parse5@^6.0.0, parse5@^6.0.1:
|
parse5@^6.0.1:
|
||||||
version "6.0.1"
|
version "6.0.1"
|
||||||
resolved "https://registry.yarnpkg.com/parse5/-/parse5-6.0.1.tgz#e1a1c085c569b3dc08321184f19a39cc27f7c30b"
|
resolved "https://registry.yarnpkg.com/parse5/-/parse5-6.0.1.tgz#e1a1c085c569b3dc08321184f19a39cc27f7c30b"
|
||||||
integrity sha512-Ofn/CTFzRGTTxwpNEs9PP93gXShHcTq255nzRYSKe8AkVpZY7e1fpmTfOyoIvjP5HG7Z2ZM7VS9PPhQGW2pOpw==
|
integrity sha512-Ofn/CTFzRGTTxwpNEs9PP93gXShHcTq255nzRYSKe8AkVpZY7e1fpmTfOyoIvjP5HG7Z2ZM7VS9PPhQGW2pOpw==
|
||||||
|
@ -7243,17 +7246,18 @@ sane@^4.0.3:
|
||||||
minimist "^1.1.1"
|
minimist "^1.1.1"
|
||||||
walker "~1.0.5"
|
walker "~1.0.5"
|
||||||
|
|
||||||
"sanitize-html@github:apostrophecms/sanitize-html#3c7f93f2058f696f5359e3e58d464161647226db":
|
sanitize-html@^2.3.2:
|
||||||
version "2.0.0-rc.3"
|
version "2.3.3"
|
||||||
resolved "https://codeload.github.com/apostrophecms/sanitize-html/tar.gz/3c7f93f2058f696f5359e3e58d464161647226db"
|
resolved "https://registry.yarnpkg.com/sanitize-html/-/sanitize-html-2.3.3.tgz#3db382c9a621cce4c46d90f10c64f1e9da9e8353"
|
||||||
|
integrity sha512-DCFXPt7Di0c6JUnlT90eIgrjs6TsJl/8HYU3KLdmrVclFN4O0heTcVbJiMa23OKVr6aR051XYtsgd8EWwEBwUA==
|
||||||
dependencies:
|
dependencies:
|
||||||
deepmerge "^4.2.2"
|
deepmerge "^4.2.2"
|
||||||
escape-string-regexp "^4.0.0"
|
escape-string-regexp "^4.0.0"
|
||||||
htmlparser2 "^4.1.0"
|
htmlparser2 "^6.0.0"
|
||||||
is-plain-object "^5.0.0"
|
is-plain-object "^5.0.0"
|
||||||
klona "^2.0.3"
|
klona "^2.0.3"
|
||||||
|
parse-srcset "^1.0.2"
|
||||||
postcss "^8.0.2"
|
postcss "^8.0.2"
|
||||||
srcset "^3.0.0"
|
|
||||||
|
|
||||||
saxes@^5.0.0:
|
saxes@^5.0.0:
|
||||||
version "5.0.1"
|
version "5.0.1"
|
||||||
|
@ -7510,11 +7514,6 @@ sprintf-js@~1.0.2:
|
||||||
resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.0.3.tgz#04e6926f662895354f3dd015203633b857297e2c"
|
resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.0.3.tgz#04e6926f662895354f3dd015203633b857297e2c"
|
||||||
integrity sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=
|
integrity sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=
|
||||||
|
|
||||||
srcset@^3.0.0:
|
|
||||||
version "3.0.0"
|
|
||||||
resolved "https://registry.yarnpkg.com/srcset/-/srcset-3.0.0.tgz#8afd8b971362dfc129ae9c1a99b3897301ce6441"
|
|
||||||
integrity sha512-D59vF08Qzu/C4GAOXVgMTLfgryt5fyWo93FZyhEWANo0PokFz/iWdDe13mX3O5TRf6l8vMTqckAfR4zPiaH0yQ==
|
|
||||||
|
|
||||||
sshpk@^1.7.0:
|
sshpk@^1.7.0:
|
||||||
version "1.16.1"
|
version "1.16.1"
|
||||||
resolved "https://registry.yarnpkg.com/sshpk/-/sshpk-1.16.1.tgz#fb661c0bef29b39db40769ee39fa70093d6f6877"
|
resolved "https://registry.yarnpkg.com/sshpk/-/sshpk-1.16.1.tgz#fb661c0bef29b39db40769ee39fa70093d6f6877"
|
||||||
|
@ -7999,6 +7998,11 @@ tslib@^2.0.0, tslib@^2.0.1, tslib@^2.0.3:
|
||||||
resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.1.0.tgz#da60860f1c2ecaa5703ab7d39bc05b6bf988b97a"
|
resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.1.0.tgz#da60860f1c2ecaa5703ab7d39bc05b6bf988b97a"
|
||||||
integrity sha512-hcVC3wYEziELGGmEEXue7D75zbwIIVUMWAVbHItGPx0ziyXxrOMQx4rQEVEV45Ut/1IotuEvwqPopzIOkDMf0A==
|
integrity sha512-hcVC3wYEziELGGmEEXue7D75zbwIIVUMWAVbHItGPx0ziyXxrOMQx4rQEVEV45Ut/1IotuEvwqPopzIOkDMf0A==
|
||||||
|
|
||||||
|
tslib@^2.2.0:
|
||||||
|
version "2.2.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.2.0.tgz#fb2c475977e35e241311ede2693cee1ec6698f5c"
|
||||||
|
integrity sha512-gS9GVHRU+RGn5KQM2rllAlR3dU6m7AcpJKdtH8gFvQiC4Otgk98XnmMU+nZenHt/+VhnBPWwgrJsyrdcw6i23w==
|
||||||
|
|
||||||
tsutils@^3.17.1:
|
tsutils@^3.17.1:
|
||||||
version "3.19.1"
|
version "3.19.1"
|
||||||
resolved "https://registry.yarnpkg.com/tsutils/-/tsutils-3.19.1.tgz#d8566e0c51c82f32f9c25a4d367cd62409a547a9"
|
resolved "https://registry.yarnpkg.com/tsutils/-/tsutils-3.19.1.tgz#d8566e0c51c82f32f9c25a4d367cd62409a547a9"
|
||||||
|
|
Loading…
Reference in New Issue