From 118e752a1fd8f3b900709db205976feb84b95304 Mon Sep 17 00:00:00 2001
From: Michael Telatynski <7t3chguy@gmail.com>
Date: Sun, 12 May 2019 23:24:12 +0100
Subject: [PATCH 001/334] Add button to clear all notification counts,
sometimes stuck in historical
Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>
---
.../views/settings/Notifications.js | 22 +++++++++++++++++++
src/i18n/strings/en_EN.json | 1 +
2 files changed, 23 insertions(+)
diff --git a/src/components/views/settings/Notifications.js b/src/components/views/settings/Notifications.js
index 9b5688aa6a..9e01d44fb6 100644
--- a/src/components/views/settings/Notifications.js
+++ b/src/components/views/settings/Notifications.js
@@ -29,6 +29,7 @@ import {
} from '../../../notifications';
import SdkConfig from "../../../SdkConfig";
import LabelledToggleSwitch from "../elements/LabelledToggleSwitch";
+import AccessibleButton from "../elements/AccessibleButton";
// TODO: this "view" component still has far too much application logic in it,
// which should be factored out to other files.
@@ -654,6 +655,17 @@ module.exports = React.createClass({
MatrixClientPeg.get().getThreePids().then((r) => this.setState({threepids: r.threepids}));
},
+ _onClearNotifications: function() {
+ const cli = MatrixClientPeg.get();
+
+ cli.getRooms().forEach(r => {
+ if (r.getUnreadNotificationCount() > 0) {
+ const events = r.getLiveTimeline().getEvents();
+ if (events.length) cli.sendReadReceipt(events.pop());
+ }
+ });
+ },
+
_updatePushRuleActions: function(rule, actions, enabled) {
const cli = MatrixClientPeg.get();
@@ -746,6 +758,13 @@ module.exports = React.createClass({
label={_t('Enable notifications for this account')}/>;
}
+ let clearNotificationsButton;
+ if (MatrixClientPeg.get().getRooms().some(r => r.getUnreadNotificationCount() > 0)) {
+ clearNotificationsButton =
+ {_t("Clear notifications")}
+ ;
+ }
+
// When enabled, the master rule inhibits all existing rules
// So do not show all notification settings
if (this.state.masterPushRule && this.state.masterPushRule.enabled) {
@@ -756,6 +775,8 @@ module.exports = React.createClass({
{ _t('All notifications are currently disabled for all targets.') }
+
+ {clearNotificationsButton}
);
}
@@ -877,6 +898,7 @@ module.exports = React.createClass({
{ devicesSection }
+ { clearNotificationsButton }
diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json
index 8534091176..c6a12d8d56 100644
--- a/src/i18n/strings/en_EN.json
+++ b/src/i18n/strings/en_EN.json
@@ -501,6 +501,7 @@
"Notify for all other messages/rooms": "Notify for all other messages/rooms",
"Notify me for anything else": "Notify me for anything else",
"Enable notifications for this account": "Enable notifications for this account",
+ "Clear notifications": "Clear notifications",
"All notifications are currently disabled for all targets.": "All notifications are currently disabled for all targets.",
"Add an email address to configure email notifications": "Add an email address to configure email notifications",
"Enable email notifications": "Enable email notifications",
From b8a3ee1841f3b06a5c97016d4b75a03ab1456ac8 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Damir=20Jeli=C4=87?=
Date: Fri, 11 Oct 2019 16:07:59 +0200
Subject: [PATCH 002/334] BasePlatform: Add prototype methods for event
indexing.
---
src/BasePlatform.js | 40 ++++++++++++++++++++++++++++++++++++++++
1 file changed, 40 insertions(+)
diff --git a/src/BasePlatform.js b/src/BasePlatform.js
index a97c14bf90..7f5df822e4 100644
--- a/src/BasePlatform.js
+++ b/src/BasePlatform.js
@@ -151,4 +151,44 @@ export default class BasePlatform {
async setMinimizeToTrayEnabled(enabled: boolean): void {
throw new Error("Unimplemented");
}
+
+ supportsEventIndexing(): boolean {
+ return false;
+ }
+
+ async initEventIndex(userId: string): boolean {
+ throw new Error("Unimplemented");
+ }
+
+ async addEventToIndex(ev: {}, profile: {}): void {
+ throw new Error("Unimplemented");
+ }
+
+ indexIsEmpty(): Promise {
+ throw new Error("Unimplemented");
+ }
+
+ async commitLiveEvents(): void {
+ throw new Error("Unimplemented");
+ }
+
+ async searchEventIndex(term: string): Promise<{}> {
+ throw new Error("Unimplemented");
+ }
+
+ async addHistoricEvents(events: [], checkpoint: {} = null, oldCheckpoint: {} = null): Promise {
+ throw new Error("Unimplemented");
+ }
+
+ async addCrawlerCheckpoint(checkpoint: {}): Promise<> {
+ throw new Error("Unimplemented");
+ }
+
+ async removeCrawlerCheckpoint(checkpoint: {}): Promise<> {
+ throw new Error("Unimplemented");
+ }
+
+ async deleteEventIndex(): Promise<> {
+ throw new Error("Unimplemented");
+ }
}
From 9ce478cb0e29fca8bf0815c7f7a13ef29fe573fe Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Damir=20Jeli=C4=87?=
Date: Fri, 11 Oct 2019 16:43:53 +0200
Subject: [PATCH 003/334] MatrixChat: Create an event index and start crawling
for events.
This patch adds support to create an event index if the clients platform
supports it and starts an event crawler.
The event crawler goes through the room history of encrypted rooms and
eventually indexes the whole room history of such rooms.
It does this by first creating crawling checkpoints and storing them
inside a database. A checkpoint consists of a room_id, direction and
token.
After the checkpoints are added the client starts a crawler method in
the background. The crawler goes through checkpoints in a round-robin
way and uses them to fetch historic room messages using the
rooms/roomId/messages API endpoint.
Every time messages are fetched a new checkpoint is created that will
be stored in the database with the fetched events in an atomic way, the
old checkpoint is deleted at the same time as well.
---
src/MatrixClientPeg.js | 4 +
src/components/structures/MatrixChat.js | 231 ++++++++++++++++++++++++
2 files changed, 235 insertions(+)
diff --git a/src/MatrixClientPeg.js b/src/MatrixClientPeg.js
index bebb254afc..5c5ee6e4ec 100644
--- a/src/MatrixClientPeg.js
+++ b/src/MatrixClientPeg.js
@@ -30,6 +30,7 @@ import {verificationMethods} from 'matrix-js-sdk/lib/crypto';
import MatrixClientBackedSettingsHandler from "./settings/handlers/MatrixClientBackedSettingsHandler";
import * as StorageManager from './utils/StorageManager';
import IdentityAuthClient from './IdentityAuthClient';
+import PlatformPeg from "./PlatformPeg";
interface MatrixClientCreds {
homeserverUrl: string,
@@ -222,6 +223,9 @@ class MatrixClientPeg {
this.matrixClient = createMatrixClient(opts);
+ const platform = PlatformPeg.get();
+ if (platform.supportsEventIndexing()) platform.initEventIndex(creds.userId);
+
// we're going to add eventlisteners for each matrix event tile, so the
// potential number of event listeners is quite high.
this.matrixClient.setMaxListeners(500);
diff --git a/src/components/structures/MatrixChat.js b/src/components/structures/MatrixChat.js
index da67416400..218b7e4d4e 100644
--- a/src/components/structures/MatrixChat.js
+++ b/src/components/structures/MatrixChat.js
@@ -1262,6 +1262,7 @@ export default createReactClass({
// to do the first sync
this.firstSyncComplete = false;
this.firstSyncPromise = Promise.defer();
+ this.crawlerChekpoints = [];
const cli = MatrixClientPeg.get();
const IncomingSasDialog = sdk.getComponent('views.dialogs.IncomingSasDialog');
@@ -1287,6 +1288,75 @@ export default createReactClass({
return self._loggedInView.child.canResetTimelineInRoom(roomId);
});
+ cli.on('sync', async (state, prevState, data) => {
+ const platform = PlatformPeg.get();
+ if (!platform.supportsEventIndexing()) return;
+
+ if (prevState === null && state === "PREPARED") {
+ /// Load our stored checkpoints, if any.
+ self.crawlerChekpoints = await platform.loadCheckpoints();
+ console.log("Seshat: Loaded checkpoints",
+ self.crawlerChekpoints);
+ return;
+ }
+
+ if (prevState === "PREPARED" && state === "SYNCING") {
+ const addInitialCheckpoints = async () => {
+ const client = MatrixClientPeg.get();
+ const rooms = client.getRooms();
+
+ const isRoomEncrypted = (room) => {
+ return client.isRoomEncrypted(room.roomId);
+ };
+
+ // We only care to crawl the encrypted rooms, non-encrytped
+ // rooms can use the search provided by the Homeserver.
+ const encryptedRooms = rooms.filter(isRoomEncrypted);
+
+ console.log("Seshat: Adding initial crawler checkpoints");
+
+ // Gather the prev_batch tokens and create checkpoints for
+ // our message crawler.
+ await Promise.all(encryptedRooms.map(async (room) => {
+ const timeline = room.getLiveTimeline();
+ const token = timeline.getPaginationToken("b");
+
+ console.log("Seshat: Got token for indexer",
+ room.roomId, token);
+
+ const backCheckpoint = {
+ roomId: room.roomId,
+ token: token,
+ direction: "b",
+ };
+
+ const forwardCheckpoint = {
+ roomId: room.roomId,
+ token: token,
+ direction: "f",
+ };
+
+ await platform.addCrawlerCheckpoint(backCheckpoint);
+ await platform.addCrawlerCheckpoint(forwardCheckpoint);
+ self.crawlerChekpoints.push(backCheckpoint);
+ self.crawlerChekpoints.push(forwardCheckpoint);
+ }));
+ };
+
+ // If our indexer is empty we're most likely running Riot the
+ // first time with indexing support or running it with an
+ // initial sync. Add checkpoints to crawl our encrypted rooms.
+ const eventIndexWasEmpty = await platform.isEventIndexEmpty();
+ if (eventIndexWasEmpty) await addInitialCheckpoints();
+
+ // Start our crawler.
+ const crawlerHandle = {};
+ self.crawlerFunc(crawlerHandle);
+ self.crawlerRef = crawlerHandle;
+ return;
+ }
+ });
+
cli.on('sync', function(state, prevState, data) {
// LifecycleStore and others cannot directly subscribe to matrix client for
// events because flux only allows store state changes during flux dispatches.
@@ -1930,4 +2000,165 @@ export default createReactClass({
{view}
;
},
+
+ async crawlerFunc(handle) {
+ // TODO either put this in a better place or find a library provided
+ // method that does this.
+ const sleep = async (ms) => {
+ return new Promise(resolve => setTimeout(resolve, ms));
+ };
+
+ let cancelled = false;
+
+ console.log("Seshat: Started crawler function");
+
+ const client = MatrixClientPeg.get();
+ const platform = PlatformPeg.get();
+
+ handle.cancel = () => {
+ cancelled = true;
+ };
+
+ while (!cancelled) {
+ // This is a low priority task and we don't want to spam our
+ // Homeserver with /messages requests so we set a hefty 3s timeout
+ // here.
+ await sleep(3000);
+
+ if (cancelled) {
+ break;
+ }
+
+ const checkpoint = this.crawlerChekpoints.shift();
+
+ /// There is no checkpoint available currently, one may appear if
+ // a sync with limited room timelines happens, so go back to sleep.
+ if (checkpoint === undefined) {
+ continue;
+ }
+
+ console.log("Seshat: crawling using checkpoint", checkpoint);
+
+ // We have a checkpoint, let us fetch some messages, again, very
+ // conservatively to not bother our Homeserver too much.
+ const eventMapper = client.getEventMapper();
+ // TODO we need to ensure to use member lazy loading with this
+ // request so we get the correct profiles.
+ const res = await client._createMessagesRequest(checkpoint.roomId,
+ checkpoint.token, 100, checkpoint.direction);
+
+ if (res.chunk.length === 0) {
+ // We got to the start/end of our timeline, lets just
+ // delete our checkpoint and go back to sleep.
+ await platform.removeCrawlerCheckpoint(checkpoint);
+ continue;
+ }
+
+ // Convert the plain JSON events into Matrix events so they get
+ // decrypted if necessary.
+ const matrixEvents = res.chunk.map(eventMapper);
+ const stateEvents = res.state.map(eventMapper);
+
+ const profiles = {};
+
+ stateEvents.forEach(ev => {
+ if (ev.event.content &&
+ ev.event.content.membership === "join") {
+ profiles[ev.event.sender] = {
+ displayname: ev.event.content.displayname,
+ avatar_url: ev.event.content.avatar_url,
+ };
+ }
+ });
+
+ const decryptionPromises = [];
+
+ matrixEvents.forEach(ev => {
+ if (ev.isBeingDecrypted() || ev.isDecryptionFailure()) {
+ // TODO the decryption promise is a private property, this
+ // should either be made public or we should convert the
+ // event that gets fired when decryption is done into a
+ // promise using the once event emitter method:
+ // https://nodejs.org/api/events.html#events_events_once_emitter_name
+ decryptionPromises.push(ev._decryptionPromise);
+ }
+ });
+
+ // Let us wait for all the events to get decrypted.
+ await Promise.all(decryptionPromises);
+
+ // We filter out events for which decryption failed, are redacted
+ // or aren't of a type that we know how to index.
+ const isValidEvent = (value) => {
+ return ([
+ "m.room.message",
+ "m.room.name",
+ "m.room.topic",
+ ].indexOf(value.getType()) >= 0
+ && !value.isRedacted() && !value.isDecryptionFailure()
+ );
+ // TODO do we need to check if the event has all the valid
+ // attributes?
+ };
+
+ // TODO if there ar no events at this point we're missing a lot
+ // decryption keys, do we wan't to retry this checkpoint at a later
+ // stage?
+ const filteredEvents = matrixEvents.filter(isValidEvent);
+
+ // Let us convert the events back into a format that Seshat can
+ // consume.
+ const events = filteredEvents.map((ev) => {
+ const jsonEvent = ev.toJSON();
+
+ let e;
+ if (ev.isEncrypted()) e = jsonEvent.decrypted;
+ else e = jsonEvent;
+
+ let profile = {};
+ if (e.sender in profiles) profile = profiles[e.sender];
+ const object = {
+ event: e,
+ profile: profile,
+ };
+ return object;
+ });
+
+ // Create a new checkpoint so we can continue crawling the room for
+ // messages.
+ const newCheckpoint = {
+ roomId: checkpoint.roomId,
+ token: res.end,
+ fullCrawl: checkpoint.fullCrawl,
+ direction: checkpoint.direction,
+ };
+
+ console.log(
+ "Seshat: Crawled room",
+ client.getRoom(checkpoint.roomId).name,
+ "and fetched", events.length, "events.",
+ );
+
+ try {
+ const eventsAlreadyAdded = await platform.addHistoricEvents(
+ events, newCheckpoint, checkpoint);
+ // If all events were already indexed we assume that we catched
+ // up with our index and don't need to crawl the room further.
+ // Let us delete the checkpoint in that case, otherwise push
+ // the new checkpoint to be used by the crawler.
+ if (eventsAlreadyAdded === true && newCheckpoint.fullCrawl !== true) {
+ await platform.removeCrawlerCheckpoint(newCheckpoint);
+ } else {
+ this.crawlerChekpoints.push(newCheckpoint);
+ }
+ } catch (e) {
+ console.log("Seshat: Error durring a crawl", e);
+ // An error occured, put the checkpoint back so we
+ // can retry.
+ this.crawlerChekpoints.push(checkpoint);
+ }
+ }
+
+ console.log("Seshat: Stopping crawler function");
+ },
});
From b23ba5f8811488c16412b6ebe2d141f1b9e18f28 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Damir=20Jeli=C4=87?=
Date: Fri, 11 Oct 2019 16:27:01 +0200
Subject: [PATCH 004/334] MatrixChat: Stop the crawler function and delete the
index when logging out.
---
src/components/structures/MatrixChat.js | 10 +++++++++-
1 file changed, 9 insertions(+), 1 deletion(-)
diff --git a/src/components/structures/MatrixChat.js b/src/components/structures/MatrixChat.js
index 218b7e4d4e..7eda69ad9b 100644
--- a/src/components/structures/MatrixChat.js
+++ b/src/components/structures/MatrixChat.js
@@ -1221,7 +1221,15 @@ export default createReactClass({
/**
* Called when the session is logged out
*/
- _onLoggedOut: function() {
+ _onLoggedOut: async function() {
+ const platform = PlatformPeg.get();
+
+ if (platform.supportsEventIndexing()) {
+ console.log("Seshat: Deleting event index.");
+ this.crawlerRef.cancel();
+ await platform.deleteEventIndex();
+ }
+
this.notifyNewScreen('login');
this.setStateForNewView({
view: VIEWS.LOGIN,
From 5e7076e985fd95a7978099322457823f96daff8e Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Damir=20Jeli=C4=87?=
Date: Fri, 11 Oct 2019 16:28:36 +0200
Subject: [PATCH 005/334] MatrixChat: Add live events to the event index as
well.
---
src/components/structures/MatrixChat.js | 65 +++++++++++++++++++++++++
1 file changed, 65 insertions(+)
diff --git a/src/components/structures/MatrixChat.js b/src/components/structures/MatrixChat.js
index 7eda69ad9b..5c4db4a562 100644
--- a/src/components/structures/MatrixChat.js
+++ b/src/components/structures/MatrixChat.js
@@ -1271,6 +1271,7 @@ export default createReactClass({
this.firstSyncComplete = false;
this.firstSyncPromise = Promise.defer();
this.crawlerChekpoints = [];
+ this.liveEventsForIndex = new Set();
const cli = MatrixClientPeg.get();
const IncomingSasDialog = sdk.getComponent('views.dialogs.IncomingSasDialog');
@@ -1363,6 +1364,14 @@ export default createReactClass({
self.crawlerRef = crawlerHandle;
return;
}
+
+ if (prevState === "SYNCING" && state === "SYNCING") {
+ // A sync was done, presumably we queued up some live events,
+ // commit them now.
+ console.log("Seshat: Committing events");
+ await platform.commitLiveEvents();
+ return;
+ }
});
cli.on('sync', function(state, prevState, data) {
@@ -1447,6 +1456,44 @@ export default createReactClass({
}, null, true);
});
+ cli.on("Room.timeline", async (ev, room, toStartOfTimeline, removed, data) => {
+ const platform = PlatformPeg.get();
+ if (!platform.supportsEventIndexing()) return;
+
+ // We only index encrypted rooms locally.
+ if (!MatrixClientPeg.get().isRoomEncrypted(room.roomId)) return;
+
+ // If it isn't a live event or if it's redacted there's nothing to
+ // do.
+ if (toStartOfTimeline || !data || !data.liveEvent
+ || ev.isRedacted()) {
+ return;
+ }
+
+ // If the event is not yet decrypted mark it for the
+ // Event.decrypted callback.
+ if (ev.isBeingDecrypted()) {
+ const eventId = ev.getId();
+ self.liveEventsForIndex.add(eventId);
+ } else {
+ // If the event is decrypted or is unencrypted add it to the
+ // index now.
+ await self.addLiveEventToIndex(ev);
+ }
+ });
+
+ cli.on("Event.decrypted", async (ev, err) => {
+ const platform = PlatformPeg.get();
+ if (!platform.supportsEventIndexing()) return;
+
+ const eventId = ev.getId();
+
+ // If the event isn't in our live event set, ignore it.
+ if (!self.liveEventsForIndex.delete(eventId)) return;
+ if (err) return;
+ await self.addLiveEventToIndex(ev);
+ });
+
cli.on("accountData", function(ev) {
if (ev.getType() === 'im.vector.web.settings') {
if (ev.getContent() && ev.getContent().theme) {
@@ -2009,6 +2056,24 @@ export default createReactClass({
;
},
+ async addLiveEventToIndex(ev) {
+ const platform = PlatformPeg.get();
+ if (!platform.supportsEventIndexing()) return;
+
+ if (["m.room.message", "m.room.name", "m.room.topic"]
+ .indexOf(ev.getType()) == -1) {
+ return;
+ }
+
+ const e = ev.toJSON().decrypted;
+ const profile = {
+ displayname: ev.sender.rawDisplayName,
+ avatar_url: ev.sender.getMxcAvatarUrl(),
+ };
+
+ platform.addEventToIndex(e, profile);
+ },
+
async crawlerFunc(handle) {
// TODO either put this in a better place or find a library provided
// method that does this.
From 4acec19d40ba57f789f4c1293ebeb6774babc6ae Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Damir=20Jeli=C4=87?=
Date: Fri, 11 Oct 2019 16:32:55 +0200
Subject: [PATCH 006/334] MatrixChat: Add new crawler checkpoints if there was
a limited timeline.
A sync call may not have all events that happened since the last time
the client synced. In such a case the room is marked as limited and
events need to be fetched separately.
When such a sync call happens our event index will have a gap. To
close the gap checkpoints are added to start crawling our room again.
Unnecessary full re-crawls are prevented by checking if our current
/room/roomId/messages request contains only events that were already
present in our event index.
---
src/components/structures/MatrixChat.js | 42 ++++++++++++++++++++++++-
1 file changed, 41 insertions(+), 1 deletion(-)
diff --git a/src/components/structures/MatrixChat.js b/src/components/structures/MatrixChat.js
index 5c4db4a562..d423bbd592 100644
--- a/src/components/structures/MatrixChat.js
+++ b/src/components/structures/MatrixChat.js
@@ -1281,8 +1281,11 @@ export default createReactClass({
// particularly noticeable when there are lots of 'limited' /sync responses
// such as when laptops unsleep.
// https://github.com/vector-im/riot-web/issues/3307#issuecomment-282895568
- cli.setCanResetTimelineCallback(function(roomId) {
+ cli.setCanResetTimelineCallback(async function(roomId) {
console.log("Request to reset timeline in room ", roomId, " viewing:", self.state.currentRoomId);
+ // TODO is there a better place to plug this in
+ await self.addCheckpointForLimitedRoom(roomId);
+
if (roomId !== self.state.currentRoomId) {
// It is safe to remove events from rooms we are not viewing.
return true;
@@ -2234,4 +2237,41 @@ export default createReactClass({
console.log("Seshat: Stopping crawler function");
},
+
+ async addCheckpointForLimitedRoom(roomId) {
+ const platform = PlatformPeg.get();
+ if (!platform.supportsEventIndexing()) return;
+ if (!MatrixClientPeg.get().isRoomEncrypted(roomId)) return;
+
+ const client = MatrixClientPeg.get();
+ const room = client.getRoom(roomId);
+
+ if (room === null) return;
+
+ const timeline = room.getLiveTimeline();
+ const token = timeline.getPaginationToken("b");
+
+ const backwardsCheckpoint = {
+ roomId: room.roomId,
+ token: token,
+ fullCrawl: false,
+ direction: "b",
+ };
+
+ const forwardsCheckpoint = {
+ roomId: room.roomId,
+ token: token,
+ fullCrawl: false,
+ direction: "f",
+ };
+
+ console.log("Seshat: Added checkpoint because of a limited timeline",
+ backwardsCheckpoint, forwardsCheckpoint);
+
+ await platform.addCrawlerCheckpoint(backwardsCheckpoint);
+ await platform.addCrawlerCheckpoint(forwardsCheckpoint);
+
+ this.crawlerChekpoints.push(backwardsCheckpoint);
+ this.crawlerChekpoints.push(forwardsCheckpoint);
+ },
});
From 3f5369183404be057af45fa248556572804727b6 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Damir=20Jeli=C4=87?=
Date: Fri, 11 Oct 2019 16:40:10 +0200
Subject: [PATCH 007/334] RoomView: Use platform specific search if our
platform supports it.
This patch extends our search to include our platform specific event
index.
There are 3 search scenarios and are handled differently when platform
support for indexing is present:
- Search a single non-encrypted room: Use the server-side search
like before.
- Search a single encrypted room: Search using our platform specific
event index.
- Search across all rooms: Search encrypted rooms using our local
event index. Search non-encrypted rooms using the classic
server-side search. Combine the results.
The combined search will result in having twice the amount of search
results since comparing the scores fairly wasn't deemed sensible.
---
src/components/structures/RoomView.js | 115 ++++++++++++++++++++++++--
1 file changed, 110 insertions(+), 5 deletions(-)
diff --git a/src/components/structures/RoomView.js b/src/components/structures/RoomView.js
index 4de573479d..1b44335f51 100644
--- a/src/components/structures/RoomView.js
+++ b/src/components/structures/RoomView.js
@@ -34,6 +34,7 @@ import { _t } from '../../languageHandler';
import {RoomPermalinkCreator} from '../../utils/permalinks/Permalinks';
import MatrixClientPeg from '../../MatrixClientPeg';
+import PlatformPeg from "../../PlatformPeg";
import ContentMessages from '../../ContentMessages';
import Modal from '../../Modal';
import sdk from '../../index';
@@ -1140,12 +1141,116 @@ module.exports = createReactClass({
}
debuglog("sending search request");
+ const platform = PlatformPeg.get();
- const searchPromise = MatrixClientPeg.get().searchRoomEvents({
- filter: filter,
- term: term,
- });
- this._handleSearchResult(searchPromise).done();
+ if (platform.supportsEventIndexing()) {
+ const combinedSearchFunc = async (searchTerm) => {
+ // Create two promises, one for the local search, one for the
+ // server-side search.
+ const client = MatrixClientPeg.get();
+ const serverSidePromise = client.searchRoomEvents({
+ term: searchTerm,
+ });
+ const localPromise = localSearchFunc(searchTerm);
+
+ // Wait for both promises to resolve.
+ await Promise.all([serverSidePromise, localPromise]);
+
+ // Get both search results.
+ const localResult = await localPromise;
+ const serverSideResult = await serverSidePromise;
+
+ // Combine the search results into one result.
+ const result = {};
+
+ // Our localResult and serverSideResult are both ordered by
+ // recency separetly, when we combine them the order might not
+ // be the right one so we need to sort them.
+ const compare = (a, b) => {
+ const aEvent = a.context.getEvent().event;
+ const bEvent = b.context.getEvent().event;
+
+ if (aEvent.origin_server_ts >
+ bEvent.origin_server_ts) return -1;
+ if (aEvent.origin_server_ts <
+ bEvent.origin_server_ts) return 1;
+ return 0;
+ };
+
+ result.count = localResult.count + serverSideResult.count;
+ result.results = localResult.results.concat(
+ serverSideResult.results).sort(compare);
+ result.highlights = localResult.highlights.concat(
+ serverSideResult.highlights);
+
+ return result;
+ };
+
+ const localSearchFunc = async (searchTerm, roomId = undefined) => {
+ const searchArgs = {
+ search_term: searchTerm,
+ before_limit: 1,
+ after_limit: 1,
+ order_by_recency: true,
+ };
+
+ if (roomId !== undefined) {
+ searchArgs.room_id = roomId;
+ }
+
+ const localResult = await platform.searchEventIndex(
+ searchArgs);
+
+ const response = {
+ search_categories: {
+ room_events: localResult,
+ },
+ };
+
+ const emptyResult = {
+ results: [],
+ highlights: [],
+ };
+
+ // TODO is there a better way to convert our result into what
+ // is expected by the handler method.
+ const result = MatrixClientPeg.get()._processRoomEventsSearch(
+ emptyResult, response);
+
+ return result;
+ };
+
+ let searchPromise;
+
+ if (scope === "Room") {
+ const roomId = this.state.room.roomId;
+
+ if (MatrixClientPeg.get().isRoomEncrypted(roomId)) {
+ // The search is for a single encrypted room, use our local
+ // search method.
+ searchPromise = localSearchFunc(term, roomId);
+ } else {
+ // The search is for a single non-encrypted room, use the
+ // server-side search.
+ searchPromise = MatrixClientPeg.get().searchRoomEvents({
+ filter: filter,
+ term: term,
+ });
+ }
+ } else {
+ // Search across all rooms, combine a server side search and a
+ // local search.
+ searchPromise = combinedSearchFunc(term);
+ }
+
+ this._handleSearchResult(searchPromise).done();
+ } else {
+ const searchPromise = MatrixClientPeg.get().searchRoomEvents({
+ filter: filter,
+ term: term,
+ });
+ this._handleSearchResult(searchPromise).done();
+ }
},
_handleSearchResult: function(searchPromise) {
From 1b63886a6baca1a4191f83992609e58e5e6dc43c Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Damir=20Jeli=C4=87?=
Date: Fri, 18 Oct 2019 16:31:39 +0200
Subject: [PATCH 008/334] MatrixChat: Add more detailed logging to the event
crawler.
---
src/components/structures/MatrixChat.js | 5 +++++
1 file changed, 5 insertions(+)
diff --git a/src/components/structures/MatrixChat.js b/src/components/structures/MatrixChat.js
index d423bbd592..3558cda586 100644
--- a/src/components/structures/MatrixChat.js
+++ b/src/components/structures/MatrixChat.js
@@ -2101,7 +2101,10 @@ export default createReactClass({
// here.
await sleep(3000);
+ console.log("Seshat: Running the crawler loop.");
+
if (cancelled) {
+ console.log("Seshat: Cancelling the crawler.");
break;
}
@@ -2124,6 +2127,7 @@ export default createReactClass({
checkpoint.token, 100, checkpoint.direction);
if (res.chunk.length === 0) {
+ console.log("Seshat: Done with the checkpoint", checkpoint)
// We got to the start/end of our timeline, lets just
// delete our checkpoint and go back to sleep.
await platform.removeCrawlerCheckpoint(checkpoint);
@@ -2223,6 +2227,7 @@ export default createReactClass({
// Let us delete the checkpoint in that case, otherwise push
// the new checkpoint to be used by the crawler.
if (eventsAlreadyAdded === true && newCheckpoint.fullCrawl !== true) {
+ console.log("Seshat: Checkpoint had already all events added, stopping the crawl", checkpoint);
await platform.removeCrawlerCheckpoint(newCheckpoint);
} else {
this.crawlerChekpoints.push(newCheckpoint);
From 89f14e55a2bb31959893f138813957acd957e032 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Damir=20Jeli=C4=87?=
Date: Fri, 18 Oct 2019 16:32:43 +0200
Subject: [PATCH 009/334] MatrixChat: Catch errors when fetching room messages
in the crawler.
---
src/components/structures/MatrixChat.js | 13 +++++++++++--
1 file changed, 11 insertions(+), 2 deletions(-)
diff --git a/src/components/structures/MatrixChat.js b/src/components/structures/MatrixChat.js
index 3558cda586..2f9e64efa9 100644
--- a/src/components/structures/MatrixChat.js
+++ b/src/components/structures/MatrixChat.js
@@ -2123,8 +2123,17 @@ export default createReactClass({
const eventMapper = client.getEventMapper();
// TODO we need to ensure to use member lazy loading with this
// request so we get the correct profiles.
- const res = await client._createMessagesRequest(checkpoint.roomId,
- checkpoint.token, 100, checkpoint.direction);
+ let res;
+
+ try {
+ res = await client._createMessagesRequest(
+ checkpoint.roomId, checkpoint.token, 100,
+ checkpoint.direction);
+ } catch (e) {
+ console.log("Seshat: Error crawling events:", e)
+ this.crawlerChekpoints.push(checkpoint);
+ continue
+ }
if (res.chunk.length === 0) {
console.log("Seshat: Done with the checkpoint", checkpoint)
From 64061173e19507ce40241989a1fb55ac705cd648 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Damir=20Jeli=C4=87?=
Date: Fri, 18 Oct 2019 16:33:07 +0200
Subject: [PATCH 010/334] MatrixChat: Check if our state array is empty in the
crawled messages response.
---
src/components/structures/MatrixChat.js | 5 ++++-
1 file changed, 4 insertions(+), 1 deletion(-)
diff --git a/src/components/structures/MatrixChat.js b/src/components/structures/MatrixChat.js
index 2f9e64efa9..51cf92da5f 100644
--- a/src/components/structures/MatrixChat.js
+++ b/src/components/structures/MatrixChat.js
@@ -2146,7 +2146,10 @@ export default createReactClass({
// Convert the plain JSON events into Matrix events so they get
// decrypted if necessary.
const matrixEvents = res.chunk.map(eventMapper);
- const stateEvents = res.state.map(eventMapper);
+ let stateEvents = [];
+ if (res.state !== undefined) {
+ stateEvents = res.state.map(eventMapper);
+ }
const profiles = {};
From 23383419e803f6916c6636de10865b386a240f73 Mon Sep 17 00:00:00 2001
From: Travis Ralston
Date: Thu, 31 Oct 2019 13:19:54 -0600
Subject: [PATCH 011/334] Add settings base for Mjolnir rules
---
.../views/dialogs/_UserSettingsDialog.scss | 4 +
.../views/dialogs/UserSettingsDialog.js | 30 ++++++++
.../tabs/user/MjolnirUserSettingsTab.js | 74 +++++++++++++++++++
src/i18n/strings/en_EN.json | 11 ++-
src/settings/Settings.js | 14 ++++
5 files changed, 132 insertions(+), 1 deletion(-)
create mode 100644 src/components/views/settings/tabs/user/MjolnirUserSettingsTab.js
diff --git a/res/css/views/dialogs/_UserSettingsDialog.scss b/res/css/views/dialogs/_UserSettingsDialog.scss
index 2a046ff501..4d831d7858 100644
--- a/res/css/views/dialogs/_UserSettingsDialog.scss
+++ b/res/css/views/dialogs/_UserSettingsDialog.scss
@@ -45,6 +45,10 @@ limitations under the License.
mask-image: url('$(res)/img/feather-customised/flag.svg');
}
+.mx_UserSettingsDialog_mjolnirIcon::before {
+ mask-image: url('$(res)/img/feather-customised/face.svg');
+}
+
.mx_UserSettingsDialog_flairIcon::before {
mask-image: url('$(res)/img/feather-customised/flair.svg');
}
diff --git a/src/components/views/dialogs/UserSettingsDialog.js b/src/components/views/dialogs/UserSettingsDialog.js
index fb9045f05a..6e324ad3fb 100644
--- a/src/components/views/dialogs/UserSettingsDialog.js
+++ b/src/components/views/dialogs/UserSettingsDialog.js
@@ -1,5 +1,6 @@
/*
Copyright 2019 New Vector Ltd
+Copyright 2019 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.
@@ -29,12 +30,34 @@ import HelpUserSettingsTab from "../settings/tabs/user/HelpUserSettingsTab";
import FlairUserSettingsTab from "../settings/tabs/user/FlairUserSettingsTab";
import sdk from "../../../index";
import SdkConfig from "../../../SdkConfig";
+import MjolnirUserSettingsTab from "../settings/tabs/user/MjolnirUserSettingsTab";
export default class UserSettingsDialog extends React.Component {
static propTypes = {
onFinished: PropTypes.func.isRequired,
};
+ constructor() {
+ super();
+
+ this.state = {
+ mjolnirEnabled: SettingsStore.isFeatureEnabled("feature_mjolnir"),
+ }
+ }
+
+ componentDidMount(): void {
+ this._mjolnirWatcher = SettingsStore.watchSetting("feature_mjolnir", null, this._mjolnirChanged.bind(this));
+ }
+
+ componentWillUnmount(): void {
+ SettingsStore.unwatchSetting(this._mjolnirWatcher);
+ }
+
+ _mjolnirChanged(settingName, roomId, atLevel, newValue) {
+ // We can cheat because we know what levels a feature is tracked at, and how it is tracked
+ this.setState({mjolnirEnabled: newValue});
+ }
+
_getTabs() {
const tabs = [];
@@ -75,6 +98,13 @@ export default class UserSettingsDialog extends React.Component {
,
));
}
+ if (this.state.mjolnirEnabled) {
+ tabs.push(new Tab(
+ _td("Ignored users"),
+ "mx_UserSettingsDialog_mjolnirIcon",
+ ,
+ ));
+ }
tabs.push(new Tab(
_td("Help & About"),
"mx_UserSettingsDialog_helpIcon",
diff --git a/src/components/views/settings/tabs/user/MjolnirUserSettingsTab.js b/src/components/views/settings/tabs/user/MjolnirUserSettingsTab.js
new file mode 100644
index 0000000000..02e64c0bc1
--- /dev/null
+++ b/src/components/views/settings/tabs/user/MjolnirUserSettingsTab.js
@@ -0,0 +1,74 @@
+/*
+Copyright 2019 The Matrix.org Foundation C.I.C.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+import React from 'react';
+import {_t} from "../../../../../languageHandler";
+const sdk = require("../../../../..");
+
+export default class MjolnirUserSettingsTab extends React.Component {
+ constructor() {
+ super();
+ }
+
+ render() {
+ return (
+
+
{_t("Ignored users")}
+
+
+ {_t("⚠ These settings are meant for advanced users.")}
+
+ {_t(
+ "Add users and servers you want to ignore here. Use asterisks " +
+ "to have Riot match any characters. For example, @bot:* " +
+ "would ignore all users that have the name 'bot' on any server.",
+ {}, {code: (s) => {s}},
+ )}
+
+ {_t(
+ "Ignoring people is done through ban lists which contain rules for " +
+ "who to ban. Subscribing to a ban list means the users/servers blocked by " +
+ "that list will be hidden from you."
+ )}
+
+
+
+ {_t("Personal ban list")}
+
+ {_t(
+ "Your personal ban list holds all the users/servers you personally don't " +
+ "want to see messages from. After ignoring your first user/server, a new room " +
+ "will show up in your room list named 'My Ban List' - stay in this room to keep " +
+ "the ban list in effect.",
+ )}
+
+
TODO
+
+
+ {_t("Subscribed lists")}
+
+ {_t("Subscribing to a ban list will cause you to join it!")}
+
+ {_t(
+ "If this isn't what you want, please use a different tool to ignore users.",
+ )}
+
+
TODO
+
+
+ );
+ }
+}
diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json
index 524a8a1abf..e909f49159 100644
--- a/src/i18n/strings/en_EN.json
+++ b/src/i18n/strings/en_EN.json
@@ -335,6 +335,7 @@
"Render simple counters in room header": "Render simple counters in room header",
"Multiple integration managers": "Multiple integration managers",
"Use the new, consistent UserInfo panel for Room Members and Group Members": "Use the new, consistent UserInfo panel for Room Members and Group Members",
+ "Try out new ways to ignore people (experimental)": "Try out new ways to ignore people (experimental)",
"Use the new, faster, composer for writing messages": "Use the new, faster, composer for writing messages",
"Enable Emoji suggestions while typing": "Enable Emoji suggestions while typing",
"Use compact timeline layout": "Use compact timeline layout",
@@ -637,6 +638,15 @@
"Access Token:": "Access Token:",
"click to reveal": "click to reveal",
"Labs": "Labs",
+ "Ignored users": "Ignored users",
+ "⚠ These settings are meant for advanced users.": "⚠ These settings are meant for advanced users.",
+ "Add users and servers you want to ignore here. Use asterisks to have Riot match any characters. For example, @bot:* would ignore all users that have the name 'bot' on any server.": "Add users and servers you want to ignore here. Use asterisks to have Riot match any characters. For example, @bot:* would ignore all users that have the name 'bot' on any server.",
+ "Ignoring people is done through ban lists which contain rules for who to ban. Subscribing to a ban list means the users/servers blocked by that list will be hidden from you.": "Ignoring people is done through ban lists which contain rules for who to ban. Subscribing to a ban list means the users/servers blocked by that list will be hidden from you.",
+ "Personal ban list": "Personal ban list",
+ "Your personal ban list holds all the users/servers you personally don't want to see messages from. After ignoring your first user/server, a new room will show up in your room list named 'My Ban List' - stay in this room to keep the ban list in effect.": "Your personal ban list holds all the users/servers you personally don't want to see messages from. After ignoring your first user/server, a new room will show up in your room list named 'My Ban List' - stay in this room to keep the ban list in effect.",
+ "Subscribed lists": "Subscribed lists",
+ "Subscribing to a ban list will cause you to join it!": "Subscribing to a ban list will cause you to join it!",
+ "If this isn't what you want, please use a different tool to ignore users.": "If this isn't what you want, please use a different tool to ignore users.",
"Notifications": "Notifications",
"Start automatically after system login": "Start automatically after system login",
"Always show the window menu bar": "Always show the window menu bar",
@@ -654,7 +664,6 @@
"Cryptography": "Cryptography",
"Device ID:": "Device ID:",
"Device key:": "Device key:",
- "Ignored users": "Ignored users",
"Bulk options": "Bulk options",
"Accept all %(invitedRooms)s invites": "Accept all %(invitedRooms)s invites",
"Reject all %(invitedRooms)s invites": "Reject all %(invitedRooms)s invites",
diff --git a/src/settings/Settings.js b/src/settings/Settings.js
index 7470641359..1cfff0182e 100644
--- a/src/settings/Settings.js
+++ b/src/settings/Settings.js
@@ -126,6 +126,20 @@ export const SETTINGS = {
supportedLevels: LEVELS_FEATURE,
default: false,
},
+ "feature_mjolnir": {
+ isFeature: true,
+ displayName: _td("Try out new ways to ignore people (experimental)"),
+ supportedLevels: LEVELS_FEATURE,
+ default: false,
+ },
+ "mjolnirRooms": {
+ supportedLevels: ['account'],
+ default: [],
+ },
+ "mjolnirPersonalRoom": {
+ supportedLevels: ['account'],
+ default: null,
+ },
"useCiderComposer": {
displayName: _td("Use the new, faster, composer for writing messages"),
supportedLevels: LEVELS_ACCOUNT_SETTINGS,
From e6e12df82d1e801019f3ea993b35ae0b2b61f04c Mon Sep 17 00:00:00 2001
From: Travis Ralston
Date: Thu, 31 Oct 2019 13:20:08 -0600
Subject: [PATCH 012/334] Add structural base for handling Mjolnir lists
---
package.json | 1 +
src/i18n/strings/en_EN.json | 2 +
src/mjolnir/BanList.js | 98 +++++++++++++++++++++++++++++
src/mjolnir/ListRule.js | 63 +++++++++++++++++++
src/mjolnir/Mjolnir.js | 122 ++++++++++++++++++++++++++++++++++++
src/utils/MatrixGlob.js | 54 ++++++++++++++++
yarn.lock | 5 ++
7 files changed, 345 insertions(+)
create mode 100644 src/mjolnir/BanList.js
create mode 100644 src/mjolnir/ListRule.js
create mode 100644 src/mjolnir/Mjolnir.js
create mode 100644 src/utils/MatrixGlob.js
diff --git a/package.json b/package.json
index e709662020..745f82d7bc 100644
--- a/package.json
+++ b/package.json
@@ -81,6 +81,7 @@
"gemini-scrollbar": "github:matrix-org/gemini-scrollbar#91e1e566",
"gfm.css": "^1.1.1",
"glob": "^5.0.14",
+ "glob-to-regexp": "^0.4.1",
"highlight.js": "^9.15.8",
"is-ip": "^2.0.0",
"isomorphic-fetch": "^2.2.1",
diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json
index e909f49159..770f4723ef 100644
--- a/src/i18n/strings/en_EN.json
+++ b/src/i18n/strings/en_EN.json
@@ -389,6 +389,8 @@
"Call invitation": "Call invitation",
"Messages sent by bot": "Messages sent by bot",
"When rooms are upgraded": "When rooms are upgraded",
+ "My Ban List": "My Ban List",
+ "This is your list of users/servers you have blocked - don't leave the room!": "This is your list of users/servers you have blocked - don't leave the room!",
"Active call (%(roomName)s)": "Active call (%(roomName)s)",
"unknown caller": "unknown caller",
"Incoming voice call from %(name)s": "Incoming voice call from %(name)s",
diff --git a/src/mjolnir/BanList.js b/src/mjolnir/BanList.js
new file mode 100644
index 0000000000..6ebc0a7e36
--- /dev/null
+++ b/src/mjolnir/BanList.js
@@ -0,0 +1,98 @@
+/*
+Copyright 2019 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.
+*/
+
+// Inspiration largely taken from Mjolnir itself
+
+import {ListRule, RECOMMENDATION_BAN, recommendationToStable} from "./ListRule";
+import MatrixClientPeg from "../MatrixClientPeg";
+
+export const RULE_USER = "m.room.rule.user";
+export const RULE_ROOM = "m.room.rule.room";
+export const RULE_SERVER = "m.room.rule.server";
+
+export const USER_RULE_TYPES = [RULE_USER, "org.matrix.mjolnir.rule.user"];
+export const ROOM_RULE_TYPES = [RULE_ROOM, "org.matrix.mjolnir.rule.room"];
+export const SERVER_RULE_TYPES = [RULE_SERVER, "org.matrix.mjolnir.rule.server"];
+export const ALL_RULE_TYPES = [...USER_RULE_TYPES, ...ROOM_RULE_TYPES, ...SERVER_RULE_TYPES];
+
+export function ruleTypeToStable(rule: string, unstable = true): string {
+ if (USER_RULE_TYPES.includes(rule)) return unstable ? USER_RULE_TYPES[USER_RULE_TYPES.length - 1] : RULE_USER;
+ if (ROOM_RULE_TYPES.includes(rule)) return unstable ? ROOM_RULE_TYPES[ROOM_RULE_TYPES.length - 1] : RULE_ROOM;
+ if (SERVER_RULE_TYPES.includes(rule)) return unstable ? SERVER_RULE_TYPES[SERVER_RULE_TYPES.length - 1] : RULE_SERVER;
+ return null;
+}
+
+export class BanList {
+ _rules: ListRule[] = [];
+ _roomId: string;
+
+ constructor(roomId: string) {
+ this._roomId = roomId;
+ this.updateList();
+ }
+
+ get roomId(): string {
+ return this._roomId;
+ }
+
+ get serverRules(): ListRule[] {
+ return this._rules.filter(r => r.kind === RULE_SERVER);
+ }
+
+ get userRules(): ListRule[] {
+ return this._rules.filter(r => r.kind === RULE_USER);
+ }
+
+ get roomRules(): ListRule[] {
+ return this._rules.filter(r => r.kind === RULE_ROOM);
+ }
+
+ banEntity(kind: string, entity: string, reason: string): Promise {
+ return MatrixClientPeg.get().sendStateEvent(this._roomId, ruleTypeToStable(kind, true), {
+ entity: entity,
+ reason: reason,
+ recommendation: recommendationToStable(RECOMMENDATION_BAN, true),
+ }, "rule:" + entity);
+ }
+
+ unbanEntity(kind: string, entity: string): Promise {
+ // Empty state event is effectively deleting it.
+ return MatrixClientPeg.get().sendStateEvent(this._roomId, ruleTypeToStable(kind, true), {}, "rule:" + entity);
+ }
+
+ updateList() {
+ this._rules = [];
+
+ const room = MatrixClientPeg.get().getRoom(this._roomId);
+ if (!room) return;
+
+ for (const eventType of ALL_RULE_TYPES) {
+ const events = room.currentState.getStateEvents(eventType, undefined);
+ for (const ev of events) {
+ if (!ev['state_key']) continue;
+
+ const kind = ruleTypeToStable(eventType, false);
+
+ const entity = ev.getContent()['entity'];
+ const recommendation = ev.getContent()['recommendation'];
+ const reason = ev.getContent()['reason'];
+ if (!entity || !recommendation || !reason) continue;
+
+ this._rules.push(new ListRule(entity, recommendation, reason, kind));
+ }
+ }
+ }
+}
diff --git a/src/mjolnir/ListRule.js b/src/mjolnir/ListRule.js
new file mode 100644
index 0000000000..d33248d24c
--- /dev/null
+++ b/src/mjolnir/ListRule.js
@@ -0,0 +1,63 @@
+/*
+Copyright 2019 The Matrix.org Foundation C.I.C.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+import {MatrixGlob} from "../utils/MatrixGlob";
+
+// Inspiration largely taken from Mjolnir itself
+
+export const RECOMMENDATION_BAN = "m.ban";
+export const RECOMMENDATION_BAN_TYPES = [RECOMMENDATION_BAN, "org.matrix.mjolnir.ban"];
+
+export function recommendationToStable(recommendation: string, unstable = true): string {
+ if (RECOMMENDATION_BAN_TYPES.includes(recommendation)) return unstable ? RECOMMENDATION_BAN_TYPES[RECOMMENDATION_BAN_TYPES.length - 1] : RECOMMENDATION_BAN;
+ return null;
+}
+
+export class ListRule {
+ _glob: MatrixGlob;
+ _entity: string;
+ _action: string;
+ _reason: string;
+ _kind: string;
+
+ constructor(entity: string, action: string, reason: string, kind: string) {
+ this._glob = new MatrixGlob(entity);
+ this._entity = entity;
+ this._action = recommendationToStable(action, false);
+ this._reason = reason;
+ this._kind = kind;
+ }
+
+ get entity(): string {
+ return this._entity;
+ }
+
+ get reason(): string {
+ return this._reason;
+ }
+
+ get kind(): string {
+ return this._kind;
+ }
+
+ get recommendation(): string {
+ return this._action;
+ }
+
+ isMatch(entity: string): boolean {
+ return this._glob.test(entity);
+ }
+}
diff --git a/src/mjolnir/Mjolnir.js b/src/mjolnir/Mjolnir.js
new file mode 100644
index 0000000000..a12534592d
--- /dev/null
+++ b/src/mjolnir/Mjolnir.js
@@ -0,0 +1,122 @@
+/*
+Copyright 2019 The Matrix.org Foundation C.I.C.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+import MatrixClientPeg from "../MatrixClientPeg";
+import {ALL_RULE_TYPES, BanList} from "./BanList";
+import SettingsStore, {SettingLevel} from "../settings/SettingsStore";
+import {_t} from "../languageHandler";
+
+// TODO: Move this and related files to the js-sdk or something once finalized.
+
+export class Mjolnir {
+ static _instance: Mjolnir = null;
+
+ _lists: BanList[] = [];
+ _roomIds: string[] = [];
+ _mjolnirWatchRef = null;
+
+ constructor() {
+ }
+
+ start() {
+ this._updateLists(SettingsStore.getValue("mjolnirRooms"));
+ this._mjolnirWatchRef = SettingsStore.watchSetting("mjolnirRooms", null, this._onListsChanged.bind(this));
+
+ MatrixClientPeg.get().on("RoomState.events", this._onEvent.bind(this));
+ }
+
+ stop() {
+ SettingsStore.unwatchSetting(this._mjolnirWatchRef);
+ MatrixClientPeg.get().removeListener("RoomState.events", this._onEvent.bind(this));
+ }
+
+ async getOrCreatePersonalList(): Promise {
+ let personalRoomId = SettingsStore.getValue("mjolnirPersonalRoom");
+ if (!personalRoomId) {
+ const resp = await MatrixClientPeg.get().createRoom({
+ name: _t("My Ban List"),
+ topic: _t("This is your list of users/servers you have blocked - don't leave the room!"),
+ preset: "private_chat"
+ });
+ personalRoomId = resp['room_id'];
+ SettingsStore.setValue("mjolnirPersonalRoom", null, SettingLevel.ACCOUNT, personalRoomId);
+ SettingsStore.setValue("mjolnirRooms", null, SettingLevel.ACCOUNT, [personalRoomId, ...this._roomIds]);
+ }
+ if (!personalRoomId) {
+ throw new Error("Error finding a room ID to use");
+ }
+
+ let list = this._lists.find(b => b.roomId === personalRoomId);
+ if (!list) list = new BanList(personalRoomId);
+ // we don't append the list to the tracked rooms because it should already be there.
+ // we're just trying to get the caller some utility access to the list
+
+ return list;
+ }
+
+ _onEvent(event) {
+ if (!this._roomIds.includes(event.getRoomId())) return;
+ if (!ALL_RULE_TYPES.includes(event.getType())) return;
+
+ this._updateLists(this._roomIds);
+ }
+
+ _onListsChanged(settingName, roomId, atLevel, newValue) {
+ // We know that ban lists are only recorded at one level so we don't need to re-eval them
+ this._updateLists(newValue);
+ }
+
+ _updateLists(listRoomIds: string[]) {
+ this._lists = [];
+ this._roomIds = listRoomIds || [];
+ if (!listRoomIds) return;
+
+ for (const roomId of listRoomIds) {
+ // Creating the list updates it
+ this._lists.push(new BanList(roomId));
+ }
+ }
+
+ isServerBanned(serverName: string): boolean {
+ for (const list of this._lists) {
+ for (const rule of list.serverRules) {
+ if (rule.isMatch(serverName)) {
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+
+ isUserBanned(userId: string): boolean {
+ for (const list of this._lists) {
+ for (const rule of list.userRules) {
+ if (rule.isMatch(userId)) {
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+
+ static sharedInstance(): Mjolnir {
+ if (!Mjolnir._instance) {
+ Mjolnir._instance = new Mjolnir();
+ }
+ return Mjolnir._instance;
+ }
+}
+
diff --git a/src/utils/MatrixGlob.js b/src/utils/MatrixGlob.js
new file mode 100644
index 0000000000..cf55040625
--- /dev/null
+++ b/src/utils/MatrixGlob.js
@@ -0,0 +1,54 @@
+/*
+Copyright 2019 The Matrix.org Foundation C.I.C.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+import * as globToRegexp from "glob-to-regexp";
+
+// Taken with permission from matrix-bot-sdk:
+// https://github.com/turt2live/matrix-js-bot-sdk/blob/eb148c2ecec7bf3ade801d73deb43df042d55aef/src/MatrixGlob.ts
+
+/**
+ * Represents a common Matrix glob. This is commonly used
+ * for server ACLs and similar functions.
+ */
+export class MatrixGlob {
+ _regex: RegExp;
+
+ /**
+ * Creates a new Matrix Glob
+ * @param {string} glob The glob to convert. Eg: "*.example.org"
+ */
+ constructor(glob: string) {
+ const globRegex = globToRegexp(glob, {
+ extended: false,
+ globstar: false,
+ });
+
+ // We need to convert `?` manually because globToRegexp's extended mode
+ // does more than we want it to.
+ const replaced = globRegex.toString().replace(/\\\?/g, ".");
+ this._regex = new RegExp(replaced.substring(1, replaced.length - 1));
+ }
+
+ /**
+ * Tests the glob against a value, returning true if it matches.
+ * @param {string} val The value to test.
+ * @returns {boolean} True if the value matches the glob, false otherwise.
+ */
+ test(val: string): boolean {
+ return this._regex.test(val);
+ }
+
+}
diff --git a/yarn.lock b/yarn.lock
index aa0a06e588..a2effb975c 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -3674,6 +3674,11 @@ glob-to-regexp@^0.3.0:
resolved "https://registry.yarnpkg.com/glob-to-regexp/-/glob-to-regexp-0.3.0.tgz#8c5a1494d2066c570cc3bfe4496175acc4d502ab"
integrity sha1-jFoUlNIGbFcMw7/kSWF1rMTVAqs=
+glob-to-regexp@^0.4.1:
+ version "0.4.1"
+ resolved "https://registry.yarnpkg.com/glob-to-regexp/-/glob-to-regexp-0.4.1.tgz#c75297087c851b9a578bd217dd59a92f59fe546e"
+ integrity sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw==
+
glob@7.1.2:
version "7.1.2"
resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.2.tgz#c19c9df9a028702d678612384a6552404c636d15"
From e9c8a31e1f07031e1b315020d48bb97434f40f41 Mon Sep 17 00:00:00 2001
From: Travis Ralston
Date: Thu, 31 Oct 2019 13:28:00 -0600
Subject: [PATCH 013/334] Start and stop Mjolnir with the lifecycle
---
src/Lifecycle.js | 7 +++++++
1 file changed, 7 insertions(+)
diff --git a/src/Lifecycle.js b/src/Lifecycle.js
index 13f3abccb1..f2b50d7f2d 100644
--- a/src/Lifecycle.js
+++ b/src/Lifecycle.js
@@ -36,6 +36,7 @@ import * as StorageManager from './utils/StorageManager';
import SettingsStore from "./settings/SettingsStore";
import TypingStore from "./stores/TypingStore";
import {IntegrationManagers} from "./integrations/IntegrationManagers";
+import {Mjolnir} from "./mjolnir/Mjolnir";
/**
* Called at startup, to attempt to build a logged-in Matrix session. It tries
@@ -585,6 +586,11 @@ async function startMatrixClient(startSyncing=true) {
IntegrationManagers.sharedInstance().startWatching();
ActiveWidgetStore.start();
+ // Start Mjolnir even though we haven't checked the feature flag yet. Starting
+ // the thing just wastes CPU cycles, but should result in no actual functionality
+ // being exposed to the user.
+ Mjolnir.sharedInstance().start();
+
if (startSyncing) {
await MatrixClientPeg.start();
} else {
@@ -645,6 +651,7 @@ export function stopMatrixClient(unsetClient=true) {
Presence.stop();
ActiveWidgetStore.stop();
IntegrationManagers.sharedInstance().stopWatching();
+ Mjolnir.sharedInstance().stop();
if (DMRoomMap.shared()) DMRoomMap.shared().stop();
const cli = MatrixClientPeg.get();
if (cli) {
From b93508728a1e4abd3dd8fa411eb6760119bf6f7d Mon Sep 17 00:00:00 2001
From: Travis Ralston
Date: Thu, 31 Oct 2019 14:24:51 -0600
Subject: [PATCH 014/334] Add personal list management to Mjolnir section
---
res/css/_components.scss | 1 +
.../tabs/user/_MjolnirUserSettingsTab.scss | 23 ++++
.../tabs/user/MjolnirUserSettingsTab.js | 117 +++++++++++++++++-
src/i18n/strings/en_EN.json | 11 +-
src/mjolnir/BanList.js | 16 ++-
src/mjolnir/Mjolnir.js | 44 ++++++-
src/utils/MatrixGlob.js | 2 +-
7 files changed, 197 insertions(+), 17 deletions(-)
create mode 100644 res/css/views/settings/tabs/user/_MjolnirUserSettingsTab.scss
diff --git a/res/css/_components.scss b/res/css/_components.scss
index 29c4d2c84c..a0e5881201 100644
--- a/res/css/_components.scss
+++ b/res/css/_components.scss
@@ -182,6 +182,7 @@
@import "./views/settings/tabs/room/_SecurityRoomSettingsTab.scss";
@import "./views/settings/tabs/user/_GeneralUserSettingsTab.scss";
@import "./views/settings/tabs/user/_HelpUserSettingsTab.scss";
+@import "./views/settings/tabs/user/_MjolnirUserSettingsTab.scss";
@import "./views/settings/tabs/user/_NotificationUserSettingsTab.scss";
@import "./views/settings/tabs/user/_PreferencesUserSettingsTab.scss";
@import "./views/settings/tabs/user/_SecurityUserSettingsTab.scss";
diff --git a/res/css/views/settings/tabs/user/_MjolnirUserSettingsTab.scss b/res/css/views/settings/tabs/user/_MjolnirUserSettingsTab.scss
new file mode 100644
index 0000000000..930dbeb440
--- /dev/null
+++ b/res/css/views/settings/tabs/user/_MjolnirUserSettingsTab.scss
@@ -0,0 +1,23 @@
+/*
+Copyright 2019 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.
+*/
+
+.mx_MjolnirUserSettingsTab .mx_Field {
+ @mixin mx_Settings_fullWidthField;
+}
+
+.mx_MjolnirUserSettingsTab_personalRule {
+ margin-bottom: 2px;
+}
diff --git a/src/components/views/settings/tabs/user/MjolnirUserSettingsTab.js b/src/components/views/settings/tabs/user/MjolnirUserSettingsTab.js
index 02e64c0bc1..97f92bb0b2 100644
--- a/src/components/views/settings/tabs/user/MjolnirUserSettingsTab.js
+++ b/src/components/views/settings/tabs/user/MjolnirUserSettingsTab.js
@@ -16,28 +16,115 @@ limitations under the License.
import React from 'react';
import {_t} from "../../../../../languageHandler";
+import {Mjolnir} from "../../../../../mjolnir/Mjolnir";
+import {ListRule} from "../../../../../mjolnir/ListRule";
+import {RULE_SERVER, RULE_USER} from "../../../../../mjolnir/BanList";
+import Modal from "../../../../../Modal";
+
const sdk = require("../../../../..");
export default class MjolnirUserSettingsTab extends React.Component {
constructor() {
super();
+
+ this.state = {
+ busy: false,
+ newPersonalRule: "",
+ };
+ }
+
+ _onPersonalRuleChanged = (e) => {
+ this.setState({newPersonalRule: e.target.value});
+ };
+
+ _onAddPersonalRule = async (e) => {
+ e.preventDefault();
+ e.stopPropagation();
+
+ let kind = RULE_SERVER;
+ if (this.state.newPersonalRule.startsWith("@")) {
+ kind = RULE_USER;
+ }
+
+ this.setState({busy: true});
+ try {
+ const list = await Mjolnir.sharedInstance().getOrCreatePersonalList();
+ await list.banEntity(kind, this.state.newPersonalRule, _t("Ignored/Blocked"));
+ this.setState({newPersonalRule: ""}); // this will also cause the new rule to be rendered
+ } catch (e) {
+ console.error(e);
+
+ const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
+ Modal.createTrackedDialog('Failed to remove Mjolnir rule', '', ErrorDialog, {
+ title: _t('Error removing ignored user/server'),
+ description: _t('Something went wrong. Please try again or view your console for hints.'),
+ });
+ } finally {
+ this.setState({busy: false});
+ }
+ };
+
+ async _removePersonalRule(rule: ListRule) {
+ this.setState({busy: true});
+ try {
+ const list = Mjolnir.sharedInstance().getPersonalList();
+ await list.unbanEntity(rule.kind, rule.entity);
+ } catch (e) {
+ console.error(e);
+
+ const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
+ Modal.createTrackedDialog('Failed to remove Mjolnir rule', '', ErrorDialog, {
+ title: _t('Error removing ignored user/server'),
+ description: _t('Something went wrong. Please try again or view your console for hints.'),
+ });
+ } finally {
+ this.setState({busy: false});
+ }
+ }
+
+ _renderPersonalBanListRules() {
+ const AccessibleButton = sdk.getComponent('elements.AccessibleButton');
+
+ const list = Mjolnir.sharedInstance().getPersonalList();
+ const rules = list ? [...list.userRules, ...list.serverRules] : [];
+ if (!list || rules.length <= 0) return {_t("You have not ignored anyone.")};
+
+ const tiles = [];
+ for (const rule of rules) {
+ tiles.push(
+
- {_t("⚠ These settings are meant for advanced users.")}
-
+ {_t("⚠ These settings are meant for advanced users.")}
+
{_t(
"Add users and servers you want to ignore here. Use asterisks " +
"to have Riot match any characters. For example, @bot:* " +
"would ignore all users that have the name 'bot' on any server.",
{}, {code: (s) => {s}},
- )}
-
+ )}
+
{_t(
"Ignoring people is done through ban lists which contain rules for " +
"who to ban. Subscribing to a ban list means the users/servers blocked by " +
@@ -55,7 +142,25 @@ export default class MjolnirUserSettingsTab extends React.Component {
"the ban list in effect.",
)}
-
TODO
+
+ {this._renderPersonalBanListRules()}
+
+
+
+
{_t("Subscribed lists")}
diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json
index 770f4723ef..fa15433a1a 100644
--- a/src/i18n/strings/en_EN.json
+++ b/src/i18n/strings/en_EN.json
@@ -640,12 +640,21 @@
"Access Token:": "Access Token:",
"click to reveal": "click to reveal",
"Labs": "Labs",
+ "Ignored/Blocked": "Ignored/Blocked",
+ "Error removing ignored user/server": "Error removing ignored user/server",
+ "Something went wrong. Please try again or view your console for hints.": "Something went wrong. Please try again or view your console for hints.",
+ "You have not ignored anyone.": "You have not ignored anyone.",
+ "Remove": "Remove",
+ "You are currently ignoring:": "You are currently ignoring:",
"Ignored users": "Ignored users",
"⚠ These settings are meant for advanced users.": "⚠ These settings are meant for advanced users.",
"Add users and servers you want to ignore here. Use asterisks to have Riot match any characters. For example, @bot:* would ignore all users that have the name 'bot' on any server.": "Add users and servers you want to ignore here. Use asterisks to have Riot match any characters. For example, @bot:* would ignore all users that have the name 'bot' on any server.",
"Ignoring people is done through ban lists which contain rules for who to ban. Subscribing to a ban list means the users/servers blocked by that list will be hidden from you.": "Ignoring people is done through ban lists which contain rules for who to ban. Subscribing to a ban list means the users/servers blocked by that list will be hidden from you.",
"Personal ban list": "Personal ban list",
"Your personal ban list holds all the users/servers you personally don't want to see messages from. After ignoring your first user/server, a new room will show up in your room list named 'My Ban List' - stay in this room to keep the ban list in effect.": "Your personal ban list holds all the users/servers you personally don't want to see messages from. After ignoring your first user/server, a new room will show up in your room list named 'My Ban List' - stay in this room to keep the ban list in effect.",
+ "Server or user ID to ignore": "Server or user ID to ignore",
+ "eg: @bot:* or example.org": "eg: @bot:* or example.org",
+ "Ignore": "Ignore",
"Subscribed lists": "Subscribed lists",
"Subscribing to a ban list will cause you to join it!": "Subscribing to a ban list will cause you to join it!",
"If this isn't what you want, please use a different tool to ignore users.": "If this isn't what you want, please use a different tool to ignore users.",
@@ -776,7 +785,6 @@
"Discovery options will appear once you have added a phone number above.": "Discovery options will appear once you have added a phone number above.",
"Unable to remove contact information": "Unable to remove contact information",
"Remove %(email)s?": "Remove %(email)s?",
- "Remove": "Remove",
"Invalid Email Address": "Invalid Email Address",
"This doesn't appear to be a valid email address": "This doesn't appear to be a valid email address",
"Unable to add email address": "Unable to add email address",
@@ -843,7 +851,6 @@
"You will not be able to undo this change as you are promoting the user to have the same power level as yourself.": "You will not be able to undo this change as you are promoting the user to have the same power level as yourself.",
"Are you sure?": "Are you sure?",
"No devices with registered encryption keys": "No devices with registered encryption keys",
- "Ignore": "Ignore",
"Jump to read receipt": "Jump to read receipt",
"Mention": "Mention",
"Invite": "Invite",
diff --git a/src/mjolnir/BanList.js b/src/mjolnir/BanList.js
index 6ebc0a7e36..026005420a 100644
--- a/src/mjolnir/BanList.js
+++ b/src/mjolnir/BanList.js
@@ -60,17 +60,23 @@ export class BanList {
return this._rules.filter(r => r.kind === RULE_ROOM);
}
- banEntity(kind: string, entity: string, reason: string): Promise {
- return MatrixClientPeg.get().sendStateEvent(this._roomId, ruleTypeToStable(kind, true), {
+ async banEntity(kind: string, entity: string, reason: string): Promise {
+ await MatrixClientPeg.get().sendStateEvent(this._roomId, ruleTypeToStable(kind, true), {
entity: entity,
reason: reason,
recommendation: recommendationToStable(RECOMMENDATION_BAN, true),
}, "rule:" + entity);
+ this._rules.push(new ListRule(entity, RECOMMENDATION_BAN, reason, ruleTypeToStable(kind, false)));
}
- unbanEntity(kind: string, entity: string): Promise {
+ async unbanEntity(kind: string, entity: string): Promise {
// Empty state event is effectively deleting it.
- return MatrixClientPeg.get().sendStateEvent(this._roomId, ruleTypeToStable(kind, true), {}, "rule:" + entity);
+ await MatrixClientPeg.get().sendStateEvent(this._roomId, ruleTypeToStable(kind, true), {}, "rule:" + entity);
+ this._rules = this._rules.filter(r => {
+ if (r.kind !== ruleTypeToStable(kind, false)) return true;
+ if (r.entity !== entity) return true;
+ return false; // we just deleted this rule
+ });
}
updateList() {
@@ -82,7 +88,7 @@ export class BanList {
for (const eventType of ALL_RULE_TYPES) {
const events = room.currentState.getStateEvents(eventType, undefined);
for (const ev of events) {
- if (!ev['state_key']) continue;
+ if (!ev.getStateKey()) continue;
const kind = ruleTypeToStable(eventType, false);
diff --git a/src/mjolnir/Mjolnir.js b/src/mjolnir/Mjolnir.js
index a12534592d..d90ea9cd04 100644
--- a/src/mjolnir/Mjolnir.js
+++ b/src/mjolnir/Mjolnir.js
@@ -18,6 +18,7 @@ import MatrixClientPeg from "../MatrixClientPeg";
import {ALL_RULE_TYPES, BanList} from "./BanList";
import SettingsStore, {SettingLevel} from "../settings/SettingsStore";
import {_t} from "../languageHandler";
+import dis from "../dispatcher";
// TODO: Move this and related files to the js-sdk or something once finalized.
@@ -27,19 +28,39 @@ export class Mjolnir {
_lists: BanList[] = [];
_roomIds: string[] = [];
_mjolnirWatchRef = null;
+ _dispatcherRef = null;
constructor() {
}
start() {
- this._updateLists(SettingsStore.getValue("mjolnirRooms"));
this._mjolnirWatchRef = SettingsStore.watchSetting("mjolnirRooms", null, this._onListsChanged.bind(this));
+ this._dispatcherRef = dis.register(this._onAction);
+ dis.dispatch({
+ action: 'do_after_sync_prepared',
+ deferred_action: {action: 'setup_mjolnir'},
+ });
+ }
+
+ _onAction = (payload) => {
+ if (payload['action'] === 'setup_mjolnir') {
+ console.log("Setting up Mjolnir: after sync");
+ this.setup();
+ }
+ };
+
+ setup() {
+ if (!MatrixClientPeg.get()) return;
+ this._updateLists(SettingsStore.getValue("mjolnirRooms"));
MatrixClientPeg.get().on("RoomState.events", this._onEvent.bind(this));
}
stop() {
SettingsStore.unwatchSetting(this._mjolnirWatchRef);
+ dis.unregister(this._dispatcherRef);
+
+ if (!MatrixClientPeg.get()) return;
MatrixClientPeg.get().removeListener("RoomState.events", this._onEvent.bind(this));
}
@@ -52,8 +73,8 @@ export class Mjolnir {
preset: "private_chat"
});
personalRoomId = resp['room_id'];
- SettingsStore.setValue("mjolnirPersonalRoom", null, SettingLevel.ACCOUNT, personalRoomId);
- SettingsStore.setValue("mjolnirRooms", null, SettingLevel.ACCOUNT, [personalRoomId, ...this._roomIds]);
+ await SettingsStore.setValue("mjolnirPersonalRoom", null, SettingLevel.ACCOUNT, personalRoomId);
+ await SettingsStore.setValue("mjolnirRooms", null, SettingLevel.ACCOUNT, [personalRoomId, ...this._roomIds]);
}
if (!personalRoomId) {
throw new Error("Error finding a room ID to use");
@@ -67,7 +88,21 @@ export class Mjolnir {
return list;
}
+ // get without creating the list
+ getPersonalList(): BanList {
+ const personalRoomId = SettingsStore.getValue("mjolnirPersonalRoom");
+ if (!personalRoomId) return null;
+
+ let list = this._lists.find(b => b.roomId === personalRoomId);
+ if (!list) list = new BanList(personalRoomId);
+ // we don't append the list to the tracked rooms because it should already be there.
+ // we're just trying to get the caller some utility access to the list
+
+ return list;
+ }
+
_onEvent(event) {
+ if (!MatrixClientPeg.get()) return;
if (!this._roomIds.includes(event.getRoomId())) return;
if (!ALL_RULE_TYPES.includes(event.getType())) return;
@@ -80,6 +115,9 @@ export class Mjolnir {
}
_updateLists(listRoomIds: string[]) {
+ if (!MatrixClientPeg.get()) return;
+
+ console.log("Updating Mjolnir ban lists to: " + listRoomIds);
this._lists = [];
this._roomIds = listRoomIds || [];
if (!listRoomIds) return;
diff --git a/src/utils/MatrixGlob.js b/src/utils/MatrixGlob.js
index cf55040625..b18e20ecf4 100644
--- a/src/utils/MatrixGlob.js
+++ b/src/utils/MatrixGlob.js
@@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
-import * as globToRegexp from "glob-to-regexp";
+import globToRegexp from "glob-to-regexp";
// Taken with permission from matrix-bot-sdk:
// https://github.com/turt2live/matrix-js-bot-sdk/blob/eb148c2ecec7bf3ade801d73deb43df042d55aef/src/MatrixGlob.ts
From 39b657ce7c4c3402802c836acce8d2c095c0bb9a Mon Sep 17 00:00:00 2001
From: Travis Ralston
Date: Thu, 31 Oct 2019 15:53:18 -0600
Subject: [PATCH 015/334] Add basic structure for (un)subscribing from lists
---
.../tabs/user/_MjolnirUserSettingsTab.scss | 2 +-
.../tabs/user/MjolnirUserSettingsTab.js | 145 ++++++++++++++++--
src/i18n/strings/en_EN.json | 13 +-
src/mjolnir/Mjolnir.js | 20 +++
4 files changed, 166 insertions(+), 14 deletions(-)
diff --git a/res/css/views/settings/tabs/user/_MjolnirUserSettingsTab.scss b/res/css/views/settings/tabs/user/_MjolnirUserSettingsTab.scss
index 930dbeb440..c60cbc5dea 100644
--- a/res/css/views/settings/tabs/user/_MjolnirUserSettingsTab.scss
+++ b/res/css/views/settings/tabs/user/_MjolnirUserSettingsTab.scss
@@ -18,6 +18,6 @@ limitations under the License.
@mixin mx_Settings_fullWidthField;
}
-.mx_MjolnirUserSettingsTab_personalRule {
+.mx_MjolnirUserSettingsTab_listItem {
margin-bottom: 2px;
}
diff --git a/src/components/views/settings/tabs/user/MjolnirUserSettingsTab.js b/src/components/views/settings/tabs/user/MjolnirUserSettingsTab.js
index 97f92bb0b2..4e05b57567 100644
--- a/src/components/views/settings/tabs/user/MjolnirUserSettingsTab.js
+++ b/src/components/views/settings/tabs/user/MjolnirUserSettingsTab.js
@@ -18,8 +18,9 @@ import React from 'react';
import {_t} from "../../../../../languageHandler";
import {Mjolnir} from "../../../../../mjolnir/Mjolnir";
import {ListRule} from "../../../../../mjolnir/ListRule";
-import {RULE_SERVER, RULE_USER} from "../../../../../mjolnir/BanList";
+import {BanList, RULE_SERVER, RULE_USER} from "../../../../../mjolnir/BanList";
import Modal from "../../../../../Modal";
+import MatrixClientPeg from "../../../../../MatrixClientPeg";
const sdk = require("../../../../..");
@@ -30,6 +31,7 @@ export default class MjolnirUserSettingsTab extends React.Component {
this.state = {
busy: false,
newPersonalRule: "",
+ newList: "",
};
}
@@ -37,6 +39,10 @@ export default class MjolnirUserSettingsTab extends React.Component {
this.setState({newPersonalRule: e.target.value});
};
+ _onNewListChanged = (e) => {
+ this.setState({newList: e.target.value});
+ };
+
_onAddPersonalRule = async (e) => {
e.preventDefault();
e.stopPropagation();
@@ -55,8 +61,8 @@ export default class MjolnirUserSettingsTab extends React.Component {
console.error(e);
const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
- Modal.createTrackedDialog('Failed to remove Mjolnir rule', '', ErrorDialog, {
- title: _t('Error removing ignored user/server'),
+ Modal.createTrackedDialog('Failed to add Mjolnir rule', '', ErrorDialog, {
+ title: _t('Error adding ignored user/server'),
description: _t('Something went wrong. Please try again or view your console for hints.'),
});
} finally {
@@ -64,6 +70,28 @@ export default class MjolnirUserSettingsTab extends React.Component {
}
};
+ _onSubscribeList = async (e) => {
+ e.preventDefault();
+ e.stopPropagation();
+
+ this.setState({busy: true});
+ try {
+ const room = await MatrixClientPeg.get().joinRoom(this.state.newList);
+ await Mjolnir.sharedInstance().subscribeToList(room.roomId);
+ this.setState({newList: ""}); // this will also cause the new rule to be rendered
+ } catch (e) {
+ console.error(e);
+
+ const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
+ Modal.createTrackedDialog('Failed to subscribe to Mjolnir list', '', ErrorDialog, {
+ title: _t('Error subscribing to list'),
+ description: _t('Please verify the room ID or alias and try again.'),
+ });
+ } finally {
+ this.setState({busy: false});
+ }
+ };
+
async _removePersonalRule(rule: ListRule) {
this.setState({busy: true});
try {
@@ -82,6 +110,28 @@ export default class MjolnirUserSettingsTab extends React.Component {
}
}
+ async _unsubscribeFromList(list: BanList) {
+ this.setState({busy: true});
+ try {
+ await Mjolnir.sharedInstance().unsubscribeFromList(list.roomId);
+ await MatrixClientPeg.get().leave(list.roomId);
+ } catch (e) {
+ console.error(e);
+
+ const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
+ Modal.createTrackedDialog('Failed to unsubscribe from Mjolnir list', '', ErrorDialog, {
+ title: _t('Error unsubscribing from list'),
+ description: _t('Please try again or view your console for hints.'),
+ });
+ } finally {
+ this.setState({busy: false});
+ }
+ }
+
+ _viewListRules(list: BanList) {
+ // TODO
+ }
+
_renderPersonalBanListRules() {
const AccessibleButton = sdk.getComponent('elements.AccessibleButton');
@@ -92,9 +142,12 @@ export default class MjolnirUserSettingsTab extends React.Component {
const tiles = [];
for (const rule of rules) {
tiles.push(
-
+ );
}
render() {
@@ -155,8 +251,12 @@ export default class MjolnirUserSettingsTab extends React.Component {
value={this.state.newPersonalRule}
onChange={this._onPersonalRuleChanged}
/>
-
+
{_t("Ignore")}
@@ -171,7 +271,28 @@ export default class MjolnirUserSettingsTab extends React.Component {
"If this isn't what you want, please use a different tool to ignore users.",
)}
-
TODO
+
+ {this._renderSubscribedBanLists()}
+
+
+
+
);
diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json
index fa15433a1a..561dbc4da9 100644
--- a/src/i18n/strings/en_EN.json
+++ b/src/i18n/strings/en_EN.json
@@ -641,11 +641,20 @@
"click to reveal": "click to reveal",
"Labs": "Labs",
"Ignored/Blocked": "Ignored/Blocked",
- "Error removing ignored user/server": "Error removing ignored user/server",
+ "Error adding ignored user/server": "Error adding ignored user/server",
"Something went wrong. Please try again or view your console for hints.": "Something went wrong. Please try again or view your console for hints.",
+ "Error subscribing to list": "Error subscribing to list",
+ "Please verify the room ID or alias and try again.": "Please verify the room ID or alias and try again.",
+ "Error removing ignored user/server": "Error removing ignored user/server",
+ "Error unsubscribing from list": "Error unsubscribing from list",
+ "Please try again or view your console for hints.": "Please try again or view your console for hints.",
"You have not ignored anyone.": "You have not ignored anyone.",
"Remove": "Remove",
"You are currently ignoring:": "You are currently ignoring:",
+ "You are not subscribed to any lists": "You are not subscribed to any lists",
+ "Unsubscribe": "Unsubscribe",
+ "View rules": "View rules",
+ "You are currently subscribed to:": "You are currently subscribed to:",
"Ignored users": "Ignored users",
"⚠ These settings are meant for advanced users.": "⚠ These settings are meant for advanced users.",
"Add users and servers you want to ignore here. Use asterisks to have Riot match any characters. For example, @bot:* would ignore all users that have the name 'bot' on any server.": "Add users and servers you want to ignore here. Use asterisks to have Riot match any characters. For example, @bot:* would ignore all users that have the name 'bot' on any server.",
@@ -658,6 +667,8 @@
"Subscribed lists": "Subscribed lists",
"Subscribing to a ban list will cause you to join it!": "Subscribing to a ban list will cause you to join it!",
"If this isn't what you want, please use a different tool to ignore users.": "If this isn't what you want, please use a different tool to ignore users.",
+ "Room ID or alias of ban list": "Room ID or alias of ban list",
+ "Subscribe": "Subscribe",
"Notifications": "Notifications",
"Start automatically after system login": "Start automatically after system login",
"Always show the window menu bar": "Always show the window menu bar",
diff --git a/src/mjolnir/Mjolnir.js b/src/mjolnir/Mjolnir.js
index d90ea9cd04..5edfe3750e 100644
--- a/src/mjolnir/Mjolnir.js
+++ b/src/mjolnir/Mjolnir.js
@@ -33,6 +33,14 @@ export class Mjolnir {
constructor() {
}
+ get roomIds(): string[] {
+ return this._roomIds;
+ }
+
+ get lists(): BanList[] {
+ return this._lists;
+ }
+
start() {
this._mjolnirWatchRef = SettingsStore.watchSetting("mjolnirRooms", null, this._onListsChanged.bind(this));
@@ -101,6 +109,18 @@ export class Mjolnir {
return list;
}
+ async subscribeToList(roomId: string) {
+ const roomIds = [...this._roomIds, roomId];
+ await SettingsStore.setValue("mjolnirRooms", null, SettingLevel.ACCOUNT, roomIds);
+ this._lists.push(new BanList(roomId));
+ }
+
+ async unsubscribeFromList(roomId: string) {
+ const roomIds = this._roomIds.filter(r => r !== roomId);
+ await SettingsStore.setValue("mjolnirRooms", null, SettingLevel.ACCOUNT, roomIds);
+ this._lists = this._lists.filter(b => b.roomId !== roomId);
+ }
+
_onEvent(event) {
if (!MatrixClientPeg.get()) return;
if (!this._roomIds.includes(event.getRoomId())) return;
From b420fd675857d6c3e212caafa1c56d2ddc4a16da Mon Sep 17 00:00:00 2001
From: Travis Ralston
Date: Thu, 31 Oct 2019 16:00:31 -0600
Subject: [PATCH 016/334] Add a view rules dialog
---
.../tabs/user/MjolnirUserSettingsTab.js | 29 ++++++++++++++++++-
src/i18n/strings/en_EN.json | 6 +++-
2 files changed, 33 insertions(+), 2 deletions(-)
diff --git a/src/components/views/settings/tabs/user/MjolnirUserSettingsTab.js b/src/components/views/settings/tabs/user/MjolnirUserSettingsTab.js
index 4e05b57567..a02ca2c570 100644
--- a/src/components/views/settings/tabs/user/MjolnirUserSettingsTab.js
+++ b/src/components/views/settings/tabs/user/MjolnirUserSettingsTab.js
@@ -129,7 +129,34 @@ export default class MjolnirUserSettingsTab extends React.Component {
}
_viewListRules(list: BanList) {
- // TODO
+ const QuestionDialog = sdk.getComponent("dialogs.QuestionDialog");
+
+ const room = MatrixClientPeg.get().getRoom(list.roomId);
+ const name = room ? room.name : list.roomId;
+
+ const renderRules = (rules: ListRule[]) => {
+ if (rules.length === 0) return {_t("None")};
+
+ const tiles = [];
+ for (const rule of rules) {
+ tiles.push(
- {_t("⚠ These settings are meant for advanced users.")}
-
+ {_t("⚠ These settings are meant for advanced users.")}
+
{_t(
"Add users and servers you want to ignore here. Use asterisks " +
"to have Riot match any characters. For example, @bot:* " +
"would ignore all users that have the name 'bot' on any server.",
{}, {code: (s) => {s}},
- )}
-
+ )}
+
{_t(
"Ignoring people is done through ban lists which contain rules for " +
"who to ban. Subscribing to a ban list means the users/servers blocked by " +
- "that list will be hidden from you."
+ "that list will be hidden from you.",
)}
diff --git a/src/mjolnir/BanList.js b/src/mjolnir/BanList.js
index 026005420a..60a924a52b 100644
--- a/src/mjolnir/BanList.js
+++ b/src/mjolnir/BanList.js
@@ -29,9 +29,15 @@ export const SERVER_RULE_TYPES = [RULE_SERVER, "org.matrix.mjolnir.rule.server"]
export const ALL_RULE_TYPES = [...USER_RULE_TYPES, ...ROOM_RULE_TYPES, ...SERVER_RULE_TYPES];
export function ruleTypeToStable(rule: string, unstable = true): string {
- if (USER_RULE_TYPES.includes(rule)) return unstable ? USER_RULE_TYPES[USER_RULE_TYPES.length - 1] : RULE_USER;
- if (ROOM_RULE_TYPES.includes(rule)) return unstable ? ROOM_RULE_TYPES[ROOM_RULE_TYPES.length - 1] : RULE_ROOM;
- if (SERVER_RULE_TYPES.includes(rule)) return unstable ? SERVER_RULE_TYPES[SERVER_RULE_TYPES.length - 1] : RULE_SERVER;
+ if (USER_RULE_TYPES.includes(rule)) {
+ return unstable ? USER_RULE_TYPES[USER_RULE_TYPES.length - 1] : RULE_USER;
+ }
+ if (ROOM_RULE_TYPES.includes(rule)) {
+ return unstable ? ROOM_RULE_TYPES[ROOM_RULE_TYPES.length - 1] : RULE_ROOM;
+ }
+ if (SERVER_RULE_TYPES.includes(rule)) {
+ return unstable ? SERVER_RULE_TYPES[SERVER_RULE_TYPES.length - 1] : RULE_SERVER;
+ }
return null;
}
diff --git a/src/mjolnir/ListRule.js b/src/mjolnir/ListRule.js
index d33248d24c..1d472e06d6 100644
--- a/src/mjolnir/ListRule.js
+++ b/src/mjolnir/ListRule.js
@@ -22,7 +22,9 @@ export const RECOMMENDATION_BAN = "m.ban";
export const RECOMMENDATION_BAN_TYPES = [RECOMMENDATION_BAN, "org.matrix.mjolnir.ban"];
export function recommendationToStable(recommendation: string, unstable = true): string {
- if (RECOMMENDATION_BAN_TYPES.includes(recommendation)) return unstable ? RECOMMENDATION_BAN_TYPES[RECOMMENDATION_BAN_TYPES.length - 1] : RECOMMENDATION_BAN;
+ if (RECOMMENDATION_BAN_TYPES.includes(recommendation)) {
+ return unstable ? RECOMMENDATION_BAN_TYPES[RECOMMENDATION_BAN_TYPES.length - 1] : RECOMMENDATION_BAN;
+ }
return null;
}
diff --git a/src/mjolnir/Mjolnir.js b/src/mjolnir/Mjolnir.js
index 5edfe3750e..9177c621d1 100644
--- a/src/mjolnir/Mjolnir.js
+++ b/src/mjolnir/Mjolnir.js
@@ -78,11 +78,13 @@ export class Mjolnir {
const resp = await MatrixClientPeg.get().createRoom({
name: _t("My Ban List"),
topic: _t("This is your list of users/servers you have blocked - don't leave the room!"),
- preset: "private_chat"
+ preset: "private_chat",
});
personalRoomId = resp['room_id'];
- await SettingsStore.setValue("mjolnirPersonalRoom", null, SettingLevel.ACCOUNT, personalRoomId);
- await SettingsStore.setValue("mjolnirRooms", null, SettingLevel.ACCOUNT, [personalRoomId, ...this._roomIds]);
+ await SettingsStore.setValue(
+ "mjolnirPersonalRoom", null, SettingLevel.ACCOUNT, personalRoomId);
+ await SettingsStore.setValue(
+ "mjolnirRooms", null, SettingLevel.ACCOUNT, [personalRoomId, ...this._roomIds]);
}
if (!personalRoomId) {
throw new Error("Error finding a room ID to use");
diff --git a/src/utils/MatrixGlob.js b/src/utils/MatrixGlob.js
index b18e20ecf4..e07aaab541 100644
--- a/src/utils/MatrixGlob.js
+++ b/src/utils/MatrixGlob.js
@@ -50,5 +50,4 @@ export class MatrixGlob {
test(val: string): boolean {
return this._regex.test(val);
}
-
}
From 3c45a39caaab2c13f8b687e08679ead3adca7b85 Mon Sep 17 00:00:00 2001
From: Travis Ralston
Date: Thu, 31 Oct 2019 16:30:51 -0600
Subject: [PATCH 019/334] Appease the other linter
---
res/css/views/messages/_MjolnirBody.scss | 2 +-
res/css/views/settings/tabs/user/_MjolnirUserSettingsTab.scss | 4 ++--
2 files changed, 3 insertions(+), 3 deletions(-)
diff --git a/res/css/views/messages/_MjolnirBody.scss b/res/css/views/messages/_MjolnirBody.scss
index 80be7429e5..2760adfd7e 100644
--- a/res/css/views/messages/_MjolnirBody.scss
+++ b/res/css/views/messages/_MjolnirBody.scss
@@ -15,5 +15,5 @@ limitations under the License.
*/
.mx_MjolnirBody {
- opacity: 0.4;
+ opacity: 0.4;
}
diff --git a/res/css/views/settings/tabs/user/_MjolnirUserSettingsTab.scss b/res/css/views/settings/tabs/user/_MjolnirUserSettingsTab.scss
index c60cbc5dea..2a3fd12f31 100644
--- a/res/css/views/settings/tabs/user/_MjolnirUserSettingsTab.scss
+++ b/res/css/views/settings/tabs/user/_MjolnirUserSettingsTab.scss
@@ -15,9 +15,9 @@ limitations under the License.
*/
.mx_MjolnirUserSettingsTab .mx_Field {
- @mixin mx_Settings_fullWidthField;
+ @mixin mx_Settings_fullWidthField;
}
.mx_MjolnirUserSettingsTab_listItem {
- margin-bottom: 2px;
+ margin-bottom: 2px;
}
From 07b8e128d2adc198767d9978329448ea59dad868 Mon Sep 17 00:00:00 2001
From: Travis Ralston
Date: Thu, 31 Oct 2019 16:43:03 -0600
Subject: [PATCH 020/334] Bypass the tests being weird
They run kinda-but-not-really async, which can lead to early/late calls to `stop()`
---
src/mjolnir/Mjolnir.js | 9 ++++++++-
1 file changed, 8 insertions(+), 1 deletion(-)
diff --git a/src/mjolnir/Mjolnir.js b/src/mjolnir/Mjolnir.js
index 9177c621d1..7539dfafb0 100644
--- a/src/mjolnir/Mjolnir.js
+++ b/src/mjolnir/Mjolnir.js
@@ -66,7 +66,14 @@ export class Mjolnir {
stop() {
SettingsStore.unwatchSetting(this._mjolnirWatchRef);
- dis.unregister(this._dispatcherRef);
+
+ try {
+ if (this._dispatcherRef) dis.unregister(this._dispatcherRef);
+ } catch (e) {
+ console.error(e);
+ // Only the tests cause problems with this particular block of code. We should
+ // never be here in production.
+ }
if (!MatrixClientPeg.get()) return;
MatrixClientPeg.get().removeListener("RoomState.events", this._onEvent.bind(this));
From 446e21c2e129ae8387b5eaf6f6fce198731887a2 Mon Sep 17 00:00:00 2001
From: "J. Ryan Stinnett"
Date: Fri, 1 Nov 2019 10:44:30 +0000
Subject: [PATCH 021/334] Relax identity server discovery error handling
If discovery results in a warning for the identity server (as in can't be found
or is malformed), this allows you to continue signing in and shows the warning
above the form.
Fixes https://github.com/vector-im/riot-web/issues/11102
---
src/components/structures/auth/Login.js | 15 ++++++++++--
src/utils/AutoDiscoveryUtils.js | 31 +++++++++++++++----------
2 files changed, 32 insertions(+), 14 deletions(-)
diff --git a/src/components/structures/auth/Login.js b/src/components/structures/auth/Login.js
index ad77ed49a5..af308e1407 100644
--- a/src/components/structures/auth/Login.js
+++ b/src/components/structures/auth/Login.js
@@ -378,8 +378,19 @@ module.exports = createReactClass({
// Do a quick liveliness check on the URLs
try {
- await AutoDiscoveryUtils.validateServerConfigWithStaticUrls(hsUrl, isUrl);
- this.setState({serverIsAlive: true, errorText: ""});
+ const { warning } =
+ await AutoDiscoveryUtils.validateServerConfigWithStaticUrls(hsUrl, isUrl);
+ if (warning) {
+ this.setState({
+ ...AutoDiscoveryUtils.authComponentStateForError(warning),
+ errorText: "",
+ });
+ } else {
+ this.setState({
+ serverIsAlive: true,
+ errorText: "",
+ });
+ }
} catch (e) {
this.setState({
busy: false,
diff --git a/src/utils/AutoDiscoveryUtils.js b/src/utils/AutoDiscoveryUtils.js
index e94c454a3e..49898aae90 100644
--- a/src/utils/AutoDiscoveryUtils.js
+++ b/src/utils/AutoDiscoveryUtils.js
@@ -1,5 +1,6 @@
/*
Copyright 2019 New Vector Ltd
+Copyright 2019 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.
@@ -33,6 +34,8 @@ export class ValidatedServerConfig {
isUrl: string;
isDefault: boolean;
+
+ warning: string;
}
export default class AutoDiscoveryUtils {
@@ -56,7 +59,14 @@ export default class AutoDiscoveryUtils {
* implementation for known values.
* @returns {*} The state for the component, given the error.
*/
- static authComponentStateForError(err: Error, pageName="login"): Object {
+ static authComponentStateForError(err: string | Error | null, pageName = "login"): Object {
+ if (!err) {
+ return {
+ serverIsAlive: true,
+ serverErrorIsFatal: false,
+ serverDeadError: null,
+ };
+ }
let title = _t("Cannot reach homeserver");
let body = _t("Ensure you have a stable internet connection, or get in touch with the server admin");
if (!AutoDiscoveryUtils.isLivelinessError(err)) {
@@ -153,11 +163,9 @@ export default class AutoDiscoveryUtils {
/**
* Validates a server configuration, using a homeserver domain name as input.
* @param {string} serverName The homeserver domain name (eg: "matrix.org") to validate.
- * @param {boolean} syntaxOnly If true, errors relating to liveliness of the servers will
- * not be raised.
* @returns {Promise} Resolves to the validated configuration.
*/
- static async validateServerName(serverName: string, syntaxOnly=false): ValidatedServerConfig {
+ static async validateServerName(serverName: string): ValidatedServerConfig {
const result = await AutoDiscovery.findClientConfig(serverName);
return AutoDiscoveryUtils.buildValidatedConfigFromDiscovery(serverName, result);
}
@@ -186,7 +194,7 @@ export default class AutoDiscoveryUtils {
const defaultConfig = SdkConfig.get()["validated_server_config"];
// Validate the identity server first because an invalid identity server causes
- // and invalid homeserver, which may not be picked up correctly.
+ // an invalid homeserver, which may not be picked up correctly.
// Note: In the cases where we rely on the default IS from the config (namely
// lack of identity server provided by the discovery method), we intentionally do not
@@ -197,20 +205,18 @@ export default class AutoDiscoveryUtils {
preferredIdentityUrl = isResult["base_url"];
} else if (isResult && isResult.state !== AutoDiscovery.PROMPT) {
console.error("Error determining preferred identity server URL:", isResult);
- if (!syntaxOnly || !AutoDiscoveryUtils.isLivelinessError(isResult.error)) {
+ if (isResult.state === AutoDiscovery.FAIL_ERROR) {
if (AutoDiscovery.ALL_ERRORS.indexOf(isResult.error) !== -1) {
throw newTranslatableError(isResult.error);
}
throw newTranslatableError(_td("Unexpected error resolving identity server configuration"));
} // else the error is not related to syntax - continue anyways.
- // rewrite homeserver error if we don't care about problems
- if (syntaxOnly) {
- hsResult.error = AutoDiscovery.ERROR_INVALID_IDENTITY_SERVER;
+ // rewrite homeserver error since we don't care about problems
+ hsResult.error = AutoDiscovery.ERROR_INVALID_IDENTITY_SERVER;
- // Also use the user's supplied identity server if provided
- if (isResult["base_url"]) preferredIdentityUrl = isResult["base_url"];
- }
+ // Also use the user's supplied identity server if provided
+ if (isResult["base_url"]) preferredIdentityUrl = isResult["base_url"];
}
if (hsResult.state !== AutoDiscovery.SUCCESS) {
@@ -241,6 +247,7 @@ export default class AutoDiscoveryUtils {
hsNameIsDifferent: url.hostname !== preferredHomeserverName,
isUrl: preferredIdentityUrl,
isDefault: false,
+ warning: hsResult.error,
});
}
}
From 86be607e92dbf148498e284d083e62b8716be2a8 Mon Sep 17 00:00:00 2001
From: Travis Ralston
Date: Wed, 6 Nov 2019 10:52:00 -0700
Subject: [PATCH 022/334] onTileUpdate -> onMessageAllowed
We keep onTileUpdate in MessgeEvent because it's a generic thing for the class to handle. onMessageAllowed is slightly different than onShowAllowed because "show allowed" doesn't quite make sense on its own, imo.
---
src/components/views/messages/MessageEvent.js | 2 +-
src/components/views/messages/MjolnirBody.js | 4 ++--
2 files changed, 3 insertions(+), 3 deletions(-)
diff --git a/src/components/views/messages/MessageEvent.js b/src/components/views/messages/MessageEvent.js
index 0d22658884..e75bcc4332 100644
--- a/src/components/views/messages/MessageEvent.js
+++ b/src/components/views/messages/MessageEvent.js
@@ -112,7 +112,7 @@ module.exports = createReactClass({
replacingEventId={this.props.replacingEventId}
editState={this.props.editState}
onHeightChanged={this.props.onHeightChanged}
- onTileUpdate={this.onTileUpdate}
+ onMessageAllowed={this.onTileUpdate}
/>;
},
});
diff --git a/src/components/views/messages/MjolnirBody.js b/src/components/views/messages/MjolnirBody.js
index d03c6c658d..baaee91657 100644
--- a/src/components/views/messages/MjolnirBody.js
+++ b/src/components/views/messages/MjolnirBody.js
@@ -21,7 +21,7 @@ import {_t} from '../../../languageHandler';
export default class MjolnirBody extends React.Component {
static propTypes = {
mxEvent: PropTypes.object.isRequired,
- onTileUpdate: PropTypes.func.isRequired,
+ onMessageAllowed: PropTypes.func.isRequired,
};
constructor() {
@@ -34,7 +34,7 @@ export default class MjolnirBody extends React.Component {
const key = `mx_mjolnir_render_${this.props.mxEvent.getRoomId()}__${this.props.mxEvent.getId()}`;
localStorage.setItem(key, "true");
- this.props.onTileUpdate();
+ this.props.onMessageAllowed();
};
render() {
From 06ab9efed639c8944c6181290c6683122151540a Mon Sep 17 00:00:00 2001
From: Slavi Pantaleev
Date: Tue, 5 Nov 2019 07:16:59 +0000
Subject: [PATCH 023/334] Translated using Weblate (Bulgarian)
Currently translated at 100.0% (1853 of 1853 strings)
Translation: Riot Web/matrix-react-sdk
Translate-URL: https://translate.riot.im/projects/riot-web/matrix-react-sdk/bg/
---
src/i18n/strings/bg.json | 47 +++++++++++++++++++++++++++++++++++++++-
1 file changed, 46 insertions(+), 1 deletion(-)
diff --git a/src/i18n/strings/bg.json b/src/i18n/strings/bg.json
index 2287c5b295..6f198b2e5a 100644
--- a/src/i18n/strings/bg.json
+++ b/src/i18n/strings/bg.json
@@ -2204,5 +2204,50 @@
"wait and try again later": "изчакате и опитате пак",
"Clear cache and reload": "Изчисти кеша и презареди",
"Show tray icon and minimize window to it on close": "Показвай икона в лентата и минимизирай прозореца там при затваряне",
- "Your email address hasn't been verified yet": "Имейл адресът ви все още не е потвърден"
+ "Your email address hasn't been verified yet": "Имейл адресът ви все още не е потвърден",
+ "Click the link in the email you received to verify and then click continue again.": "Кликнете на връзката получена по имейл за да потвърдите, а след това натиснете продължи отново.",
+ "You are about to remove %(count)s messages by %(user)s. This cannot be undone. Do you wish to continue?|one": "На път сте да премахнете 1 съобщение от %(user)s. Това е необратимо. Искате ли да продължите?",
+ "Remove %(count)s messages|one": "Премахни 1 съобщение",
+ "Room %(name)s": "Стая %(name)s",
+ "Recent rooms": "Скорошни стаи",
+ "%(count)s unread messages including mentions.|other": "%(count)s непрочетени съобщения, включително споменавания.",
+ "%(count)s unread messages including mentions.|one": "1 непрочетено споменаване.",
+ "%(count)s unread messages.|other": "%(count)s непрочетени съобщения.",
+ "%(count)s unread messages.|one": "1 непрочетено съобщение.",
+ "Unread mentions.": "Непрочетени споменавания.",
+ "Unread messages.": "Непрочетени съобщения.",
+ "Trust & Devices": "Доверие и устройства",
+ "Direct messages": "Директни съобщения",
+ "Failed to deactivate user": "Неуспешно деактивиране на потребител",
+ "This client does not support end-to-end encryption.": "Този клиент не поддържа шифроване от край до край.",
+ "Messages in this room are not end-to-end encrypted.": "Съобщенията в тази стая не са шифровани от край до край.",
+ "React": "Реагирай",
+ "Message Actions": "Действия със съобщението",
+ "Show image": "Покажи снимката",
+ "Frequently Used": "Често използвани",
+ "Smileys & People": "Усмивки и хора",
+ "Animals & Nature": "Животни и природа",
+ "Food & Drink": "Храна и напитки",
+ "Activities": "Действия",
+ "Travel & Places": "Пътуване и места",
+ "Objects": "Обекти",
+ "Symbols": "Символи",
+ "Flags": "Знамена",
+ "Quick Reactions": "Бързи реакции",
+ "Cancel search": "Отмени търсенето",
+ "Please create a new issue on GitHub so that we can investigate this bug.": "Моля, отворете нов проблем в GitHub за да проучим проблема.",
+ "To continue you need to accept the terms of this service.": "За да продължите, трябва да приемете условията за ползване.",
+ "Document": "Документ",
+ "Missing captcha public key in homeserver configuration. Please report this to your homeserver administrator.": "Липсва публичния ключ за catcha в конфигурацията на сървъра. Съобщете това на администратора на сървъра.",
+ "No identity server is configured so you cannot add an email address in order to reset your password in the future.": "Не е конфигуриран сървър за самоличност, така че не можете да добавите имейл адрес за възстановяване на паролата в бъдеще.",
+ "%(creator)s created and configured the room.": "%(creator)s създаде и настрой стаята.",
+ "Jump to first unread room.": "Отиди до първата непрочетена стая.",
+ "Jump to first invite.": "Отиди до първата покана.",
+ "Command Autocomplete": "Подсказка за команди",
+ "Community Autocomplete": "Подсказка за общности",
+ "DuckDuckGo Results": "DuckDuckGo резултати",
+ "Emoji Autocomplete": "Подсказка за емоджита",
+ "Notification Autocomplete": "Подсказка за уведомления",
+ "Room Autocomplete": "Подсказка за стаи",
+ "User Autocomplete": "Подсказка за потребители"
}
From dc5abbe3809831e00a8d2d1f2ed85d8c77bf364a Mon Sep 17 00:00:00 2001
From: Jeff Huang
Date: Tue, 5 Nov 2019 03:58:00 +0000
Subject: [PATCH 024/334] Translated using Weblate (Chinese (Traditional))
Currently translated at 100.0% (1853 of 1853 strings)
Translation: Riot Web/matrix-react-sdk
Translate-URL: https://translate.riot.im/projects/riot-web/matrix-react-sdk/zh_Hant/
---
src/i18n/strings/zh_Hant.json | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/src/i18n/strings/zh_Hant.json b/src/i18n/strings/zh_Hant.json
index 94183ef83f..58dca89415 100644
--- a/src/i18n/strings/zh_Hant.json
+++ b/src/i18n/strings/zh_Hant.json
@@ -2272,5 +2272,6 @@
"Unread messages.": "未讀的訊息。",
"Show tray icon and minimize window to it on close": "顯示系統匣圖示並在關閉視窗時將其最小化至其中",
"This action requires accessing the default identity server to validate an email address or phone number, but the server does not have any terms of service.": "此動作需要存取預設的身份識別伺服器 以驗證電子郵件或電話號碼,但伺服器沒有任何服務條款。",
- "Trust": "信任"
+ "Trust": "信任",
+ "Message Actions": "訊息動作"
}
From 6d4971c29eb2b760f15dae41085d5b164d5dc8e2 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?K=C3=A9vin=20C?=
Date: Mon, 4 Nov 2019 12:28:24 +0000
Subject: [PATCH 025/334] Translated using Weblate (French)
Currently translated at 100.0% (1853 of 1853 strings)
Translation: Riot Web/matrix-react-sdk
Translate-URL: https://translate.riot.im/projects/riot-web/matrix-react-sdk/fr/
---
src/i18n/strings/fr.json | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/src/i18n/strings/fr.json b/src/i18n/strings/fr.json
index 7807facb1c..328c3b7f9e 100644
--- a/src/i18n/strings/fr.json
+++ b/src/i18n/strings/fr.json
@@ -2279,5 +2279,6 @@
"Unread messages.": "Messages non lus.",
"Show tray icon and minimize window to it on close": "Afficher l’icône dans la barre d’état et minimiser la fenêtre lors de la fermeture",
"This action requires accessing the default identity server to validate an email address or phone number, but the server does not have any terms of service.": "Cette action nécessite l’accès au serveur d’identité par défaut afin de valider une adresse e-mail ou un numéro de téléphone, mais le serveur n’a aucune condition de service.",
- "Trust": "Confiance"
+ "Trust": "Confiance",
+ "Message Actions": "Actions de message"
}
From 4eb39190b493159f863debf7987b86980f8cd5b7 Mon Sep 17 00:00:00 2001
From: dreamerchris
Date: Tue, 5 Nov 2019 12:12:12 +0000
Subject: [PATCH 026/334] Translated using Weblate (Greek)
Currently translated at 39.7% (735 of 1853 strings)
Translation: Riot Web/matrix-react-sdk
Translate-URL: https://translate.riot.im/projects/riot-web/matrix-react-sdk/el/
---
src/i18n/strings/el.json | 91 +++++++++++++++++++++++++++++++++-------
1 file changed, 75 insertions(+), 16 deletions(-)
diff --git a/src/i18n/strings/el.json b/src/i18n/strings/el.json
index c5d6468881..a2438cc22c 100644
--- a/src/i18n/strings/el.json
+++ b/src/i18n/strings/el.json
@@ -57,7 +57,7 @@
"%(senderDisplayName)s removed the room name.": "Ο %(senderDisplayName)s διέγραψε το όνομα του δωματίου.",
"Changes your display nickname": "Αλλάζει το ψευδώνυμο χρήστη",
"Conference call failed.": "Η κλήση συνδιάσκεψης απέτυχε.",
- "powered by Matrix": "με τη βοήθεια του Matrix",
+ "powered by Matrix": "λειτουργεί με το Matrix",
"Confirm password": "Επιβεβαίωση κωδικού πρόσβασης",
"Confirm your new password": "Επιβεβαίωση του νέου κωδικού πρόσβασης",
"Continue": "Συνέχεια",
@@ -567,7 +567,7 @@
"numbullet": "απαρίθμηση",
"You must join the room to see its files": "Πρέπει να συνδεθείτε στο δωμάτιο για να δείτε τα αρχεία του",
"Reject all %(invitedRooms)s invites": "Απόρριψη όλων των προσκλήσεων %(invitedRooms)s",
- "Failed to invite the following users to the %(roomName)s room:": "Δεν ήταν δυνατή η πρόσκληση των χρηστών στο δωμάτιο %(roomName)s:",
+ "Failed to invite the following users to the %(roomName)s room:": "Δεν ήταν δυνατή η πρόσκληση των παρακάτω χρηστών στο δωμάτιο %(roomName)s:",
"Deops user with given id": "Deop χρήστη με το συγκεκριμένο αναγνωριστικό",
"Drop here to tag %(section)s": "Απόθεση εδώ για ορισμό ετικέτας στο %(section)s",
"Join as voice or video.": "Συμμετάσχετε με φωνή ή βίντεο.",
@@ -575,7 +575,7 @@
"Show timestamps in 12 hour format (e.g. 2:30pm)": "Εμφάνιση χρονικών σημάνσεων σε 12ωρη μορφή ώρας (π.χ. 2:30 μ.μ.)",
"The signing key you provided matches the signing key you received from %(userId)s's device %(deviceId)s. Device marked as verified.": "Το κλειδί υπογραφής που δώσατε αντιστοιχεί στο κλειδί υπογραφής που λάβατε από τη συσκευή %(userId)s %(deviceId)s. Η συσκευή έχει επισημανθεί ως επιβεβαιωμένη.",
"To link to a room it must have an address.": "Για να συνδεθείτε σε ένα δωμάτιο πρέπει να έχετε μια διεύθυνση.",
- "Your email address does not appear to be associated with a Matrix ID on this Homeserver.": "Η διεύθυνση ηλεκτρονικής αλληλογραφίας σας δεν φαίνεται να συσχετίζεται με Matrix ID σε αυτόν τον διακομιστή.",
+ "Your email address does not appear to be associated with a Matrix ID on this Homeserver.": "Η διεύθυνση της ηλ. αλληλογραφίας σας δεν φαίνεται να συσχετίζεται με μια ταυτότητα Matrix σε αυτόν τον Διακομιστή Φιλοξενίας.",
"Your password was successfully changed. You will not receive push notifications on other devices until you log back in to them": "Ο κωδικός πρόσβασής σας άλλαξε επιτυχώς. Δεν θα λάβετε ειδοποιήσεις push σε άλλες συσκευές μέχρι να συνδεθείτε ξανά σε αυτές",
"You will not be able to undo this change as you are promoting the user to have the same power level as yourself.": "Δεν θα μπορέσετε να αναιρέσετε αυτήν την αλλαγή καθώς προωθείτε τον χρήστη να έχει το ίδιο επίπεδο δύναμης με τον εαυτό σας.",
"Sent messages will be stored until your connection has returned.": "Τα απεσταλμένα μηνύματα θα αποθηκευτούν μέχρι να αακτηθεί η σύνδεσή σας.",
@@ -765,7 +765,7 @@
"The platform you're on": "Η πλατφόρμα στην οποία βρίσκεστε",
"The version of Riot.im": "Η έκδοση του Riot.im",
"Your language of choice": "Η γλώσσα επιλογής σας",
- "Your homeserver's URL": "Το URL του διακομιστή σας",
+ "Your homeserver's URL": "Το URL του διακομιστή φιλοξενίας σας",
"Every page you use in the app": "Κάθε σελίδα που χρησιμοποιείτε στην εφαρμογή",
"e.g. ": "π.χ. ",
"Your device resolution": "Η ανάλυση της συσκευής σας",
@@ -774,7 +774,7 @@
"Whether or not you're logged in (we don't record your user name)": "Εάν είστε συνδεδεμένος/η ή όχι (δεν καταγράφουμε το όνομα χρήστη σας)",
"e.g. %(exampleValue)s": "π.χ. %(exampleValue)s",
"Review Devices": "Ανασκόπηση συσκευών",
- "Call Anyway": "Κλήση όπως και να 'χει",
+ "Call Anyway": "Πραγματοποίηση Κλήσης όπως και να 'χει",
"Answer Anyway": "Απάντηση όπως και να 'χει",
"Call": "Κλήση",
"Answer": "Απάντηση",
@@ -785,20 +785,20 @@
"Warning: any person you add to a community will be publicly visible to anyone who knows the community ID": "Προσοχή: κάθε άτομο που προσθέτετε στην κοινότητα θε είναι δημοσίως ορατό σε οποιονδήποτε γνωρίζει το αναγνωριστικό της κοινότητας",
"Invite new community members": "Προσκαλέστε νέα μέλη στην κοινότητα",
"Name or matrix ID": "Όνομα ή αναγνωριστικό του matrix",
- "Invite to Community": "Πρόσκληση στην κοινότητα",
+ "Invite to Community": "Προσκαλέστε στην κοινότητα",
"Which rooms would you like to add to this community?": "Ποια δωμάτια θα θέλατε να προσθέσετε σε αυτή την κοινότητα;",
"Add rooms to the community": "Προσθήκη δωματίων στην κοινότητα",
"Add to community": "Προσθήκη στην κοινότητα",
- "Failed to invite the following users to %(groupId)s:": "Αποτυχία πρόσκλησης των ακόλουθων χρηστών στο %(groupId)s :",
+ "Failed to invite the following users to %(groupId)s:": "Αποτυχία πρόσκλησης στο %(groupId)s των χρηστών:",
"Failed to invite users to community": "Αποτυχία πρόσκλησης χρηστών στην κοινότητα",
"Failed to invite users to %(groupId)s": "Αποτυχία πρόσκλησης χρηστών στο %(groupId)s",
- "Failed to add the following rooms to %(groupId)s:": "Αποτυχία προσθήκης των ακόλουθων δωματίων στο %(groupId)s:",
+ "Failed to add the following rooms to %(groupId)s:": "Αποτυχία προσθήκης στο %(groupId)s των δωματίων:",
"There are unknown devices in this room: if you proceed without verifying them, it will be possible for someone to eavesdrop on your call.": "Υπάρχουν άγνωστες συσκευές στο δωμάτιο: εάν συνεχίσετε χωρίς να τις επιβεβαιώσετε, θα μπορούσε κάποιος να κρυφακούει την κλήση σας.",
"Show these rooms to non-members on the community page and room list?": "Εμφάνιση αυτών των δωματίων σε μη-μέλη στην σελίδα της κοινότητας και στη λίστα δωματίων;",
"Room name or alias": "Όνομα η ψευδώνυμο δωματίου",
- "Restricted": "Περιορισμένο",
- "Unable to create widget.": "Αδυναμία δημιουργίας widget.",
- "Reload widget": "Ανανέωση widget",
+ "Restricted": "Περιορισμένο/η",
+ "Unable to create widget.": "Αδυναμία δημιουργίας γραφικού στοιχείου.",
+ "Reload widget": "Επαναφόρτωση γραφικού στοιχείου",
"You are not in this room.": "Δεν είστε μέλος αυτού του δωματίου.",
"You do not have permission to do that in this room.": "Δεν έχετε την άδεια να το κάνετε αυτό σε αυτό το δωμάτιο.",
"You are now ignoring %(userId)s": "Τώρα αγνοείτε τον/την %(userId)s",
@@ -818,14 +818,14 @@
"Delete %(count)s devices|other": "Διαγραφή %(count)s συσκευών",
"Delete %(count)s devices|one": "Διαγραφή συσκευής",
"Select devices": "Επιλογή συσκευών",
- "Cannot add any more widgets": "Δεν είναι δυνατή η προσθήκη άλλων widget",
- "The maximum permitted number of widgets have already been added to this room.": "Ο μέγιστος επιτρεπτός αριθμός widget έχει ήδη προστεθεί σε αυτό το δωμάτιο.",
- "Add a widget": "Προσθήκη widget",
+ "Cannot add any more widgets": "Δεν είναι δυνατή η προσθήκη άλλων γραφικών στοιχείων",
+ "The maximum permitted number of widgets have already been added to this room.": "Ο μέγιστος επιτρεπτός αριθμός γραφικών στοιχείων έχει ήδη προστεθεί σε αυτό το δωμάτιο.",
+ "Add a widget": "Προσθέστε ένα γραφικό στοιχείο",
"%(senderName)s sent an image": "Ο/Η %(senderName)s έστειλε μία εικόνα",
"%(senderName)s sent a video": "Ο/Η %(senderName)s έστειλε ένα βίντεο",
"%(senderName)s uploaded a file": "Ο/Η %(senderName)s αναφόρτωσε ένα αρχείο",
"If your other devices do not have the key for this message you will not be able to decrypt them.": "Εάν οι άλλες συσκευές σας δεν έχουν το κλειδί για αυτό το μήνυμα, τότε δεν θα μπορείτε να το αποκρυπτογραφήσετε.",
- "Disinvite this user?": "Ακύρωση πρόσκλησης αυτού του χρήστη;",
+ "Disinvite this user?": "Απόσυρση της πρόσκλησης αυτού του χρήστη;",
"Mention": "Αναφορά",
"Invite": "Πρόσκληση",
"User Options": "Επιλογές Χρήστη",
@@ -849,5 +849,64 @@
"Seen by %(displayName)s (%(userName)s) at %(dateTime)s": "Διαβάστηκε από τον/την %(displayName)s (%(userName)s) στις %(dateTime)s",
"Room Notification": "Ειδοποίηση Δωματίου",
"Notify the whole room": "Ειδοποιήστε όλο το δωμάτιο",
- "Sets the room topic": "Ορίζει το θέμα του δωματίου"
+ "Sets the room topic": "Ορίζει το θέμα του δωματίου",
+ "Add Email Address": "Προσθήκη Διεύθυνσης Ηλ. Ταχυδρομείου",
+ "Add Phone Number": "Προσθήκη Τηλεφωνικού Αριθμού",
+ "Whether or not you're logged in (we don't record your username)": "Χωρίς να έχει σημασία εάν είστε συνδεδεμένοι (δεν καταγράφουμε το όνομα χρήστη σας)",
+ "Which officially provided instance you are using, if any": "Ποιά επίσημα παρεχόμενη έκδοση χρησιμοποιείτε, εάν χρησιμοποιείτε κάποια",
+ "Whether or not you're using the Richtext mode of the Rich Text Editor": "Χωρίς να έχει σημασία εάν χρησιμοποιείτε την λειτουργία \"Πλούσιο Κείμενο\" του Επεξεργαστή Πλουσίου Κειμένου",
+ "Whether or not you're using the 'breadcrumbs' feature (avatars above the room list)": "Χωρίς να έχει σημασία εάν χρησιμοποιείτε το χαρακτηριστικό 'ψίχουλα' (τα άβαταρ πάνω από την λίστα δωματίων)",
+ "Your User Agent": "Ο Πράκτορας Χρήστη σας",
+ "Where this page includes identifiable information, such as a room, user or group ID, that data is removed before being sent to the server.": "Όπου αυτή η σελίδα περιέχει αναγνωρίσιμες πληροφορίες, όπως ταυτότητα δωματίου, χρήστη ή ομάδας, αυτά τα δεδομένα αφαιρούνται πριν πραγματοποιηθεί αποστολή στον διακομιστή.",
+ "Call failed due to misconfigured server": "Η κλήση απέτυχε λόγω της λανθασμένης διάρθρωσης του διακομιστή",
+ "Please ask the administrator of your homeserver (%(homeserverDomain)s) to configure a TURN server in order for calls to work reliably.": "Παρακαλείστε να ρωτήσετε τον διαχειριστή του διακομιστή φιλοξενίας σας (%(homeserverDomain)s) να ρυθμίσουν έναν διακομιστή πρωτοκόλλου TURN ώστε οι κλήσεις να λειτουργούν απρόσκοπτα.",
+ "Alternatively, you can try to use the public server at turn.matrix.org, but this will not be as reliable, and it will share your IP address with that server. You can also manage this in Settings.": "Εναλλακτικά, δοκιμάστε να χρησιμοποιήσετε τον δημόσιο διακομιστή στο turn.matrix.org, αλλά δεν θα είναι το ίδιο απρόσκοπτο, και θα κοινοποιεί την διεύθυνση IP σας με τον διακομιστή. Μπορείτε επίσης να το διαχειριστείτε στις Ρυθμίσεις.",
+ "Try using turn.matrix.org": "Δοκιμάστε το turn.matrix.org",
+ "A conference call could not be started because the integrations server is not available": "Μια κλήση συνδιάσκεψης δεν μπορούσε να ξεκινήσει διότι ο διακομιστής ενσωμάτωσης είναι μη διαθέσιμος",
+ "Call in Progress": "Κλήση σε Εξέλιξη",
+ "A call is currently being placed!": "Μια κλήση πραγματοποιείτε τώρα!",
+ "A call is already in progress!": "Μια κλήση είναι σε εξέλιξη ήδη!",
+ "Permission Required": "Απαιτείται Άδεια",
+ "You do not have permission to start a conference call in this room": "Δεν έχετε άδεια για να ξεκινήσετε μια κλήση συνδιάσκεψης σε αυτό το δωμάτιο",
+ "Replying With Files": "Απαντώντας Με Αρχεία",
+ "At this time it is not possible to reply with a file. Would you like to upload this file without replying?": "Αυτήν την στιγμή δεν είναι δυνατό να απαντήσετε με αρχείο. Θα θέλατε να ανεβάσετε το αρχείο χωρίς να απαντήσετε;",
+ "The file '%(fileName)s' failed to upload.": "Απέτυχε το ανέβασμα του αρχείου '%(fileName)s'.",
+ "The file '%(fileName)s' exceeds this homeserver's size limit for uploads": "Το αρχείο '%(fileName)s' ξεπερνάει το όριο μεγέθους ανεβάσματος αυτού του διακομιστή φιλοξενίας",
+ "The server does not support the room version specified.": "Ο διακομιστής δεν υποστηρίζει την έκδοση του δωματίου που ορίστηκε.",
+ "Name or Matrix ID": "Όνομα ή ταυτότητα Matrix",
+ "Identity server has no terms of service": "Ο διακομιστής ταυτοποίησης δεν έχει όρους χρήσης",
+ "This action requires accessing the default identity server to validate an email address or phone number, but the server does not have any terms of service.": "Αυτή η δράση απαιτεί την πρόσβαση στο προκαθορισμένο διακομιστή ταυτοποίησης για να επιβεβαιώσει μια διεύθυνση ηλ. ταχυδρομείου ή αριθμό τηλεφώνου, αλλά ο διακομιστής δεν έχει όρους χρήσης.",
+ "Only continue if you trust the owner of the server.": "Συνεχίστε μόνο εάν εμπιστεύεστε τον ιδιοκτήτη του διακομιστή.",
+ "Trust": "Εμπιστοσύνη",
+ "Unable to load! Check your network connectivity and try again.": "Αδυναμία φόρτωσης! Ελέγξτε την σύνδεση του δικτύου και προσπαθήστε ξανά.",
+ "Registration Required": "Απαιτείτε Εγγραφή",
+ "You need to register to do this. Would you like to register now?": "Χρειάζεται να γίνει εγγραφή για αυτό. Θα θέλατε να κάνετε εγγραφή τώρα;",
+ "Email, name or Matrix ID": "Ηλ. ταχυδρομείο, όνομα ή ταυτότητα Matrix",
+ "Failed to start chat": "Αποτυχία αρχικοποίησης συνομιλίας",
+ "Failed to invite users to the room:": "Αποτυχία πρόσκλησης χρηστών στο δωμάτιο:",
+ "Missing roomId.": "Λείπει η ταυτότητα δωματίου.",
+ "Messages": "Μηνύματα",
+ "Actions": "Δράσεις",
+ "Other": "Άλλα",
+ "Prepends ¯\\_(ツ)_/¯ to a plain-text message": "Προ-εισάγει ¯\\_(ツ)_/¯ σε ένα μήνυμα απλού κειμένου",
+ "Sends a message as plain text, without interpreting it as markdown": "Αποστέλλει ένα μήνυμα ως απλό κείμενο, χωρίς να το ερμηνεύει ως \"markdown\"",
+ "Upgrades a room to a new version": "Αναβαθμίζει το δωμάτιο σε μια καινούργια έκδοση",
+ "You do not have the required permissions to use this command.": "Δεν διαθέτετε τις απαιτούμενες άδειες για να χρησιμοποιήσετε αυτήν την εντολή.",
+ "Room upgrade confirmation": "Επιβεβαίωση αναβάθμισης δωματίου",
+ "Upgrading a room can be destructive and isn't always necessary.": "Η αναβάθμιση ενός δωματίου μπορεί να είναι καταστροφική και δεν είναι πάντα απαραίτητη.",
+ "Room upgrades are usually recommended when a room version is considered unstable. Unstable room versions might have bugs, missing features, or security vulnerabilities.": "Οι αναβαθμίσεις δωματίου είναι συνήθως προτεινόμενες όταν μια έκδοση δωματίου θεωρείτε ασταθής. Ασταθείς εκδόσεις δωματίων μπορεί να έχουν σφάλματα, ελλειπή χαρακτηριστικά, ή αδυναμίες ασφαλείας.",
+ "Room upgrades usually only affect server-side processing of the room. If you're having problems with your Riot client, please file an issue with .": "Οι αναβαθμίσεις δωματίων συνήθως επηρεάζουν μόνο την επεξεργασία του δωματίου από την πλευρά του διακομιστή. Εάν έχετε προβλήματα με το πρόγραμμα-πελάτη Riot, παρακαλώ αρχειοθετήστε ένα πρόβλημα μέσω .",
+ "Warning: Upgrading a room will not automatically migrate room members to the new version of the room. We'll post a link to the new room in the old version of the room - room members will have to click this link to join the new room.": "Προσοχή: Αναβαθμίζοντας ένα δωμάτιο δεν θα μεταφέρει αυτόματα τα μέλη του δωματίου στη νέα έκδοση του δωματίου. Θα αναρτήσουμε ένα σύνδεσμο προς το νέο δωμάτιο στη παλιά έκδοση του δωματίου - τα μέλη του δωματίου θα πρέπει να πατήσουν στον σύνδεσμο για να μπουν στο νέο δωμάτιο.",
+ "Please confirm that you'd like to go forward with upgrading this room from to .": "Παρακαλώ επιβεβαιώστε ότι θα θέλατε να προχωρήσετε με την αναβάθμιση του δωματίου από σε .",
+ "Upgrade": "Αναβάθμιση",
+ "Changes your display nickname in the current room only": "Αλλάζει το εμφανιζόμενο ψευδώνυμο μόνο στο παρόν δωμάτιο",
+ "Changes the avatar of the current room": "Αλλάζει το άβαταρ αυτού του δωματίου",
+ "Changes your avatar in this current room only": "Αλλάζει το άβαταρ σας μόνο στο παρόν δωμάτιο",
+ "Changes your avatar in all rooms": "Αλλάζει το άβαταρ σας σε όλα τα δωμάτια",
+ "Gets or sets the room topic": "Λαμβάνει ή θέτει το θέμα του δωματίου",
+ "This room has no topic.": "Το δωμάτιο αυτό δεν έχει κανένα θέμα.",
+ "Sets the room name": "Θέτει το θέμα του δωματίου",
+ "Use an identity server": "Χρησιμοποιήστε ένα διακομιστή ταυτοτήτων",
+ "Your Riot is misconfigured": "Οι παράμετροι του Riot σας είναι λανθασμένα ρυθμισμένοι",
+ "Explore rooms": "Εξερευνήστε δωμάτια"
}
From 84d23676d6002005c7ff5b906e383eeef1300647 Mon Sep 17 00:00:00 2001
From: Szimszon
Date: Mon, 4 Nov 2019 21:14:13 +0000
Subject: [PATCH 027/334] Translated using Weblate (Hungarian)
Currently translated at 100.0% (1853 of 1853 strings)
Translation: Riot Web/matrix-react-sdk
Translate-URL: https://translate.riot.im/projects/riot-web/matrix-react-sdk/hu/
---
src/i18n/strings/hu.json | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/src/i18n/strings/hu.json b/src/i18n/strings/hu.json
index 4946c7b14f..5d21a17e37 100644
--- a/src/i18n/strings/hu.json
+++ b/src/i18n/strings/hu.json
@@ -2266,5 +2266,6 @@
"Unread messages.": "Olvasatlan üzenetek.",
"Show tray icon and minimize window to it on close": "Tálcaikon mutatása és az ablak összecsukása bezáráskor",
"This action requires accessing the default identity server to validate an email address or phone number, but the server does not have any terms of service.": "Ez a művelet az e-mail cím vagy telefonszám ellenőrzése miatt hozzáférést igényel az alapértelmezett azonosítási szerverhez (), de a szervernek nincsen semmilyen felhasználási feltétele.",
- "Trust": "Megbízom benne"
+ "Trust": "Megbízom benne",
+ "Message Actions": "Üzenet Műveletek"
}
From 8b3844f83b6d47053d01ffa391c51b35255c9118 Mon Sep 17 00:00:00 2001
From: shuji narazaki
Date: Thu, 7 Nov 2019 23:12:55 +0000
Subject: [PATCH 028/334] Translated using Weblate (Japanese)
Currently translated at 60.8% (1126 of 1853 strings)
Translation: Riot Web/matrix-react-sdk
Translate-URL: https://translate.riot.im/projects/riot-web/matrix-react-sdk/ja/
---
src/i18n/strings/ja.json | 39 ++++++++++++++++++++++++++++++++++++++-
1 file changed, 38 insertions(+), 1 deletion(-)
diff --git a/src/i18n/strings/ja.json b/src/i18n/strings/ja.json
index 23199094e8..5e3fecaed9 100644
--- a/src/i18n/strings/ja.json
+++ b/src/i18n/strings/ja.json
@@ -1343,5 +1343,42 @@
"%(senderDisplayName)s upgraded this room.": "%(senderDisplayName)s はこの部屋をアップグレードしました。",
"%(senderDisplayName)s made the room public to whoever knows the link.": "%(senderDisplayName)s はこの部屋をリンクを知っている人全てに公開しました。",
"%(senderDisplayName)s made the room invite only.": "%(senderDisplayName)s はこの部屋を招待者のみに変更しました。",
- "%(senderDisplayName)s has allowed guests to join the room.": "%(senderDisplayName)s はゲストがこの部屋に参加できるようにしました。"
+ "%(senderDisplayName)s has allowed guests to join the room.": "%(senderDisplayName)s はゲストがこの部屋に参加できるようにしました。",
+ "Whether or not you're using the 'breadcrumbs' feature (avatars above the room list)": "「パンくずリスト」機能(部屋リストの上のアバター)を使っているかどうか",
+ "Only continue if you trust the owner of the server.": "そのサーバーの所有者を信頼する場合のみ続ける。",
+ "Trust": "信頼",
+ "Use an identity server to invite by email. Manage in Settings.": "メールによる招待のためにIDサーバーを用いる。設定画面で管理する。",
+ "%(senderDisplayName)s changed the join rule to %(rule)s": "%(senderDisplayName)s は参加ルールを %(rule)s に変更しました",
+ "%(senderDisplayName)s has prevented guests from joining the room.": "%(senderDisplayName)s はゲストの部屋への参加を差し止めています。",
+ "%(senderDisplayName)s changed guest access to %(rule)s": "%(senderDisplayName)s はゲストのアクセスを %(rule)s に変更しました",
+ "%(displayName)s is typing …": "%(displayName)s が入力中 …",
+ "%(names)s and %(count)s others are typing …|other": "%(names)s と他 %(count)s 名が入力中 …",
+ "%(names)s and %(count)s others are typing …|one": "%(names)s ともう一人が入力中 …",
+ "%(names)s and %(lastPerson)s are typing …": "%(names)s と %(lastPerson)s が入力中 …",
+ "Cannot reach homeserver": "ホームサーバーに接続できません",
+ "Your Riot is misconfigured": "あなたのRiotは設定が間違っています",
+ "Cannot reach identity server": "IDサーバーに接続できません",
+ "No homeserver URL provided": "ホームサーバーのURLが与えられていません",
+ "User %(userId)s is already in the room": "ユーザー %(userId)s はすでにその部屋にいます",
+ "User %(user_id)s does not exist": "ユーザー %(user_id)s は存在しません",
+ "The user's homeserver does not support the version of the room.": "そのユーザーのホームサーバーはその部屋のバージョンに対応していません。",
+ "Use a few words, avoid common phrases": "ありふれた語句を避けて、いくつかの単語を使ってください",
+ "This is a top-10 common password": "これがよく使われるパスワードの上位10個です",
+ "This is a top-100 common password": "これがよく使われるパスワードの上位100個です",
+ "This is a very common password": "これはとてもよく使われるパスワードです",
+ "This is similar to a commonly used password": "これはよく使われるパスワードに似ています",
+ "A word by itself is easy to guess": "単語一つだけだと簡単に特定されます",
+ "Custom user status messages": "ユーザーステータスのメッセージをカスタマイズする",
+ "Render simple counters in room header": "部屋のヘッダーに簡単なカウンターを表示する",
+ "Use the new, faster, composer for writing messages": "メッセージの編集に新しい高速なコンポーザーを使う",
+ "Enable Emoji suggestions while typing": "入力中の絵文字提案機能を有効にする",
+ "Show avatar changes": "アバターの変更を表示する",
+ "Show display name changes": "表示名の変更を表示する",
+ "Show read receipts sent by other users": "他の人の既読情報を表示する",
+ "Enable big emoji in chat": "チャットで大きな絵文字を有効にする",
+ "Send typing notifications": "入力中であることを通知する",
+ "Enable Community Filter Panel": "コミュニティーフィルターパネルを有効にする",
+ "Show recently visited rooms above the room list": "最近訪問した部屋をリストの上位に表示する",
+ "Low bandwidth mode": "低帯域通信モード",
+ "Trust & Devices": "信頼と端末"
}
From defe3fb5f81e2d34d4c91fcd43e820e30c2b2f3d Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?=EC=86=A1=ED=83=9C=EC=84=AD?=
Date: Mon, 4 Nov 2019 15:39:35 +0000
Subject: [PATCH 029/334] Translated using Weblate (Korean)
Currently translated at 100.0% (1853 of 1853 strings)
Translation: Riot Web/matrix-react-sdk
Translate-URL: https://translate.riot.im/projects/riot-web/matrix-react-sdk/ko/
---
src/i18n/strings/ko.json | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/src/i18n/strings/ko.json b/src/i18n/strings/ko.json
index 1aebd0ce17..54425657cf 100644
--- a/src/i18n/strings/ko.json
+++ b/src/i18n/strings/ko.json
@@ -2123,5 +2123,6 @@
"Unread messages.": "읽지 않은 메시지.",
"Show tray icon and minimize window to it on close": "닫을 때 창을 최소화하고 트레이 아이콘으로 표시하기",
"This action requires accessing the default identity server to validate an email address or phone number, but the server does not have any terms of service.": "이 작업에는 이메일 주소 또는 전화번호를 확인하기 위해 기본 ID 서버 에 접근해야 합니다. 하지만 서버가 서비스 약관을 갖고 있지 않습니다.",
- "Trust": "신뢰함"
+ "Trust": "신뢰함",
+ "Message Actions": "메시지 동작"
}
From 824a26549644de047d6dd75d46b9bde0babd47a6 Mon Sep 17 00:00:00 2001
From: MamasLT
Date: Mon, 4 Nov 2019 22:55:04 +0000
Subject: [PATCH 030/334] Translated using Weblate (Lithuanian)
Currently translated at 50.4% (933 of 1853 strings)
Translation: Riot Web/matrix-react-sdk
Translate-URL: https://translate.riot.im/projects/riot-web/matrix-react-sdk/lt/
---
src/i18n/strings/lt.json | 271 ++++++++++++++++++++++++++++-----------
1 file changed, 194 insertions(+), 77 deletions(-)
diff --git a/src/i18n/strings/lt.json b/src/i18n/strings/lt.json
index 6a8076ac96..2aeb207387 100644
--- a/src/i18n/strings/lt.json
+++ b/src/i18n/strings/lt.json
@@ -1,17 +1,17 @@
{
"This email address is already in use": "Šis el. pašto adresas jau naudojamas",
"This phone number is already in use": "Šis telefono numeris jau naudojamas",
- "Failed to verify email address: make sure you clicked the link in the email": "Nepavyko patvirtinti el. pašto adreso: įsitikinkite, kad gautame el. laiške spustelėjote nuorodą",
+ "Failed to verify email address: make sure you clicked the link in the email": "Nepavyko patvirtinti el. pašto adreso: įsitikinkite, kad paspaudėte nuorodą el. laiške",
"The platform you're on": "Jūsų naudojama platforma",
"The version of Riot.im": "Riot.im versija",
"Whether or not you're logged in (we don't record your user name)": "Nesvarbu ar esate prisijungę ar ne (mes neįrašome jūsų naudotojo vardo)",
"Your language of choice": "Jūsų pasirinkta kalba",
- "Which officially provided instance you are using, if any": "Kurį oficialiai pateiktą egzempliorių naudojate",
- "Whether or not you're using the Richtext mode of the Rich Text Editor": "Ar jūs naudojate Raiškiojo Teksto Redaktoriaus Raiškiojo Teksto režimą ar ne",
- "Your homeserver's URL": "Jūsų serverio URL adresas",
- "Your identity server's URL": "Jūsų identifikavimo serverio URL adresas",
+ "Which officially provided instance you are using, if any": "Kurią oficialiai teikiamą instanciją naudojate, jei tokių yra",
+ "Whether or not you're using the Richtext mode of the Rich Text Editor": "Nepriklausomai nuo to ar jūs naudojate Raiškiojo Teksto Redaktoriaus Raiškiojo Teksto režimą",
+ "Your homeserver's URL": "Jūsų serverio URL",
+ "Your identity server's URL": "Jūsų tapatybės serverio URL",
"Analytics": "Statistika",
- "The information being sent to us to help make Riot.im better includes:": "Informacijoje, kuri yra siunčiama Riot.im tobulinimui yra:",
+ "The information being sent to us to help make Riot.im better includes:": "Informacija, siunčiama mums, kad padėtų tobulinti Riot.im, apima:",
"Fetching third party location failed": "Nepavyko gauti trečios šalies vietos",
"A new version of Riot is available.": "Yra prieinama nauja Riot versija.",
"I understand the risks and wish to continue": "Aš suprantu riziką ir noriu tęsti",
@@ -196,7 +196,7 @@
"Answer Anyway": "Vis tiek atsiliepti",
"Call": "Skambinti",
"Answer": "Atsiliepti",
- "Unable to capture screen": "Nepavyko nufotografuoti ekraną",
+ "Unable to capture screen": "Nepavyko nufotografuoti ekrano",
"You are already in a call.": "Jūs jau dalyvaujate skambutyje.",
"VoIP is unsupported": "VoIP yra nepalaikoma",
"Could not connect to the integration server": "Nepavyko prisijungti prie integracijos serverio",
@@ -210,18 +210,18 @@
"Thu": "Ket",
"Fri": "Pen",
"Sat": "Šeš",
- "Jan": "Sau",
+ "Jan": "Sausis",
"Feb": "Vas",
- "Mar": "Kov",
+ "Mar": "Kovas",
"Apr": "Bal",
"May": "Geg",
- "Jun": "Bir",
- "Jul": "Lie",
- "Aug": "Rgp",
- "Sep": "Rgs",
- "Oct": "Spa",
- "Nov": "Lap",
- "Dec": "Gru",
+ "Jun": "Birž",
+ "Jul": "Liepa",
+ "Aug": "Rugpj",
+ "Sep": "Rugs",
+ "Oct": "Spalis",
+ "Nov": "Lapkr",
+ "Dec": "Gruodis",
"PM": "PM",
"AM": "AM",
"%(weekDayName)s %(time)s": "%(weekDayName)s %(time)s",
@@ -229,16 +229,16 @@
"%(weekDayName)s, %(monthName)s %(day)s %(fullYear)s": "%(weekDayName)s, %(fullYear)s %(monthName)s %(day)s",
"%(weekDayName)s, %(monthName)s %(day)s %(fullYear)s %(time)s": "%(weekDayName)s, %(fullYear)s %(monthName)s %(day)s %(time)s",
"Who would you like to add to this community?": "Ką norėtumėte pridėti į šią bendruomenę?",
- "Warning: any person you add to a community will be publicly visible to anyone who knows the community ID": "Įspėjimas: bet kuris pridėtas asmuo bus matomas visiems, žinantiems bendruomenės ID",
+ "Warning: any person you add to a community will be publicly visible to anyone who knows the community ID": "Įspėjimas: bet kuris jūsų pridėtas asmuo bus viešai matomas visiems, žinantiems bendruomenės ID",
"Name or matrix ID": "Vardas ar matrix ID",
"Invite to Community": "Pakviesti į bendruomenę",
"Which rooms would you like to add to this community?": "Kuriuos kambarius norėtumėte pridėti į šią bendruomenę?",
"Add rooms to the community": "Pridėti kambarius į bendruomenę",
"Add to community": "Pridėti į bendruomenę",
- "Failed to invite the following users to %(groupId)s:": "Nepavyko pakviesti šių naudotojų į %(groupId)s:",
- "Failed to invite users to community": "Nepavyko pakviesti naudotojus į bendruomenę",
- "Failed to invite users to %(groupId)s": "Nepavyko pakviesti naudotojų į %(groupId)s",
- "Failed to add the following rooms to %(groupId)s:": "Nepavyko pridėti šiuos kambarius į %(groupId)s:",
+ "Failed to invite the following users to %(groupId)s:": "Nepavyko pakviesti šių vartotojų į %(groupId)s:",
+ "Failed to invite users to community": "Nepavyko pakviesti vartotojų į bendruomenę",
+ "Failed to invite users to %(groupId)s": "Nepavyko pakviesti vartotojų į %(groupId)s",
+ "Failed to add the following rooms to %(groupId)s:": "Nepavyko pridėti šių kambarių į %(groupId)s:",
"Riot does not have permission to send you notifications - please check your browser settings": "Riot neturi leidimo siųsti jums pranešimus - patikrinkite savo naršyklės nustatymus",
"Riot was not given permission to send notifications - please try again": "Riot nebuvo suteiktas leidimas siųsti pranešimus - bandykite dar kartą",
"Unable to enable Notifications": "Nepavyko įjungti Pranešimus",
@@ -251,7 +251,7 @@
"Send Invites": "Siųsti pakvietimus",
"Failed to invite user": "Nepavyko pakviesti naudotojo",
"Failed to invite": "Nepavyko pakviesti",
- "Failed to invite the following users to the %(roomName)s room:": "Nepavyko pakviesti šių naudotojų į kambarį %(roomName)s :",
+ "Failed to invite the following users to the %(roomName)s room:": "Nepavyko pakviesti šių vartotojų į kambarį %(roomName)s:",
"You need to be logged in.": "Turite būti prisijungę.",
"Unable to create widget.": "Nepavyko sukurti valdiklio.",
"Failed to send request.": "Nepavyko išsiųsti užklausos.",
@@ -263,8 +263,8 @@
"Changes your display nickname": "Pakeičia jūsų rodomą slapyvardį",
"Sets the room topic": "Nustato kambario temą",
"Invites user with given id to current room": "Pakviečia naudotoją su nurodytu id į esamą kambarį",
- "You are now ignoring %(userId)s": "Dabar nepaisote %(userId)s",
- "Opens the Developer Tools dialog": "Atveria kūrėjo įrankių dialogą",
+ "You are now ignoring %(userId)s": "Dabar ignoruojate %(userId)s",
+ "Opens the Developer Tools dialog": "Atveria programuotojo įrankių dialogą",
"Unknown (user, device) pair:": "Nežinoma pora (naudotojas, įrenginys):",
"Device already verified!": "Įrenginys jau patvirtintas!",
"WARNING: Device already verified, but keys do NOT MATCH!": "ĮSPĖJIMAS: Įrenginys jau patvirtintas, tačiau raktai NESUTAMPA!",
@@ -273,26 +273,26 @@
"Unrecognised command:": "Neatpažinta komanda:",
"Reason": "Priežastis",
"%(targetName)s accepted an invitation.": "%(targetName)s priėmė pakvietimą.",
- "%(senderName)s invited %(targetName)s.": "%(senderName)s pakvietė naudotoją %(targetName)s.",
- "%(oldDisplayName)s changed their display name to %(displayName)s.": "%(oldDisplayName)s pasikeitė savo rodomą vardą į %(displayName)s.",
- "%(senderName)s set their display name to %(displayName)s.": "%(senderName)s nusistatė savo rodomą vardą į %(displayName)s.",
- "%(senderName)s removed their display name (%(oldDisplayName)s).": "%(senderName)s pašalino savo rodomą vardą (%(oldDisplayName)s).",
+ "%(senderName)s invited %(targetName)s.": "%(senderName)s pakvietė %(targetName)s.",
+ "%(oldDisplayName)s changed their display name to %(displayName)s.": "%(oldDisplayName)s pakeitė savo vardą į %(displayName)s.",
+ "%(senderName)s set their display name to %(displayName)s.": "%(senderName)s nustatė savo vardą į %(displayName)s.",
+ "%(senderName)s removed their display name (%(oldDisplayName)s).": "%(senderName)s pašalino savo vardą (%(oldDisplayName)s).",
"%(senderName)s removed their profile picture.": "%(senderName)s pašalino savo profilio paveikslą.",
- "%(senderName)s changed their profile picture.": "%(senderName)s pasikeitė savo profilio paveikslą.",
- "%(senderName)s set a profile picture.": "%(senderName)s nusistatė profilio paveikslą.",
+ "%(senderName)s changed their profile picture.": "%(senderName)s pakeitė savo profilio paveikslą.",
+ "%(senderName)s set a profile picture.": "%(senderName)s nustatė profilio paveikslą.",
"%(targetName)s rejected the invitation.": "%(targetName)s atmetė pakvietimą.",
"%(senderDisplayName)s changed the topic to \"%(topic)s\".": "%(senderDisplayName)s pakeitė temą į \"%(topic)s\".",
"%(senderDisplayName)s changed the room name to %(roomName)s.": "%(senderDisplayName)s pakeitė kambario pavadinimą į %(roomName)s.",
"%(senderDisplayName)s sent an image.": "%(senderDisplayName)s išsiuntė paveikslą.",
"Someone": "Kažkas",
"%(senderName)s answered the call.": "%(senderName)s atsiliepė į skambutį.",
- "(unknown failure: %(reason)s)": "(nežinoma lemtingoji klaida: %(reason)s)",
+ "(unknown failure: %(reason)s)": "(nežinoma klaida: %(reason)s)",
"%(senderName)s ended the call.": "%(senderName)s užbaigė skambutį.",
"%(displayName)s is typing": "%(displayName)s rašo",
"%(names)s and %(count)s others are typing|other": "%(names)s ir dar kiti %(count)s rašo",
"%(names)s and %(lastPerson)s are typing": "%(names)s ir %(lastPerson)s rašo",
"Send anyway": "Vis tiek siųsti",
- "Unnamed Room": "Kambarys be pavadinimo",
+ "Unnamed Room": "Bevardis kambarys",
"Hide removed messages": "Slėpti pašalintas žinutes",
"Hide display name changes": "Slėpti rodomo vardo pakeitimus",
"Show timestamps in 12 hour format (e.g. 2:30pm)": "Rodyti laiko žymas 12 valandų formatu (pvz., 2:30pm)",
@@ -372,7 +372,7 @@
"Settings": "Nustatymai",
"Show panel": "Rodyti skydelį",
"Press to start a chat with someone": "Norėdami pradėti su kuo nors pokalbį, paspauskite ",
- "Community Invites": "",
+ "Community Invites": "Bendruomenės pakvietimai",
"People": "Žmonės",
"Reason: %(reasonText)s": "Priežastis: %(reasonText)s",
"%(roomName)s does not exist.": "%(roomName)s nėra.",
@@ -576,27 +576,27 @@
"The export file will be protected with a passphrase. You should enter the passphrase here, to decrypt the file.": "Eksportavimo failas bus apsaugotas slaptafraze. Norėdami iššifruoti failą, čia turėtumėte įvesti slaptafrazę.",
"File to import": "Failas, kurį importuoti",
"Import": "Importuoti",
- "Your User Agent": "Jūsų naudotojo agentas",
+ "Your User Agent": "Jūsų vartotojo agentas",
"Review Devices": "Peržiūrėti įrenginius",
"You do not have permission to start a conference call in this room": "Jūs neturite leidimo šiame kambaryje pradėti konferencinį pokalbį",
"The file '%(fileName)s' exceeds this home server's size limit for uploads": "Failas \"%(fileName)s\" viršija šio namų serverio įkeliamų failų dydžio apribojimą",
"Room name or alias": "Kambario pavadinimas ar slapyvardis",
- "Your email address does not appear to be associated with a Matrix ID on this Homeserver.": "Neatrodo, kad jūsų el. pašto adresas šiame namų serveryje būtų susietas su Matrix ID.",
+ "Your email address does not appear to be associated with a Matrix ID on this Homeserver.": "Neatrodo, kad jūsų el. pašto adresas šiame serveryje būtų susietas su Matrix ID.",
"Who would you like to communicate with?": "Su kuo norėtumėte susisiekti?",
"Missing room_id in request": "Užklausoje trūksta room_id",
"Missing user_id in request": "Užklausoje trūksta user_id",
"Unrecognised room alias:": "Neatpažintas kambario slapyvardis:",
- "WARNING: KEY VERIFICATION FAILED! The signing key for %(userId)s and device %(deviceId)s is \"%(fprint)s\" which does not match the provided key \"%(fingerprint)s\". This could mean your communications are being intercepted!": "ĮSPĖJIMAS: RAKTO PATVIRTINIMAS NEPAVYKO! Pasirašymo raktas, skirtas %(userId)s ir įrenginiui %(deviceId)s yra \"%(fprint)s\", o tai nesutampa su pateiktu raktu \"%(fingerprint)s\". Tai gali reikšti, kad kažkas perima jūsų komunikavimą!",
- "The signing key you provided matches the signing key you received from %(userId)s's device %(deviceId)s. Device marked as verified.": "Jūsų pateiktas pasirašymo raktas sutampa su pasirašymo raktus, kuris gautas iš naudotojo %(userId)s įrenginio %(deviceId)s. Įrenginys pažymėtas kaip patvirtintas.",
+ "WARNING: KEY VERIFICATION FAILED! The signing key for %(userId)s and device %(deviceId)s is \"%(fprint)s\" which does not match the provided key \"%(fingerprint)s\". This could mean your communications are being intercepted!": "ĮSPĖJIMAS: RAKTO PATVIRTINIMAS NEPAVYKO! Pasirašymo raktas, skirtas %(userId)s ir įrenginiui %(deviceId)s yra \"%(fprint)s\", o tai nesutampa su pateiktu raktu \"%(fingerprint)s\". Tai gali reikšti, kad jūsų komunikacijos yra perimamos!",
+ "The signing key you provided matches the signing key you received from %(userId)s's device %(deviceId)s. Device marked as verified.": "Jūsų pateiktas pasirašymo raktas sutampa su pasirašymo raktu, kurį gavote iš vartotojo %(userId)s įrenginio %(deviceId)s. Įrenginys pažymėtas kaip patvirtintas.",
"VoIP conference started.": "VoIP konferencija pradėta.",
"VoIP conference finished.": "VoIP konferencija užbaigta.",
"%(senderDisplayName)s removed the room name.": "%(senderDisplayName)s pašalino kambario pavadinimą.",
- "%(senderName)s turned on end-to-end encryption (algorithm %(algorithm)s).": "%(senderName)s įjungė ištisinį šifravimą (%(algorithm)s algoritmas).",
+ "%(senderName)s turned on end-to-end encryption (algorithm %(algorithm)s).": "%(senderName)s įjungė end-to-end šifravimą (%(algorithm)s algoritmas).",
"%(widgetName)s widget modified by %(senderName)s": "%(senderName)s modifikavo %(widgetName)s valdiklį",
"%(widgetName)s widget added by %(senderName)s": "%(senderName)s pridėjo %(widgetName)s valdiklį",
"%(widgetName)s widget removed by %(senderName)s": "%(senderName)s pašalino %(widgetName)s valdiklį",
"Failure to create room": "Nepavyko sukurti kambarį",
- "Server may be unavailable, overloaded, or you hit a bug.": "Gali būti, kad serveris neprieinamas, perkrautas arba susidūrėte su klaida.",
+ "Server may be unavailable, overloaded, or you hit a bug.": "Serveris gali būti neprieinamas, per daug apkrautas, arba susidūrėte su klaida.",
"Use compact timeline layout": "Naudoti kompaktišką laiko juostos išdėstymą",
"Autoplay GIFs and videos": "Automatiškai atkurti GIF ir vaizdo įrašus",
"Never send encrypted messages to unverified devices from this device": "Niekada nesiųsti iš šio įrenginio šifruotų žinučių į nepatvirtintus įrenginius",
@@ -624,30 +624,30 @@
"Invited": "Pakviestas",
"Filter room members": "Filtruoti kambario dalyvius",
"Server unavailable, overloaded, or something else went wrong.": "Serveris neprieinamas, perkrautas arba nutiko kažkas kito.",
- "%(duration)ss": "%(duration)s sek.",
- "%(duration)sm": "%(duration)s min.",
- "%(duration)sh": "%(duration)s val.",
- "%(duration)sd": "%(duration)s d.",
+ "%(duration)ss": "%(duration)s sek",
+ "%(duration)sm": "%(duration)s min",
+ "%(duration)sh": "%(duration)s val",
+ "%(duration)sd": "%(duration)s d",
"Seen by %(userName)s at %(dateTime)s": "%(userName)s matė ties %(dateTime)s",
"Seen by %(displayName)s (%(userName)s) at %(dateTime)s": "%(displayName)s (%(userName)s) matė ties %(dateTime)s",
- "Show these rooms to non-members on the community page and room list?": "Ar rodyti šiuos kambarius ne dalyviams bendruomenės puslapyje ir kambarių sąraše?",
+ "Show these rooms to non-members on the community page and room list?": "Rodyti šiuos kambarius ne nariams bendruomenės puslapyje ir kambarių sąraše?",
"Invite new room members": "Pakviesti naujus kambario dalyvius",
"Changes colour scheme of current room": "Pakeičia esamo kambario spalvų rinkinį",
- "Kicks user with given id": "Išmeta naudotoją su nurodytu id",
- "Bans user with given id": "Užblokuoja naudotoja su nurodytu id",
+ "Kicks user with given id": "Išmeta vartotoją su nurodytu id",
+ "Bans user with given id": "Užblokuoja vartotoją su nurodytu id",
"Unbans user with given id": "Atblokuoja naudotoją su nurodytu id",
- "%(senderName)s banned %(targetName)s.": "%(senderName)s užblokavo naudotoją %(targetName)s.",
- "%(senderName)s unbanned %(targetName)s.": "%(senderName)s atblokavo naudotoją %(targetName)s.",
- "%(senderName)s kicked %(targetName)s.": "%(senderName)s išmetė naudotoją %(targetName)s.",
+ "%(senderName)s banned %(targetName)s.": "%(senderName)s užblokavo %(targetName)s.",
+ "%(senderName)s unbanned %(targetName)s.": "%(senderName)s atblokavo %(targetName)s.",
+ "%(senderName)s kicked %(targetName)s.": "%(senderName)s išmetė %(targetName)s.",
"(not supported by this browser)": "(nėra palaikoma šios naršyklės)",
"(no answer)": "(nėra atsakymo)",
- "%(senderName)s made future room history visible to all room members, from the point they are invited.": "%(senderName)s padarė kambario ateities istoriją matomą visiems kambario dalyviams nuo to laiko, kai jie buvo pakviesti.",
- "%(senderName)s made future room history visible to all room members.": "%(senderName)s padarė kambario ateities istoriją matomą visiems kambario dalyviams.",
- "%(senderName)s made future room history visible to anyone.": "%(senderName)s padarė kambario ateities istoriją matomą bet kam.",
+ "%(senderName)s made future room history visible to all room members, from the point they are invited.": "%(senderName)s padarė būsimą kambario istoriją matomą visiems kambario dalyviams, nuo pat jų pakvietimo.",
+ "%(senderName)s made future room history visible to all room members.": "%(senderName)s padarė būsimą kambario istoriją matomą visiems kambario dalyviams.",
+ "%(senderName)s made future room history visible to anyone.": "%(senderName)s padarė būsimą kambario istoriją matomą bet kam.",
"%(names)s and %(count)s others are typing|one": "%(names)s ir dar vienas naudotojas rašo",
"Your browser does not support the required cryptography extensions": "Jūsų naršyklė nepalaiko reikalingų kriptografijos plėtinių",
"Not a valid Riot keyfile": "Negaliojantis Riot rakto failas",
- "Authentication check failed: incorrect password?": "Tapatybės nustatymo patikrinimas patyrė nesėkmę: neteisingas slaptažodis?",
+ "Authentication check failed: incorrect password?": "Autentifikavimo patikra nepavyko: neteisingas slaptažodis?",
"Send analytics data": "Siųsti analitinius duomenis",
"Incoming voice call from %(name)s": "Gaunamasis balso skambutis nuo %(name)s",
"Incoming video call from %(name)s": "Gaunamasis vaizdo skambutis nuo %(name)s",
@@ -672,14 +672,14 @@
"Failed to set avatar.": "Nepavyko nustatyti avataro.",
"Forget room": "Pamiršti kambarį",
"Share room": "Bendrinti kambarį",
- "There are unknown devices in this room: if you proceed without verifying them, it will be possible for someone to eavesdrop on your call.": "Šiame kambaryje yra nepatvirtintų įrenginių: jeigu tęsite jų nepatvirtinę, tuomet kas nors galės slapta klausytis jūsų skambučio.",
+ "There are unknown devices in this room: if you proceed without verifying them, it will be possible for someone to eavesdrop on your call.": "Šiame kambaryje yra nežinomų įrenginių: jei tęsite jų nepatvirtinę, kam nors bus įmanoma slapta klausytis jūsų skambučio.",
"Usage": "Naudojimas",
"Searches DuckDuckGo for results": "Atlieka rezultatų paiešką sistemoje DuckDuckGo",
- "To use it, just wait for autocomplete results to load and tab through them.": "Norėdami tai naudoti, tiesiog, palaukite, kol bus įkelti automatiškai užbaigti rezultatai, o tuomet, pereikite per juos naudodami Tab klavišą.",
+ "To use it, just wait for autocomplete results to load and tab through them.": "Norėdami tai naudoti, tiesiog palaukite, kol bus įkelti automatiškai užbaigti rezultatai, tuomet pereikite per juos naudodami Tab klavišą.",
"%(targetName)s left the room.": "%(targetName)s išėjo iš kambario.",
"%(senderName)s changed the pinned messages for the room.": "%(senderName)s pakeitė prisegtas kambario žinutes.",
- "Sorry, your homeserver is too old to participate in this room.": "Atleiskite, jūsų namų serveris yra per senas dalyvauti šiame kambaryje.",
- "Please contact your homeserver administrator.": "Prašome susisiekti su savo namų serverio administratoriumi.",
+ "Sorry, your homeserver is too old to participate in this room.": "Atleiskite, jūsų serverio versija yra per sena dalyvauti šiame kambaryje.",
+ "Please contact your homeserver administrator.": "Prašome susisiekti su savo serverio administratoriumi.",
"Enable inline URL previews by default": "Įjungti tiesiogines URL nuorodų peržiūras pagal numatymą",
"Enable URL previews for this room (only affects you)": "Įjungti URL nuorodų peržiūras šiame kambaryje (įtakoja tik jus)",
"Enable URL previews by default for participants in this room": "Įjungti URL nuorodų peržiūras pagal numatymą dalyviams šiame kambaryje",
@@ -731,7 +731,7 @@
"If you don't specify an email address, you won't be able to reset your password. Are you sure?": "Jeigu nenurodysite savo el. pašto adreso, negalėsite atstatyti savo slaptažodį. Ar esate tikri?",
"Home server URL": "Namų serverio URL",
"Identity server URL": "Tapatybės serverio URL",
- "Please contact your service administrator to continue using the service.": "Norėdami tęsti naudotis paslauga, susisiekite su savo paslaugos administratoriumi.",
+ "Please contact your service administrator to continue using the service.": "Norėdami toliau naudotis šia paslauga, susisiekite su savo paslaugos administratoriumi.",
"Reload widget": "Įkelti valdiklį iš naujo",
"Picture": "Paveikslas",
"Create new room": "Sukurti naują kambarį",
@@ -787,12 +787,12 @@
"You cannot place a call with yourself.": "Negalite skambinti patys sau.",
"Registration Required": "Reikalinga registracija",
"You need to register to do this. Would you like to register now?": "Norėdami tai atlikti, turite užsiregistruoti. Ar norėtumėte užsiregistruoti dabar?",
- "Missing roomId.": "Trūksta kambario ID (roomId).",
+ "Missing roomId.": "Trūksta kambario ID.",
"Leave room": "Išeiti iš kambario",
"(could not connect media)": "(nepavyko prijungti medijos)",
- "This homeserver has hit its Monthly Active User limit.": "Šis namų serveris pasiekė savo mėnesinį aktyvių naudotojų limitą.",
- "This homeserver has exceeded one of its resource limits.": "Šis namų serveris viršijo vieno iš savo išteklių limitą.",
- "Unable to connect to Homeserver. Retrying...": "Nepavyksta prisijungti prie namų serverio. Bandoma iš naujo...",
+ "This homeserver has hit its Monthly Active User limit.": "Šis serveris pasiekė savo mėnesinį aktyvių naudotojų limitą.",
+ "This homeserver has exceeded one of its resource limits.": "Šis serveris viršijo vieno iš savo išteklių limitą.",
+ "Unable to connect to Homeserver. Retrying...": "Nepavyksta prisijungti prie serverio. Bandoma iš naujo...",
"Hide avatar changes": "Slėpti avatarų pasikeitimus",
"Disable Community Filter Panel": "Išjungti bendruomenės filtro skydelį",
"Enable widget screenshots on supported widgets": "Palaikomuose valdikliuose įjungti valdiklių ekrano kopijas",
@@ -854,12 +854,12 @@
"%(oneUser)schanged their avatar %(count)s times|other": "%(oneUser)s pasikeitė avatarą %(count)s kartų(-us)",
"And %(count)s more...|other": "Ir dar %(count)s...",
"Existing Call": "Esamas skambutis",
- "A call is already in progress!": "Skambutis jau yra inicijuojamas!",
+ "A call is already in progress!": "Skambutis jau vyksta!",
"Default": "Numatytasis",
"Restricted": "Apribotas",
"Moderator": "Moderatorius",
- "Ignores a user, hiding their messages from you": "Nepaiso naudotojo, paslepiant nuo jūsų jo žinutes",
- "Stops ignoring a user, showing their messages going forward": "Sustabdo naudotojo nepaisymą, rodant jo tolimesnes žinutes",
+ "Ignores a user, hiding their messages from you": "Ignoruoja vartotoją, slepiant nuo jūsų jo žinutes",
+ "Stops ignoring a user, showing their messages going forward": "Sustabdo vartotojo ignoravimą, rodant jums jo tolimesnes žinutes",
"Hide avatars in user and room mentions": "Slėpti avatarus naudotojų ir kambarių paminėjimuose",
"Revoke Moderator": "Panaikinti moderatorių",
"deleted": "perbrauktas",
@@ -871,13 +871,13 @@
"Invites": "Pakvietimai",
"You have no historical rooms": "Jūs neturite istorinių kambarių",
"Historical": "Istoriniai",
- "Every page you use in the app": "Kiekvienas puslapis, kurį naudoji programoje",
+ "Every page you use in the app": "Kiekvienas puslapis, kurį jūs naudojate programoje",
"Call Timeout": "Skambučio laikas baigėsi",
- "%(senderName)s added %(count)s %(addedAddresses)s as addresses for this room.|other": "%(senderName)s kaip šio kambario adresus pridėjo %(addedAddresses)s.",
- "%(senderName)s added %(count)s %(addedAddresses)s as addresses for this room.|one": "%(senderName)s kaip šio kambario adresą pridėjo %(addedAddresses)s.",
- "%(senderName)s removed %(count)s %(removedAddresses)s as addresses for this room.|other": "%(senderName)s kaip šio kambario adresus pašalino %(removedAddresses)s.",
- "%(senderName)s removed %(count)s %(removedAddresses)s as addresses for this room.|one": "%(senderName)s kaip šio kambario adresą pašalino %(removedAddresses)s.",
- "%(senderName)s added %(addedAddresses)s and removed %(removedAddresses)s as addresses for this room.": "%(senderName)s kaip šio kambario adresus pridėjo %(addedAddresses)s ir pašalino %(removedAddresses)s.",
+ "%(senderName)s added %(count)s %(addedAddresses)s as addresses for this room.|other": "%(senderName)s pridėjo %(addedAddresses)s, kaip šio kambario adresus.",
+ "%(senderName)s added %(count)s %(addedAddresses)s as addresses for this room.|one": "%(senderName)s pridėjo %(addedAddresses)s, kaip šio kambario adresą.",
+ "%(senderName)s removed %(count)s %(removedAddresses)s as addresses for this room.|other": "%(senderName)s pašalino %(removedAddresses)s, kaip šio kambario adresus.",
+ "%(senderName)s removed %(count)s %(removedAddresses)s as addresses for this room.|one": "%(senderName)s pašalino %(removedAddresses)s, kaip šio kambario adresą.",
+ "%(senderName)s added %(addedAddresses)s and removed %(removedAddresses)s as addresses for this room.": "%(senderName)s pridėjo %(addedAddresses)s ir pašalino %(removedAddresses)s, kaip šio kambario adresus.",
"%(senderName)s set the main address for this room to %(address)s.": "%(senderName)s nustatė pagrindinį šio kambario adresą į %(address)s.",
"%(senderName)s removed the main address for this room.": "%(senderName)s pašalino pagrindinį šio kambario adresą.",
"Disinvite": "Atšaukti pakvietimą",
@@ -885,8 +885,8 @@
"Unknown for %(duration)s": "Nežinoma jau %(duration)s",
"(warning: cannot be disabled again!)": "(įspėjimas: nebeįmanoma bus išjungti!)",
"Unable to load! Check your network connectivity and try again.": "Nepavyko įkelti! Patikrinkite savo tinklo ryšį ir bandykite dar kartą.",
- "%(targetName)s joined the room.": "%(targetName)s atėjo į kambarį.",
- "User %(user_id)s does not exist": "Naudotojo %(user_id)s nėra",
+ "%(targetName)s joined the room.": "%(targetName)s prisijungė prie kambario.",
+ "User %(user_id)s does not exist": "Vartotojas %(user_id)s neegzistuoja",
"Unknown server error": "Nežinoma serverio klaida",
"Avoid sequences": "Venkite sekų",
"Avoid recent years": "Venkite paskiausių metų",
@@ -896,7 +896,7 @@
"All-uppercase is almost as easy to guess as all-lowercase": "Visas didžiąsias raides taip pat lengva atspėti kaip ir visas mažąsias",
"Reversed words aren't much harder to guess": "Žodžius atvirkštine tvarka nėra sunkiau atspėti",
"Predictable substitutions like '@' instead of 'a' don't help very much": "Nuspėjami pakaitalai, tokie kaip \"@\" vietoj \"a\", nelabai padeda",
- "Add another word or two. Uncommon words are better.": "Pridėkite dar vieną žodį ar du. Geriau nedažnai vartojamus žodžius.",
+ "Add another word or two. Uncommon words are better.": "Pridėkite dar vieną ar du žodžius. Geriau nedažnai vartojamus žodžius.",
"Repeats like \"aaa\" are easy to guess": "Tokius pasikartojimus kaip \"aaa\" yra lengva atspėti",
"Repeats like \"abcabcabc\" are only slightly harder to guess than \"abc\"": "Tokius pasikartojimus kaip \"abcabcabc\" yra tik truputėlį sunkiau atspėti nei \"abc\"",
"Sequences like abc or 6543 are easy to guess": "Tokias sekas kaip \"abc\" ar \"6543\" yra lengva atspėti",
@@ -906,13 +906,13 @@
"This is a top-100 common password": "Tai yra vienas iš 100 dažniausiai naudojamų slaptažodžių",
"This is a very common password": "Tai yra labai dažnai naudojamas slaptažodis",
"This is similar to a commonly used password": "Šis yra panašus į dažnai naudojamą slaptažodį",
- "A word by itself is easy to guess": "Patį žodį savaime yra lengva atspėti",
+ "A word by itself is easy to guess": "Pats žodis yra lengvai atspėjamas",
"Names and surnames by themselves are easy to guess": "Pačius vardus ar pavardes yra lengva atspėti",
"Common names and surnames are easy to guess": "Dažnai naudojamus vardus ar pavardes yra lengva atspėti",
"Straight rows of keys are easy to guess": "Klavišų eilę yra lengva atspėti",
"Short keyboard patterns are easy to guess": "Trumpus klaviatūros šablonus yra lengva atspėti",
"Avoid repeated words and characters": "Venkite pasikartojančių žodžių ir simbolių",
- "Use a few words, avoid common phrases": "Naudokite kelis žodžius, venkite dažnai naudojamų frazių",
+ "Use a few words, avoid common phrases": "Naudokite keletą žodžių, venkite dažnai naudojamų frazių",
"No need for symbols, digits, or uppercase letters": "Nereikia simbolių, skaitmenų ar didžiųjų raidžių",
"Encrypted messages in group chats": "Šifruotos žinutės grupės pokalbiuose",
"Delete Backup": "Ištrinti atsarginę kopiją",
@@ -1000,5 +1000,122 @@
"Explore rooms": "Peržiūrėti kambarius",
"Your Riot is misconfigured": "Jūsų Riot yra neteisingai sukonfigūruotas",
"Sign in to your Matrix account on %(serverName)s": "Prisijunkite prie savo paskyros %(serverName)s serveryje",
- "Sign in to your Matrix account on ": "Prisijunkite prie savo paskyros serveryje"
+ "Sign in to your Matrix account on ": "Prisijunkite prie savo paskyros serveryje",
+ "Whether or not you're using the 'breadcrumbs' feature (avatars above the room list)": "Nepriklausomai nuo to ar jūs naudojate 'duonos trupinių' funkciją (avatarai virš kambarių sąrašo)",
+ "Where this page includes identifiable information, such as a room, user or group ID, that data is removed before being sent to the server.": "Kur šis puslapis įtraukia identifikuojamą informaciją, kaip kambarys, vartotojas ar grupės ID, tie duomenys yra pašalinami prieš siunčiant į serverį.",
+ "The remote side failed to pick up": "Nuotolinėi pusėi nepavyko atsiliepti",
+ "Call failed due to misconfigured server": "Skambutis nepavyko dėl neteisingai sukonfigūruoto serverio",
+ "Please ask the administrator of your homeserver (%(homeserverDomain)s) to configure a TURN server in order for calls to work reliably.": "Paprašykite savo serverio administratoriaus (%(homeserverDomain)s) sukonfiguruoti TURN serverį, kad skambučiai veiktų patikimai.",
+ "Alternatively, you can try to use the public server at turn.matrix.org, but this will not be as reliable, and it will share your IP address with that server. You can also manage this in Settings.": "Alternatyviai, jūs galite bandyti naudoti viešą serverį turn.matrix.org, bet tai nebus taip patikima, ir tai atskleis jūsų IP adresą šiam serveriui. Jūs taip pat galite tvarkyti tai Nustatymuose.",
+ "Try using turn.matrix.org": "Bandykite naudoti turn.matrix.org",
+ "A conference call could not be started because the integrations server is not available": "Konferencinio skambučio nebuvo galima pradėti, nes nėra integracijų serverio",
+ "Call in Progress": "Vykstantis skambutis",
+ "A call is currently being placed!": "Šiuo metu skambinama!",
+ "Replying With Files": "Atsakyti su failais",
+ "At this time it is not possible to reply with a file. Would you like to upload this file without replying?": "Šiuo metu neįmanoma atsakyti su failu. Ar norite įkelti šį failą neatsakydami?",
+ "The file '%(fileName)s' failed to upload.": "Failo '%(fileName)s' nepavyko įkelti.",
+ "The file '%(fileName)s' exceeds this homeserver's size limit for uploads": "Failas '%(fileName)s' viršyja šio serverio įkeliamų failų dydžio limitą",
+ "The server does not support the room version specified.": "Serveris nepalaiko nurodytos kambario versijos.",
+ "Invite new community members": "Pakviesti naujus bendruomenės narius",
+ "Name or Matrix ID": "Vardas arba Matrix ID",
+ "Identity server has no terms of service": "Tapatybės serveris neturi paslaugų teikimo sąlygų",
+ "This action requires accessing the default identity server to validate an email address or phone number, but the server does not have any terms of service.": "Šiam veiksmui reikalinga prieiti numatytąjį tapatybės serverį , kad patvirtinti el. pašto adresą arba telefono numerį, bet serveris neturi jokių paslaugos teikimo sąlygų.",
+ "Only continue if you trust the owner of the server.": "Tęskite tik tada, jei pasitikite serverio savininku.",
+ "Trust": "Pasitikėti",
+ "Email, name or Matrix ID": "El. paštas, vardas arba Matrix ID",
+ "Failed to start chat": "Nepavyko pradėti pokalbio",
+ "Failed to invite users to the room:": "Nepavyko pakviesti vartotojų į kambarį:",
+ "You need to be able to invite users to do that.": "Norėdami tai atlikti jūs turite turėti galimybę pakviesti vartotojus.",
+ "Power level must be positive integer.": "Galios lygis privalo būti teigiamas sveikasis skaičius.",
+ "Messages": "Žinutės",
+ "Actions": "Veiksmai",
+ "Other": "Kitas",
+ "Prepends ¯\\_(ツ)_/¯ to a plain-text message": "Prideda ¯\\_(ツ)_/¯ prie paprasto teksto pranešimo",
+ "Sends a message as plain text, without interpreting it as markdown": "SIunčia žinutę, kaip paprastą tekstą, jo neinterpretuodamas kaip pažymėto",
+ "Upgrades a room to a new version": "Atnaujina kambarį į naują versiją",
+ "You do not have the required permissions to use this command.": "Jūs neturite reikalingų leidimų naudoti šią komandą.",
+ "Room upgrade confirmation": "Kambario atnaujinimo patvirtinimas",
+ "Upgrading a room can be destructive and isn't always necessary.": "Kambario atnaujinimas gali būti destruktyvus ir nėra visada reikalingas.",
+ "Room upgrades are usually recommended when a room version is considered unstable. Unstable room versions might have bugs, missing features, or security vulnerabilities.": "Kambario atnaujinimai paprastai rekomenduojami, kada kambario versija yra laikoma nestabili. Nestabilios kambario versijos gali turėti klaidų, trūkstamų funkcijų, arba saugumo spragų.",
+ "Room upgrades usually only affect server-side processing of the room. If you're having problems with your Riot client, please file an issue with .": "Kambario atnaujinimai paprastai paveikia tik serverio pusės kambario apdorojimą. Jei jūs turite problemų su jūsų Riot klientu, prašome užregistruoti problemą .",
+ "Warning: Upgrading a room will not automatically migrate room members to the new version of the room. We'll post a link to the new room in the old version of the room - room members will have to click this link to join the new room.": "Įspėjimas: Kambario atnaujinimas automatiškai nemigruos kambario dalyvių į naują kambario versiją. Mes paskelbsime nuorodą į naują kambarį senojoje kambario versijoje - kambario dalyviai turės ją paspausti, norėdami prisijungti prie naujo kambario.",
+ "Please confirm that you'd like to go forward with upgrading this room from to .": "Prašome patvirtinti, kad jūs norite tęsti šio kambario atnaujinimą iš į .",
+ "Upgrade": "Atnaujinti",
+ "Changes your display nickname in the current room only": "Pakeičia jūsų rodomą slapyvardį tik esamame kambaryje",
+ "Changes the avatar of the current room": "Pakeičia esamo kambario avatarą",
+ "Changes your avatar in this current room only": "Pakeičia jūsų avatarą tik esamame kambaryje",
+ "Changes your avatar in all rooms": "Pakeičia jūsų avatarą visuose kambariuose",
+ "Gets or sets the room topic": "Gauna arba nustato kambario temą",
+ "This room has no topic.": "Šis kambarys neturi temos.",
+ "Sets the room name": "Nustato kambario pavadinimą",
+ "Use an identity server": "Naudoti tapatybės serverį",
+ "Use an identity server to invite by email. Click continue to use the default identity server (%(defaultIdentityServerName)s) or manage in Settings.": "Norėdami pakviesti nurodydami el. paštą, naudokite tapatybės serverį. Tam, kad būtų naudojamas numatytasis tapatybės serveris %(defaultIdentityServerName)s, spauskite tęsti, arba tvarkykite nustatymuose.",
+ "Use an identity server to invite by email. Manage in Settings.": "Norėdami pakviesti nurodydami el. paštą, naudokite tapatybės serverį. Tvarkykite nustatymuose.",
+ "Joins room with given alias": "Prisijungia prie kambario su nurodytu slapyvardžiu",
+ "Unbans user with given ID": "Atblokuoja vartotoją su nurodytu id",
+ "Ignored user": "Ignoruojamas vartotojas",
+ "Unignored user": "Nebeignoruojamas vartotojas",
+ "You are no longer ignoring %(userId)s": "Dabar nebeignoruojate %(userId)s",
+ "Define the power level of a user": "Nustatykite vartotojo galios lygį",
+ "Deops user with given id": "Deop'ina vartotoją su nurodytu id",
+ "Adds a custom widget by URL to the room": "Į kambarį prideda pasirinktinį valdiklį pagal URL",
+ "Please supply a https:// or http:// widget URL": "Prašome pateikti https:// arba http:// valdiklio URL",
+ "You cannot modify widgets in this room.": "Jūs negalite keisti valdiklių šiame kambaryje.",
+ "Verifies a user, device, and pubkey tuple": "Patikrina vartotoją, įrenginį ir pubkey seką",
+ "Forces the current outbound group session in an encrypted room to be discarded": "Priverčia išmesti esamą užsibaigiančią grupės sesiją šifruotame kambaryje",
+ "Sends the given message coloured as a rainbow": "Išsiunčia nurodytą žinutę nuspalvintą kaip vaivorykštė",
+ "Sends the given emote coloured as a rainbow": "Išsiunčia nurodytą emociją nuspalvintą kaip vaivorykštė",
+ "Displays list of commands with usages and descriptions": "Parodo komandų sąrašą su naudojimo būdais ir aprašymais",
+ "%(targetName)s accepted the invitation for %(displayName)s.": "%(targetName)s priėmė pakvietimą %(displayName)s.",
+ "%(senderName)s requested a VoIP conference.": "%(senderName)s pageidauja VoIP konferencijos.",
+ "%(senderName)s made no change.": "%(senderName)s neatliko pakeitimo.",
+ "%(senderName)s withdrew %(targetName)s's invitation.": "%(senderName)s atšaukė %(targetName)s pakvietimą.",
+ "%(senderDisplayName)s upgraded this room.": "%(senderDisplayName)s atnaujino šį kambarį.",
+ "%(senderDisplayName)s made the room public to whoever knows the link.": "%(senderDisplayName)s padarė kambarį viešą visiems žinantiems nuorodą.",
+ "%(senderDisplayName)s made the room invite only.": "%(senderDisplayName)s padarė kambarį tik pakviestiems.",
+ "%(senderDisplayName)s changed the join rule to %(rule)s": "%(senderDisplayName)s pakeitė prisijungimo normą į %(rule)s",
+ "%(senderDisplayName)s has allowed guests to join the room.": "%(senderDisplayName)s leido svečiams prisijungti prie kambario.",
+ "%(senderDisplayName)s has prevented guests from joining the room.": "%(senderDisplayName)s uždraudė svečiams prisijungti prie kambario.",
+ "%(senderDisplayName)s changed guest access to %(rule)s": "%(senderDisplayName)s pakeitė svečių prieigą prie %(rule)s",
+ "%(senderDisplayName)s enabled flair for %(groups)s in this room.": "%(senderDisplayName)s įjungė ženkliukus bendruomenėi %(groups)s šiame kambaryje.",
+ "%(senderDisplayName)s disabled flair for %(groups)s in this room.": "%(senderDisplayName)s išjungė ženkliukus bendruomenėi %(groups)s šiame kambaryje.",
+ "%(senderDisplayName)s enabled flair for %(newGroups)s and disabled flair for %(oldGroups)s in this room.": "%(senderDisplayName)s įjungė ženkliukus bendruomenėi %(newGroups)s ir išjungė ženkliukus bendruomenėi %(oldGroups)s šiame kambaryje.",
+ "%(senderName)s placed a %(callType)s call.": "%(senderName)s pradėjo %(callType)s skambutį.",
+ "%(senderName)s revoked the invitation for %(targetDisplayName)s to join the room.": "%(senderName)s atšaukė pakvietimą %(targetDisplayName)s prisijungti prie kambario.",
+ "%(senderName)s sent an invitation to %(targetDisplayName)s to join the room.": "%(senderName)s išsiuntė pakvietimą %(targetDisplayName)s prisijungti prie kambario.",
+ "%(senderName)s made future room history visible to all room members, from the point they joined.": "%(senderName)s padarė būsimą kambario istoriją matomą visiems kambario dalyviams, nuo pat jų prisijungimo.",
+ "%(senderName)s made future room history visible to unknown (%(visibility)s).": "%(senderName)s padarė būsimą kambario istoriją matomą nežinomam (%(visibility)s).",
+ "%(userId)s from %(fromPowerLevel)s to %(toPowerLevel)s": "%(userId)s iš %(fromPowerLevel)s į %(toPowerLevel)s",
+ "%(senderName)s changed the power level of %(powerLevelDiffText)s.": "%(senderName)s pakeitė %(powerLevelDiffText)s galios lygį.",
+ "%(displayName)s is typing …": "%(displayName)s rašo …",
+ "%(names)s and %(count)s others are typing …|other": "%(names)s ir %(count)s kiti rašo …",
+ "%(names)s and %(count)s others are typing …|one": "%(names)s ir vienas kitas rašo …",
+ "%(names)s and %(lastPerson)s are typing …": "%(names)s ir %(lastPerson)s rašo …",
+ "Cannot reach homeserver": "Serveris nepasiekiamas",
+ "Ensure you have a stable internet connection, or get in touch with the server admin": "Įsitikinkite, kad jūsų interneto ryšys yra stabilus, arba susisiekite su serverio administratoriumi",
+ "Ask your Riot admin to check your config for incorrect or duplicate entries.": "Paprašykite savo Riot administratoriaus patikrinti ar jūsų konfigūracijoje nėra neteisingų arba pasikartojančių įrašų.",
+ "Cannot reach identity server": "Tapatybės serveris nepasiekiamas",
+ "You can register, but some features will be unavailable until the identity server is back online. If you keep seeing this warning, check your configuration or contact a server admin.": "Jūs galite registruotis, tačiau kai kurios funkcijos bus nepasiekiamos, kol tapatybės serveris prisijungs. Jei ir toliau matote šį įspėjimą, patikrinkite savo konfigūraciją arba susisiekite su serverio administratoriumi.",
+ "You can reset your password, but some features will be unavailable until the identity server is back online. If you keep seeing this warning, check your configuration or contact a server admin.": "Jūs galite iš naujo nustatyti savo slaptažodį, tačiau kai kurios funkcijos bus nepasiekiamos, kol tapatybės serveris prisijungs. Jei ir toliau matote šį įspėjimą, patikrinkite savo konfigūraciją arba susisiekite su serverio administratoriumi.",
+ "You can log in, but some features will be unavailable until the identity server is back online. If you keep seeing this warning, check your configuration or contact a server admin.": "Jūs galite prisijungti, tačiau kai kurios funkcijos bus nepasiekiamos, kol tapatybės serveris prisijungs. Jei ir toliau matote šį įspėjimą, patikrinkite savo konfigūraciją arba susisiekite su serverio administratoriumi.",
+ "No homeserver URL provided": "Nepateiktas serverio URL",
+ "Unexpected error resolving homeserver configuration": "Netikėta klaida nustatant serverio konfigūraciją",
+ "Unexpected error resolving identity server configuration": "Netikėta klaida nustatant tapatybės serverio konfigūraciją",
+ "%(items)s and %(count)s others|other": "%(items)s ir %(count)s kiti",
+ "%(items)s and %(count)s others|one": "%(items)s ir vienas kitas",
+ "%(items)s and %(lastItem)s": "%(items)s ir %(lastItem)s",
+ "Unrecognised address": "Neatpažintas adresas",
+ "You do not have permission to invite people to this room.": "Jūs neturite leidimo pakviesti žmones į šį kambarį.",
+ "User %(userId)s is already in the room": "Vartotojas %(userId)s jau yra kambaryje",
+ "User %(user_id)s may or may not exist": "Vartotojas %(user_id)s gali ir neegzistuoti",
+ "The user must be unbanned before they can be invited.": "Norint pakviesti vartotoją, pirmiausia turi būti pašalintas draudimas.",
+ "The user's homeserver does not support the version of the room.": "Vartotojo serveris nepalaiko kambario versijos.",
+ "Use a longer keyboard pattern with more turns": "Naudokite ilgesnį klaviatūros modelį su daugiau vijų",
+ "There was an error joining the room": "Prisijungiant prie kambario įvyko klaida",
+ "Failed to join room": "Prisijungti prie kambario nepavyko",
+ "Message Pinning": "Žinutės prisegimas",
+ "Custom user status messages": "Pasirinktinės vartotojo būsenos žinutės",
+ "Group & filter rooms by custom tags (refresh to apply changes)": "Grupuoti ir filtruoti kambarius pagal pasirinktines žymas (atnaujinkite, kad pritaikytumėte pakeitimus)",
+ "Render simple counters in room header": "Užkrauti paprastus skaitiklius kambario antraštėje",
+ "Multiple integration managers": "Daugialypiai integracijų valdikliai"
}
From bb5f532eeb5d074c16439f50af7d2a62996e6249 Mon Sep 17 00:00:00 2001
From: fenuks
Date: Wed, 6 Nov 2019 00:32:52 +0000
Subject: [PATCH 031/334] Translated using Weblate (Polish)
Currently translated at 74.6% (1382 of 1853 strings)
Translation: Riot Web/matrix-react-sdk
Translate-URL: https://translate.riot.im/projects/riot-web/matrix-react-sdk/pl/
---
src/i18n/strings/pl.json | 10 +++++++++-
1 file changed, 9 insertions(+), 1 deletion(-)
diff --git a/src/i18n/strings/pl.json b/src/i18n/strings/pl.json
index dd09059da8..31f82bc2dd 100644
--- a/src/i18n/strings/pl.json
+++ b/src/i18n/strings/pl.json
@@ -1645,5 +1645,13 @@
"Chat with Riot Bot": "Rozmowa z Botem Riota",
"FAQ": "Najczęściej zadawane pytania",
"Always show the window menu bar": "Zawsze pokazuj pasek menu okna",
- "Close button should minimize window to tray": "Przycisk zamknięcia minimalizuje okno do zasobnika"
+ "Close button should minimize window to tray": "Przycisk zamknięcia minimalizuje okno do zasobnika",
+ "Add Email Address": "Dodaj adres e-mail",
+ "Add Phone Number": "Dodaj numer telefonu",
+ "This action requires accessing the default identity server to validate an email address or phone number, but the server does not have any terms of service.": "Ta czynność wymaga dostępu do domyślnego serwera tożsamości do walidacji adresu e-mail, czy numeru telefonu, ale serwer nie określa warunków korzystania z usługi.",
+ "Sends a message as plain text, without interpreting it as markdown": "Wysyła wiadomość jako zwykły tekst, bez jego interpretacji jako markdown",
+ "You do not have the required permissions to use this command.": "Nie posiadasz wymaganych uprawnień do użycia tego polecenia.",
+ "Changes the avatar of the current room": "Zmienia awatar dla obecnego pokoju",
+ "Use an identity server": "Użyj serwera tożsamości",
+ "Show previews/thumbnails for images": "Pokaż podgląd/miniatury obrazów"
}
From 4fe95b0075832725f9e11a64cd3d44be29ee1ceb Mon Sep 17 00:00:00 2001
From: Walter
Date: Thu, 7 Nov 2019 18:22:05 +0000
Subject: [PATCH 032/334] Translated using Weblate (Russian)
Currently translated at 99.8% (1849 of 1853 strings)
Translation: Riot Web/matrix-react-sdk
Translate-URL: https://translate.riot.im/projects/riot-web/matrix-react-sdk/ru/
---
src/i18n/strings/ru.json | 15 ++++++++++-----
1 file changed, 10 insertions(+), 5 deletions(-)
diff --git a/src/i18n/strings/ru.json b/src/i18n/strings/ru.json
index b22c627213..01065a9e96 100644
--- a/src/i18n/strings/ru.json
+++ b/src/i18n/strings/ru.json
@@ -1299,7 +1299,7 @@
"Two-way device verification using short text": "Двусторонняя проверка устройства используя короткий текст",
"Enable Emoji suggestions while typing": "Включить предложения эмоджи при наборе",
"Show a placeholder for removed messages": "Показывать плашки вместо удалённых сообщений",
- "Show join/leave messages (invites/kicks/bans unaffected)": "Показывать сообщения о вступлении/выходе (не влияет на приглашения, исключения и запреты)",
+ "Show join/leave messages (invites/kicks/bans unaffected)": "Показывать сообщения о вступлении | выходе (не влияет на приглашения, исключения и запреты)",
"Show avatar changes": "Показывать изменения аватара",
"Show display name changes": "Показывать изменения отображаемого имени",
"Show read receipts": "Показывать уведомления о прочтении",
@@ -1347,7 +1347,7 @@
"Account management": "Управление аккаунтом",
"Deactivating your account is a permanent action - be careful!": "Деактивация вашей учётной записи — это необратимое действие. Будьте осторожны!",
"Chat with Riot Bot": "Чат с ботом Riot",
- "Help & About": "Помощь & о программе",
+ "Help & About": "Помощь & О программе",
"FAQ": "Часто задаваемые вопросы",
"Versions": "Версии",
"Lazy loading is not supported by your current homeserver.": "Ленивая подгрузка не поддерживается вашим сервером.",
@@ -1391,7 +1391,7 @@
"Backing up %(sessionsRemaining)s keys...": "Резервное копирование %(sessionsRemaining)s ключей...",
"All keys backed up": "Все ключи сохранены",
"Developer options": "Параметры разработчика",
- "General": "Общий",
+ "General": "Общие",
"Set a new account password...": "Установить новый пароль учётной записи...",
"Legal": "Законный",
"At this time it is not possible to reply with an emote.": "В настоящее время невозможно ответить с помощью эмоции.",
@@ -2018,7 +2018,7 @@
"Create a private room": "Создать приватную комнату",
"Topic (optional)": "Тема (опционально)",
"Make this room public": "Сделать комнату публичной",
- "Use the new, faster, composer for writing messages": "Используйте новый, более быстрый, composer для написания сообщений.",
+ "Use the new, faster, composer for writing messages": "Используйте новый, более быстрый, редактор для написания сообщений.",
"Send read receipts for messages (requires compatible homeserver to disable)": "Отправлять подтверждения о прочтении сообщений (требуется отключение совместимого домашнего сервера)",
"Show previews/thumbnails for images": "Показать превью / миниатюры для изображений",
"Disconnect from the identity server and connect to instead?": "Отключиться от сервера идентификации и вместо этого подключиться к ?",
@@ -2160,5 +2160,10 @@
"Recent rooms": "Недавние комнаты",
"No identity server is configured so you cannot add an email address in order to reset your password in the future.": "Сервер идентификации не настроен, поэтому вы не можете добавить адрес электронной почты, чтобы в будущем сбросить пароль.",
"Jump to first unread room.": "Перейти в первую непрочитанную комнату.",
- "Jump to first invite.": "Перейти к первому приглашению."
+ "Jump to first invite.": "Перейти к первому приглашению.",
+ "Trust": "Доверие",
+ "%(count)s unread messages including mentions.|one": "1 непрочитанное упоминание.",
+ "%(count)s unread messages.|one": "1 непрочитанное сообщение.",
+ "Unread messages.": "Непрочитанные сообщения.",
+ "Message Actions": "Сообщение действий"
}
From c4d45e87ea251f62f3ffc8c33c916613d256a9b3 Mon Sep 17 00:00:00 2001
From: Travis Ralston
Date: Fri, 8 Nov 2019 15:54:48 -0700
Subject: [PATCH 033/334] Use a ternary operator instead of relying on AND
semantics in EditHIstoryDialog
Fixes https://github.com/vector-im/riot-web/issues/11334 (probably).
`allEvents` should never have a boolean in it, so given the stack trace and the code this is my best estimate for what the problem could be. I can't reproduce the problem.
---
src/components/views/dialogs/MessageEditHistoryDialog.js | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/components/views/dialogs/MessageEditHistoryDialog.js b/src/components/views/dialogs/MessageEditHistoryDialog.js
index 6014cb941c..b5e4daa1c1 100644
--- a/src/components/views/dialogs/MessageEditHistoryDialog.js
+++ b/src/components/views/dialogs/MessageEditHistoryDialog.js
@@ -116,7 +116,7 @@ export default class MessageEditHistoryDialog extends React.PureComponent {
nodes.push((
Date: Fri, 8 Nov 2019 18:40:35 +0000
Subject: [PATCH 034/334] Translated using Weblate (French)
Currently translated at 100.0% (1864 of 1864 strings)
Translation: Riot Web/matrix-react-sdk
Translate-URL: https://translate.riot.im/projects/riot-web/matrix-react-sdk/fr/
---
src/i18n/strings/fr.json | 13 ++++++++++++-
1 file changed, 12 insertions(+), 1 deletion(-)
diff --git a/src/i18n/strings/fr.json b/src/i18n/strings/fr.json
index 328c3b7f9e..40840c8d58 100644
--- a/src/i18n/strings/fr.json
+++ b/src/i18n/strings/fr.json
@@ -2280,5 +2280,16 @@
"Show tray icon and minimize window to it on close": "Afficher l’icône dans la barre d’état et minimiser la fenêtre lors de la fermeture",
"This action requires accessing the default identity server to validate an email address or phone number, but the server does not have any terms of service.": "Cette action nécessite l’accès au serveur d’identité par défaut afin de valider une adresse e-mail ou un numéro de téléphone, mais le serveur n’a aucune condition de service.",
"Trust": "Confiance",
- "Message Actions": "Actions de message"
+ "Message Actions": "Actions de message",
+ "%(name)s (%(userId)s)": "%(name)s (%(userId)s)",
+ "Send verification requests in direct message": "Envoyer les demandes de vérification en message direct",
+ "You verified %(name)s": "Vous avez vérifié %(name)s",
+ "You cancelled verifying %(name)s": "Vous avez annulé la vérification de %(name)s",
+ "%(name)s cancelled verifying": "%(name)s a annulé la vérification",
+ "You accepted": "Vous avez accepté",
+ "%(name)s accepted": "%(name)s a accepté",
+ "You cancelled": "Vous avez annulé",
+ "%(name)s cancelled": "%(name)s a annulé",
+ "%(name)s wants to verify": "%(name)s veut vérifier",
+ "You sent a verification request": "Vous avez envoyé une demande de vérification"
}
From 949ba89b4a5cc657d953ca3566badb5b24779c4e Mon Sep 17 00:00:00 2001
From: Elwyn Malethan
Date: Sat, 9 Nov 2019 19:00:33 +0000
Subject: [PATCH 035/334] Added translation using Weblate (Welsh)
---
src/i18n/strings/cy.json | 1 +
1 file changed, 1 insertion(+)
create mode 100644 src/i18n/strings/cy.json
diff --git a/src/i18n/strings/cy.json b/src/i18n/strings/cy.json
new file mode 100644
index 0000000000..0967ef424b
--- /dev/null
+++ b/src/i18n/strings/cy.json
@@ -0,0 +1 @@
+{}
From def4f90257bd486b41305b6e34c3087b29e72a46 Mon Sep 17 00:00:00 2001
From: Besnik Bleta
Date: Sun, 10 Nov 2019 18:11:53 +0000
Subject: [PATCH 036/334] Translated using Weblate (Albanian)
Currently translated at 99.8% (1860 of 1864 strings)
Translation: Riot Web/matrix-react-sdk
Translate-URL: https://translate.riot.im/projects/riot-web/matrix-react-sdk/sq/
---
src/i18n/strings/sq.json | 43 +++++++++++++++++++++++++++++++++++++++-
1 file changed, 42 insertions(+), 1 deletion(-)
diff --git a/src/i18n/strings/sq.json b/src/i18n/strings/sq.json
index 4f25162491..1d80a90a2b 100644
--- a/src/i18n/strings/sq.json
+++ b/src/i18n/strings/sq.json
@@ -2205,5 +2205,46 @@
"Emoji Autocomplete": "Vetëplotësim Emoji-sh",
"Notification Autocomplete": "Vetëplotësim NJoftimesh",
"Room Autocomplete": "Vetëplotësim Dhomash",
- "User Autocomplete": "Vetëplotësim Përdoruesish"
+ "User Autocomplete": "Vetëplotësim Përdoruesish",
+ "This action requires accessing the default identity server to validate an email address or phone number, but the server does not have any terms of service.": "Ky veprim lyp hyrje te shërbyesi parazgjedhje i identiteteve për të vlerësuar një adresë email ose një numër telefoni, por shërbyesi nuk ka ndonjë kusht shërbimesh.",
+ "Trust": "Besim",
+ "%(name)s (%(userId)s)": "%(name)s (%(userId)s)",
+ "Use the new, consistent UserInfo panel for Room Members and Group Members": "Përdorni panelin e ri UserInfo për Anëtarë Dhome dhe Anëtarë Grupi",
+ "Send verification requests in direct message": "Dërgo kërkesa verifikimi në mesazh të drejtpërdrejt",
+ "Show tray icon and minimize window to it on close": "Me mbylljen, shfaq ikonë paneli dhe minimizo dritaren",
+ "Room %(name)s": "Dhoma %(name)s",
+ "Recent rooms": "Dhoma së fundi",
+ "%(count)s unread messages including mentions.|one": "1 përmendje e palexuar.",
+ "%(count)s unread messages.|one": "1 mesazh i palexuar.",
+ "Unread messages.": "Mesazhe të palexuar.",
+ "Trust & Devices": "Besim & Pajisje",
+ "Direct messages": "Mesazhe të drejtpërdrejtë",
+ "Failed to deactivate user": "S’u arrit të çaktivizohet përdorues",
+ "This client does not support end-to-end encryption.": "Ky klient nuk mbulon fshehtëzim skaj-më-skaj.",
+ "Messages in this room are not end-to-end encrypted.": "Mesazhet në këtë dhomë nuk janë të fshehtëzuara skaj-më-skaj.",
+ "React": "Reagoni",
+ "Message Actions": "Veprime Mesazhesh",
+ "You verified %(name)s": "Verifikuat %(name)s",
+ "You cancelled verifying %(name)s": "Anuluat verifikimin e %(name)s",
+ "%(name)s cancelled verifying": "%(name)s anuloi verifikimin",
+ "You accepted": "Pranuat",
+ "%(name)s accepted": "%(name)s pranoi",
+ "You cancelled": "Anuluat",
+ "%(name)s cancelled": "%(name)s anuloi",
+ "%(name)s wants to verify": "%(name)s dëshiron të verifikojë",
+ "You sent a verification request": "Dërguat një kërkesë verifikimi",
+ "Frequently Used": "Përdorur Shpesh",
+ "Smileys & People": "Emotikone & Persona",
+ "Animals & Nature": "Kafshë & Natyrë",
+ "Food & Drink": "Ushqim & Pije",
+ "Activities": "Veprimtari",
+ "Travel & Places": "Udhëtim & Vende",
+ "Objects": "Objekte",
+ "Symbols": "Simbole",
+ "Flags": "Flamuj",
+ "Quick Reactions": "Reagime të Shpejta",
+ "Cancel search": "Anulo kërkimin",
+ "No identity server is configured so you cannot add an email address in order to reset your password in the future.": "S’ka shërbyes identitetesh të formësuar, ndaj s’mund të shtoni një adresë email që të mund të ricaktoni fjalëkalimin tuaj në të ardhmen.",
+ "Jump to first unread room.": "Hidhu te dhoma e parë e palexuar.",
+ "Jump to first invite.": "Hidhu te ftesa e parë."
}
From eaac3fe3b8000423f195edcc865fa32cb3ff2deb Mon Sep 17 00:00:00 2001
From: Osoitz
Date: Sat, 9 Nov 2019 11:12:46 +0000
Subject: [PATCH 037/334] Translated using Weblate (Basque)
Currently translated at 100.0% (1864 of 1864 strings)
Translation: Riot Web/matrix-react-sdk
Translate-URL: https://translate.riot.im/projects/riot-web/matrix-react-sdk/eu/
---
src/i18n/strings/eu.json | 16 +++++++++++++++-
1 file changed, 15 insertions(+), 1 deletion(-)
diff --git a/src/i18n/strings/eu.json b/src/i18n/strings/eu.json
index 72b6fff50d..888f6984e7 100644
--- a/src/i18n/strings/eu.json
+++ b/src/i18n/strings/eu.json
@@ -2222,5 +2222,19 @@
"Jump to first unread room.": "Jauzi irakurri gabeko lehen gelara.",
"Jump to first invite.": "Jauzi lehen gonbidapenera.",
"Command Autocomplete": "Aginduak auto-osatzea",
- "DuckDuckGo Results": "DuckDuckGo emaitzak"
+ "DuckDuckGo Results": "DuckDuckGo emaitzak",
+ "This action requires accessing the default identity server to validate an email address or phone number, but the server does not have any terms of service.": "Ekintza honek lehenetsitako identitate-zerbitzaria atzitzea eskatzen du, e-mail helbidea edo telefono zenbakia balioztatzeko, baina zerbitzariak ez du erabilera baldintzarik.",
+ "Trust": "Jo fidagarritzat",
+ "%(name)s (%(userId)s)": "%(name)s (%(userId)s)",
+ "Send verification requests in direct message": "Bidali egiaztaketa eskariak mezu zuzen bidez",
+ "Message Actions": "Mezu-ekintzak",
+ "You verified %(name)s": "%(name)s egiaztatu duzu",
+ "You cancelled verifying %(name)s": "%(name)s egiaztatzeari utzi diozu",
+ "%(name)s cancelled verifying": "%(name)s(e)k egiaztaketa utzi du",
+ "You accepted": "Onartu duzu",
+ "%(name)s accepted": "%(name)s onartuta",
+ "You cancelled": "Utzi duzu",
+ "%(name)s cancelled": "%(name)s utzita",
+ "%(name)s wants to verify": "%(name)s(e)k egiaztatu nahi du",
+ "You sent a verification request": "Egiaztaketa eskari bat bidali duzu"
}
From a4a0dc9c2d7e14c32c1d3cba6e3663f277e443a4 Mon Sep 17 00:00:00 2001
From: Slavi Pantaleev
Date: Mon, 11 Nov 2019 06:47:26 +0000
Subject: [PATCH 038/334] Translated using Weblate (Bulgarian)
Currently translated at 100.0% (1864 of 1864 strings)
Translation: Riot Web/matrix-react-sdk
Translate-URL: https://translate.riot.im/projects/riot-web/matrix-react-sdk/bg/
---
src/i18n/strings/bg.json | 13 ++++++++++++-
1 file changed, 12 insertions(+), 1 deletion(-)
diff --git a/src/i18n/strings/bg.json b/src/i18n/strings/bg.json
index 6f198b2e5a..3697cc635c 100644
--- a/src/i18n/strings/bg.json
+++ b/src/i18n/strings/bg.json
@@ -2249,5 +2249,16 @@
"Emoji Autocomplete": "Подсказка за емоджита",
"Notification Autocomplete": "Подсказка за уведомления",
"Room Autocomplete": "Подсказка за стаи",
- "User Autocomplete": "Подсказка за потребители"
+ "User Autocomplete": "Подсказка за потребители",
+ "%(name)s (%(userId)s)": "%(name)s (%(userId)s)",
+ "Send verification requests in direct message": "Изпращай заявки за потвърждение чрез директни съобщения",
+ "You verified %(name)s": "Потвърдихте %(name)s",
+ "You cancelled verifying %(name)s": "Отказахте потвърждаването за %(name)s",
+ "%(name)s cancelled verifying": "%(name)s отказа потвърждаването",
+ "You accepted": "Приехте",
+ "%(name)s accepted": "%(name)s прие",
+ "You cancelled": "Отказахте потвърждаването",
+ "%(name)s cancelled": "%(name)s отказа",
+ "%(name)s wants to verify": "%(name)s иска да извърши потвърждение",
+ "You sent a verification request": "Изпратихте заявка за потвърждение"
}
From d8ea25403acb33a166f7f62ab668146c51cf306a Mon Sep 17 00:00:00 2001
From: Jeff Huang
Date: Mon, 11 Nov 2019 03:26:55 +0000
Subject: [PATCH 039/334] Translated using Weblate (Chinese (Traditional))
Currently translated at 100.0% (1864 of 1864 strings)
Translation: Riot Web/matrix-react-sdk
Translate-URL: https://translate.riot.im/projects/riot-web/matrix-react-sdk/zh_Hant/
---
src/i18n/strings/zh_Hant.json | 13 ++++++++++++-
1 file changed, 12 insertions(+), 1 deletion(-)
diff --git a/src/i18n/strings/zh_Hant.json b/src/i18n/strings/zh_Hant.json
index 58dca89415..185026aad5 100644
--- a/src/i18n/strings/zh_Hant.json
+++ b/src/i18n/strings/zh_Hant.json
@@ -2273,5 +2273,16 @@
"Show tray icon and minimize window to it on close": "顯示系統匣圖示並在關閉視窗時將其最小化至其中",
"This action requires accessing the default identity server to validate an email address or phone number, but the server does not have any terms of service.": "此動作需要存取預設的身份識別伺服器 以驗證電子郵件或電話號碼,但伺服器沒有任何服務條款。",
"Trust": "信任",
- "Message Actions": "訊息動作"
+ "Message Actions": "訊息動作",
+ "%(name)s (%(userId)s)": "%(name)s (%(userId)s)",
+ "Send verification requests in direct message": "在直接訊息中傳送驗證請求",
+ "You verified %(name)s": "您驗證了 %(name)s",
+ "You cancelled verifying %(name)s": "您已取消驗證 %(name)s",
+ "%(name)s cancelled verifying": "%(name)s 已取消驗證",
+ "You accepted": "您已接受",
+ "%(name)s accepted": "%(name)s 已接受",
+ "You cancelled": "您已取消",
+ "%(name)s cancelled": "%(name)s 已取消",
+ "%(name)s wants to verify": "%(name)s 想要驗證",
+ "You sent a verification request": "您已傳送了驗證請求"
}
From 163f9f057fd39db2beb45ecd060a34bdb49e5f2e Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Stanislav=20Luke=C5=A1?=
Date: Sun, 10 Nov 2019 22:20:21 +0000
Subject: [PATCH 040/334] Translated using Weblate (Czech)
Currently translated at 99.9% (1863 of 1864 strings)
Translation: Riot Web/matrix-react-sdk
Translate-URL: https://translate.riot.im/projects/riot-web/matrix-react-sdk/cs/
---
src/i18n/strings/cs.json | 179 +++++++++++++++++++++++++++++++++++++--
1 file changed, 174 insertions(+), 5 deletions(-)
diff --git a/src/i18n/strings/cs.json b/src/i18n/strings/cs.json
index 2d3088c279..e4e01b0116 100644
--- a/src/i18n/strings/cs.json
+++ b/src/i18n/strings/cs.json
@@ -1716,7 +1716,7 @@
"Remove messages": "Odstranit zprávy",
"Notify everyone": "Upozornění pro celou místnost",
"Enable encryption?": "Povolit šifrování?",
- "Once enabled, encryption for a room cannot be disabled. Messages sent in an encrypted room cannot be seen by the server, only by the participants of the room. Enabling encryption may prevent many bots and bridges from working correctly. Learn more about encryption.": "Po zapnutí už nelze šifrování v této místnosti zakázat. Zprávy v šifrovaných místostech můžou číst jenom členové místnosti, server se k obsahu nedostane. Šifrování místností nepodporuje většina botů a bridgů. Více informací o šifrování.",
+ "Once enabled, encryption for a room cannot be disabled. Messages sent in an encrypted room cannot be seen by the server, only by the participants of the room. Enabling encryption may prevent many bots and bridges from working correctly. Learn more about encryption.": "Po zapnutí už nelze šifrování v této místnosti zakázat. Zprávy v šifrovaných místostech můžou číst jenom členové místnosti, server se k obsahu nedostane. Šifrování místností nepodporuje většina botů a propojení. Více informací o šifrování.",
"Error updating main address": "Nepovedlo se změnit hlavní adresu",
"There was an error updating the room's main address. It may not be allowed by the server or a temporary failure occurred.": "Nastala chyba při pokusu o nastavení hlavní adresy místnosti. Mohl to zakázat server, nebo to může být dočasná chyba.",
"Error creating alias": "Nepovedlo se vyrobit alias",
@@ -1957,8 +1957,8 @@
"If you don't want to use to discover and be discoverable by existing contacts you know, enter another identity server below.": "Pokud nechcete na hledání existujících kontaktů používat server , zvolte si jiný server.",
"Identity Server": "Server identit",
"You are not currently using an identity server. To discover and be discoverable by existing contacts you know, add one below.": "Aktuálně nepoužíváte žádný server identit. Na hledání existujících kontaktů a přidání se do registru kontatů přidejte server identit níže.",
- "Disconnecting from your identity server will mean you won't be discoverable by other users and you won't be able to invite others by email or phone.": "Odpojení ze serveru identit znamená, že vás nepůjde najít podle emailové adresy a telefonního čísla and vy taky nebudete moct hledat ostatní.",
- "Using an identity server is optional. If you choose not to use an identity server, you won't be discoverable by other users and you won't be able to invite others by email or phone.": "Použití serveru identit je volitelné. Nemusíte server identit používat, ale nepůjde vás pak najít podle emailové adresy a telefonního čísla a vy také nebudete moct hledat ostatní.",
+ "Disconnecting from your identity server will mean you won't be discoverable by other users and you won't be able to invite others by email or phone.": "Odpojení ze serveru identit znamená, že vás nepůjde najít podle emailové adresy ani telefonního čísla and vy také nebudete moct hledat ostatní.",
+ "Using an identity server is optional. If you choose not to use an identity server, you won't be discoverable by other users and you won't be able to invite others by email or phone.": "Použití serveru identit je volitelné. Nemusíte server identit používat, ale nepůjde vás pak najít podle emailové adresy ani telefonního čísla a vy také nebudete moct hledat ostatní.",
"Do not use an identity server": "Nepoužívat server identit",
"Enter a new identity server": "Zadejte nový server identit",
"Failed to update integration manager": "Nepovedlo se aktualizovat správce integrací",
@@ -1969,7 +1969,7 @@
"You are currently using %(serverName)s to manage your bots, widgets, and sticker packs.": "Aktálně používáte %(serverName)s na správu robotů, widgetů, and balíků samolepek.",
"Add which integration manager you want to manage your bots, widgets, and sticker packs.": "Zadejte kterého správce integrací chcete používat na správu robotů, widgetů a balíků samolepek.",
"Integration Manager": "Správce integrací",
- "Enter a new integration manager": "Přidat nový správce integrací",
+ "Enter a new integration manager": "Přidat nového správce integrací",
"Agree to the identity server (%(serverName)s) Terms of Service to allow yourself to be discoverable by email address or phone number.": "Musíte odsouhlasit podmínky použití serveru (%(serverName)s) abyste se mohli zapsat do registru emailových adres a telefonních čísel.",
"Deactivate account": "Deaktivovat účet",
"Always show the window menu bar": "Vždy zobrazovat horní lištu okna",
@@ -2002,5 +2002,174 @@
"Command Help": "Nápověda příkazu",
"Integrations Manager": "Správce integrací",
"Find others by phone or email": "Hledat ostatní pomocí emailu nebo telefonu",
- "Be found by phone or email": "Umožnit ostatním mě nalézt pomocí emailu nebo telefonu"
+ "Be found by phone or email": "Umožnit ostatním mě nalézt pomocí emailu nebo telefonu",
+ "Add Email Address": "Přidat emailovou adresu",
+ "Add Phone Number": "Přidat telefonní číslo",
+ "This action requires accessing the default identity server to validate an email address or phone number, but the server does not have any terms of service.": "Tato akce vyžaduje přístup k výchozímu serveru identity aby šlo ověřit email a telefon, ale server nemá podmínky použití.",
+ "Trust": "Důvěra",
+ "%(name)s (%(userId)s)": "%(name)s (%(userId)s)",
+ "Multiple integration managers": "Více správců integrací",
+ "Use the new, consistent UserInfo panel for Room Members and Group Members": "Používat nový, konzistentní panel s informaci o uživalelích pro členy místností and skupin",
+ "Send verification requests in direct message": "Poslat žádost o verifikaci jako přímou zprávu",
+ "Use the new, faster, composer for writing messages": "Použít nový, rychlejší editor zpráv",
+ "Show previews/thumbnails for images": "Zobrazovat náhledy obrázků",
+ "You should remove your personal data from identity server before disconnecting. Unfortunately, identity server is currently offline or cannot be reached.": "Před odpojením byste měli smazat osobní údaje ze serveru identit . Bohužel, server je offline nebo neodpovídá.",
+ "You should:": "Měli byste:",
+ "check your browser plugins for anything that might block the identity server (such as Privacy Badger)": "zkontrolujte, jestli nemáte v prohlížeči nějaký doplněk blokující server identit (například Privacy Badger)",
+ "contact the administrators of identity server ": "konaktujte administrátora serveru identit ",
+ "wait and try again later": "počkejte z zkuste to znovu později",
+ "Discovery": "Nalezitelnost",
+ "Clear cache and reload": "Smazat mezipaměť a načíst znovu",
+ "Show tray icon and minimize window to it on close": "Zobrazovat systémovou ikonu a minimalizovat při zavření",
+ "Read Marker lifetime (ms)": "životnost značky přečteno (ms)",
+ "Read Marker off-screen lifetime (ms)": "životnost značky přečteno mimo obrazovku (ms)",
+ "A device's public name is visible to people you communicate with": "Veřejné jméno zařízení je viditelné pro lidi se kterými komunikujete",
+ "Upgrade the room": "Upgradovat místnost",
+ "Enable room encryption": "Povolit v místnosti šifrování",
+ "Error changing power level requirement": "Chyba změny požadavku na úroveň oprávnění",
+ "An error occurred changing the room's power level requirements. Ensure you have sufficient permissions and try again.": "Došlo k chybě při změně požadované úrovně oprávnění v místnosti. Ubezpečte se, že na to máte dostatečná práva a zkuste to znovu.",
+ "Error changing power level": "Chyba při změně úrovně oprávnění",
+ "An error occurred changing the user's power level. Ensure you have sufficient permissions and try again.": "Došlo k chybě při změně úrovně oprávnění uživatele. Ubezpečte se, že na to máte dostatečná práva a zkuste to znovu.",
+ "Unable to revoke sharing for email address": "Nepovedlo se zrušit sdílení emailové adresy",
+ "Unable to share email address": "Nepovedlo se nasdílet emailovou adresu",
+ "Your email address hasn't been verified yet": "Vaše emailová adresa nebyla zatím verifikována",
+ "Click the link in the email you received to verify and then click continue again.": "Pro verifikaci a pokračování klikněte na odkaz v emailu, který vím přišel.",
+ "Verify the link in your inbox": "Ověřte odkaz v emailové schánce",
+ "Complete": "Dokončit",
+ "Revoke": "Zneplatnit",
+ "Share": "Sdílet",
+ "Discovery options will appear once you have added an email above.": "Možnosti nalezitelnosti se objeví až výše přidáte svou emailovou adresu.",
+ "Unable to revoke sharing for phone number": "Nepovedlo se zrušit sdílení telefonního čísla",
+ "Unable to share phone number": "Nepovedlo se nasdílet telefonní číslo",
+ "Please enter verification code sent via text.": "Zadejte prosím ověřovací SMS kód.",
+ "Discovery options will appear once you have added a phone number above.": "Možnosti nalezitelnosti se objeví až zadáte telefonní číslo výše.",
+ "Remove %(email)s?": "Odstranit %(email)s?",
+ "Remove %(phone)s?": "Odstranit %(phone)s?",
+ "A text message has been sent to +%(msisdn)s. Please enter the verification code it contains.": "SMS zpráva byla odeslána na +%(msisdn)s. Zadejte prosím ověřovací kód, který obsahuje.",
+ "No recent messages by %(user)s found": "Nebyly nalezeny žádné nedávné zprávy od uživatele %(user)s",
+ "Try scrolling up in the timeline to see if there are any earlier ones.": "Zkuste zascrollovat nahoru, jestli tam nejsou nějaké dřívější.",
+ "Remove recent messages by %(user)s": "Odstranit nedávné zprávy od uživatele %(user)s",
+ "You are about to remove %(count)s messages by %(user)s. This cannot be undone. Do you wish to continue?|other": "Odstraňujeme %(count)s zpráv od %(user)s. Nelze to vzít zpět. Chcete pokračovat?",
+ "You are about to remove %(count)s messages by %(user)s. This cannot be undone. Do you wish to continue?|one": "Odstraňujeme jednu zprávu od %(user)s. Nelze to vzít zpět. Chcete pokračovat?",
+ "For a large amount of messages, this might take some time. Please don't refresh your client in the meantime.": "Pro větší množství zpráv to může nějakou dobu trvat. V průběhu prosím neobnovujte klienta.",
+ "Remove %(count)s messages|other": "Odstranit %(count)s zpráv",
+ "Remove %(count)s messages|one": "Odstranit zprávu",
+ "Deactivate user?": "Deaktivovat uživatele?",
+ "Deactivating this user will log them out and prevent them from logging back in. Additionally, they will leave all the rooms they are in. This action cannot be reversed. Are you sure you want to deactivate this user?": "Deaktivování uživatele ho odhlásí a zabrání mu v opětovném přihlášení. Navíc bude ostraněn ze všech místností. Akci nelze vzít zpět. Jste si jistí, že chcete uživatele deaktivovat?",
+ "Deactivate user": "Deaktivovat uživatele",
+ "Remove recent messages": "Odstranit nedávné zprávy",
+ "Bold": "Tučně",
+ "Italics": "Kurzívou",
+ "Strikethrough": "Přešktnutě",
+ "Code block": "Blok kódu",
+ "Room %(name)s": "Místnost %(name)s",
+ "Recent rooms": "Nedávné místnosti",
+ "Loading room preview": "Načítání náhdledu místnosti",
+ "An error (%(errcode)s) was returned while trying to validate your invite. You could try to pass this information on to a room admin.": "Při ověřování pozvánky jsme dostali chybu (%(errcode)s). Můžete zkusit tuto informaci předat administrátorovi místnosti.",
+ "This invite to %(roomName)s was sent to %(email)s which is not associated with your account": "Pozvánka do místnosti %(roomName)s byla poslána na %(email)s, který není přidaný k tomuto účtu",
+ "Link this email with your account in Settings to receive invites directly in Riot.": "Přidejte si tento email k účtu v Nastavení, abyste dostávali pozvání přímo v Riotu.",
+ "This invite to %(roomName)s was sent to %(email)s": "Pozvánka do %(roomName)s byla odeslána na %(email)s",
+ "Use an identity server in Settings to receive invites directly in Riot.": "Používat server identit z nastavení k přijímání pozvánek přímo v Riotu.",
+ "Share this email in Settings to receive invites directly in Riot.": "Sdílet tento email v nastavení, abyste mohli dostávat pozvánky přímo v Riotu.",
+ "%(count)s unread messages including mentions.|other": "%(count)s nepřečtených zpráv a zmínek.",
+ "%(count)s unread messages including mentions.|one": "Nepřečtená zmínka.",
+ "%(count)s unread messages.|other": "%(count)s nepřečtených zpráv.",
+ "%(count)s unread messages.|one": "Nepřečtená zpráva.",
+ "Unread mentions.": "Nepřečtená zmínka.",
+ "Unread messages.": "Nepřečtené zprávy.",
+ "Failed to connect to integrations server": "Nepovedlo se připojit k serveru integrací",
+ "No integrations server is configured to manage stickers with": "Žádný server integrací není nakonfigurován aby spravoval samolepky",
+ "Trust & Devices": "Důvěra & Zařízení",
+ "Direct messages": "Přímé zprávy",
+ "Failed to deactivate user": "Deaktivace uživatele se nezdařila",
+ "This client does not support end-to-end encryption.": "Tento klient nepodporuje end-to-end šifrování.",
+ "Messages in this room are not end-to-end encrypted.": "Zprávy nejsou end-to-end šifrované.",
+ "React": "Reagovat",
+ "Message Actions": "Akce zprávy",
+ "Show image": "Zobrazit obrázek",
+ "You verified %(name)s": "Ověřili jste %(name)s",
+ "You cancelled verifying %(name)s": "Zrušili jste ověření %(name)s",
+ "%(name)s cancelled verifying": "%(name)s zrušil/a ověření",
+ "You accepted": "Přijali jste",
+ "%(name)s accepted": "%(name)s přijal/a",
+ "You cancelled": "Zrušili jste",
+ "%(name)s cancelled": "%(name)s zrušil/a",
+ "%(name)s wants to verify": "%(name)s chce ověřit",
+ "You sent a verification request": "Poslali jste požadavek na ověření",
+ "Show all": "Zobrazit vše",
+ "Edited at %(date)s. Click to view edits.": "Pozměněno v %(date)s. Klinutím zobrazíte změny.",
+ "Frequently Used": "Často používané",
+ "Smileys & People": "Obličeje & Lidé",
+ "Animals & Nature": "Zvířata & Příroda",
+ "Food & Drink": "Jídlo & Nápoje",
+ "Activities": "Aktivity",
+ "Travel & Places": "Cestování & Místa",
+ "Objects": "Objekty",
+ "Symbols": "Symboly",
+ "Flags": "Vlajky",
+ "Quick Reactions": "Rychlé reakce",
+ "Cancel search": "Zrušit hledání",
+ "Please create a new issue on GitHub so that we can investigate this bug.": "Vyrobte prosím nové issue na GitHubu abychom mohli chybu opravit.",
+ "%(severalUsers)smade no changes %(count)s times|other": "%(severalUsers)s neudělali %(count)s krát žádnou změnu",
+ "%(severalUsers)smade no changes %(count)s times|one": "%(severalUsers)sneudělali žádnou změnu",
+ "%(oneUser)smade no changes %(count)s times|other": "%(oneUser)sneudělal %(count)s krát žádnou změnu",
+ "%(oneUser)smade no changes %(count)s times|one": "%(oneUser)sneudělal žádnou změnu",
+ "Room alias": "Alias místnosti",
+ "e.g. my-room": "například moje-mistost",
+ "Please provide a room alias": "Zadejte prosím alias místnosti",
+ "This alias is available to use": "Tento alias je volný",
+ "This alias is already in use": "Tento alias už je používán",
+ "Use bots, bridges, widgets and sticker packs": "Použít roboty, propojení, widgety a balíky samolepek",
+ "Terms of Service": "Podmínky použití",
+ "To continue you need to accept the terms of this service.": "Musíte souhlasit s podmínkami použití, abychom mohli pokračovat.",
+ "Service": "Služba",
+ "Summary": "Shrnutí",
+ "Document": "Dokument",
+ "Upload all": "Nahrát vše",
+ "Resend edit": "Odeslat změnu znovu",
+ "Resend %(unsentCount)s reaction(s)": "Poslat %(unsentCount)s reakcí znovu",
+ "Resend removal": "Odeslat smazání znovu",
+ "Report Content": "Nahlásit obsah",
+ "Missing captcha public key in homeserver configuration. Please report this to your homeserver administrator.": "Na domovském serveru chybí veřejný klíč pro captcha. Nahlaste to prosím administrátorovi serveru.",
+ "No identity server is configured so you cannot add an email address in order to reset your password in the future.": "Žádný server identit není nakonfigurován, takže nemůžete přidat emailovou adresu pro obnovení hesla.",
+ "Set an email for account recovery. Use email or phone to optionally be discoverable by existing contacts.": "Nastavit emailovou adresu pro obnovení hesla. Také můžete použít email nebo telefon, aby vás vaši přátelé snadno nalezli.",
+ "Set an email for account recovery. Use email to optionally be discoverable by existing contacts.": "Nastavit emailovou adresu pro obnovení hesla. Také můžete použít email, aby vás vaši přátelé snadno nalezli.",
+ "Enter your custom homeserver URL What does this mean?": "Zadejte adresu domovského serveru Co to znamená?",
+ "Enter your custom identity server URL What does this mean?": "Zadejte adresu serveru identit Co to znamená?",
+ "Explore": "Prohlížet",
+ "Filter": "Filtrovat",
+ "Filter rooms…": "Filtrovat místnosti…",
+ "%(creator)s created and configured the room.": "%(creator)s vytvořil a nakonfiguroval místnost.",
+ "Preview": "Náhled",
+ "View": "Zobrazit",
+ "Find a room…": "Najít místnost…",
+ "Find a room… (e.g. %(exampleRoom)s)": "Najít místnost… (například %(exampleRoom)s)",
+ "If you can't find the room you're looking for, ask for an invite or Create a new room.": "Pokud nemůžete nelézt místnost, kterou hledáte, napište o pozvánku nebo si jí Vytvořte.",
+ "Explore rooms": "Prohlížet místnosti",
+ "Jump to first unread room.": "Skočit na první nepřečtenou místnost.",
+ "Jump to first invite.": "Skočit na první pozvánku.",
+ "No identity server is configured: add one in server settings to reset your password.": "Žádný server identit není nakonfigurován: přidejte si ho v nastavení, abyste mohli obnovit heslo.",
+ "This account has been deactivated.": "Tento účet byl deaktivován.",
+ "Your new account (%(newAccountId)s) is registered, but you're already logged into a different account (%(loggedInUserId)s).": "nový účet (%(newAccountId)s) je registrován, ale už jste přihlášeni pod jiným účtem (%(loggedInUserId)s).",
+ "Continue with previous account": "Pokračovat s předchozím účtem",
+ "Log in to your new account.": "Přihlaste se svým novým účtem.",
+ "You can now close this window or log in to your new account.": "Teď můžete okno zavřít, nebo se přihlásit svým novým účtem.",
+ "Registration Successful": "Úspěšná registrace",
+ "Failed to re-authenticate due to a homeserver problem": "Kvůli problémům s domovským server se nepovedlo autentifikovat znovu",
+ "Failed to re-authenticate": "Nepovedlo se autentifikovat",
+ "Regain access to your account and recover encryption keys stored on this device. Without them, you won’t be able to read all of your secure messages on any device.": "Získejte znovu přístup k účtu a obnovte si šifrovací klíče uložené na tomto zařízení. Bez nich nebudete schopni číst zabezpečené zprávy na některých zařízeních.",
+ "Enter your password to sign in and regain access to your account.": "Zadejte heslo pro přihlášení a obnovte si přístup k účtu.",
+ "Forgotten your password?": "Zapomněli jste heslo?",
+ "Sign in and regain access to your account.": "Přihlaste se a získejte přístup ke svému účtu.",
+ "You cannot sign in to your account. Please contact your homeserver admin for more information.": "Nemůžete se přihlásit do svého účtu. Kontaktujte administrátora domovského serveru pro více informací.",
+ "You're signed out": "Jste odhlášeni",
+ "Clear personal data": "Smazat osobní data",
+ "Warning: Your personal data (including encryption keys) is still stored on this device. Clear it if you're finished using this device, or want to sign in to another account.": "Varování: Vaše osobní data (včetně šifrovacích klíčů) jsou pořád uložena na tomto zařízení. Smažte je, pokud už toto zařízení nehodláte používat nebo se přihlašte pod jiný účet.",
+ "Command Autocomplete": "Automatické doplňování příkazů",
+ "Community Autocomplete": "Automatické doplňování komunit",
+ "DuckDuckGo Results": "Výsledky hledání DuckDuckGo",
+ "Emoji Autocomplete": "Automatické doplňování Emodži",
+ "Notification Autocomplete": "Automatické doplňování upozornění",
+ "Room Autocomplete": "Automatické doplňování místností",
+ "User Autocomplete": "Automatické doplňování uživatelů"
}
From 0bfbf34c3956c8364134739a78a3c983597af814 Mon Sep 17 00:00:00 2001
From: Tirifto
Date: Sun, 10 Nov 2019 21:24:20 +0000
Subject: [PATCH 041/334] Translated using Weblate (Esperanto)
Currently translated at 99.9% (1862 of 1864 strings)
Translation: Riot Web/matrix-react-sdk
Translate-URL: https://translate.riot.im/projects/riot-web/matrix-react-sdk/eo/
---
src/i18n/strings/eo.json | 155 ++++++++++++++++++++++++++++++++++++---
1 file changed, 143 insertions(+), 12 deletions(-)
diff --git a/src/i18n/strings/eo.json b/src/i18n/strings/eo.json
index 7e4599aaea..bbed6773b5 100644
--- a/src/i18n/strings/eo.json
+++ b/src/i18n/strings/eo.json
@@ -182,7 +182,7 @@
"Answer": "Respondi",
"Send anyway": "Tamen sendi",
"Send": "Sendi",
- "Enable automatic language detection for syntax highlighting": "Ŝalti memfaran rekonon de lingvo por sintaksa markado",
+ "Enable automatic language detection for syntax highlighting": "Ŝalti memagan rekonon de lingvo por sintaksa markado",
"Hide avatars in user and room mentions": "Kaŝi profilbildojn en mencioj de uzantoj kaj babilejoj",
"Disable big emoji in chat": "Malŝalti grandajn mienetojn en babilo",
"Don't send typing notifications": "Ne elsendi sciigojn pri tajpado",
@@ -1270,7 +1270,7 @@
"Ignored users": "Malatentaj uzantoj",
"Key backup": "Sekurkopio de ŝlosilo",
"Security & Privacy": "Sekureco & Privateco",
- "Voice & Video": "Voĉo k Vido",
+ "Voice & Video": "Voĉo kaj vido",
"Upgrade room to version %(ver)s": "Ĝisdatigi ĉambron al versio %(ver)s",
"Room information": "Ĉambraj informoj",
"Internal room ID:": "Ena ĉambra identigilo:",
@@ -1507,7 +1507,7 @@
"Changes made to your community name and avatar might not be seen by other users for up to 30 minutes.": "Ŝanĝoj al viaj komunumaj nomo kaj profilbildo eble ne montriĝos al aliaj uzantoj ĝis 30 minutoj.",
"Who can join this community?": "Kiu povas aliĝi al tiu ĉi komunumo?",
"This room is not public. You will not be able to rejoin without an invite.": "Ĉi tiu ĉambro ne estas publika. Vi ne povos realiĝi sen invito.",
- "Can't leave Server Notices room": "Ne eblas eliri el ĉambro « Server Notices »",
+ "Can't leave Server Notices room": "Ne eblas eliri el ĉambro « Server Notices »",
"Revoke invite": "Nuligi inviton",
"Invited by %(sender)s": "Invitita de %(sender)s",
"Error updating main address": "Ĝisdatigo de la ĉefa adreso eraris",
@@ -1710,7 +1710,7 @@
"Connect this device to Key Backup": "Konekti ĉi tiun aparaton al Savkopiado de ŝlosiloj",
"Start using Key Backup": "Ekuzi Savkopiadon de ŝlosiloj",
"Timeline": "Historio",
- "Autocomplete delay (ms)": "Prokrasto de memfara kompletigo",
+ "Autocomplete delay (ms)": "Prokrasto de memaga kompletigo",
"Upgrade this room to the recommended room version": "Gradaltigi ĉi tiun ĉambron al rekomendata ĉambra versio",
"Open Devtools": "Malfermi programistajn ilojn",
"Uploaded sound": "Alŝutita sono",
@@ -1747,7 +1747,7 @@
"Go to Settings": "Iri al agordoj",
"Flair": "Etikedo",
"No Audio Outputs detected": "Neniu soneligo troviĝis",
- "Send %(eventType)s events": "Sendi okazojn de tipo «%(eventType)s»",
+ "Send %(eventType)s events": "Sendi okazojn de tipo « %(eventType)s »",
"Select the roles required to change various parts of the room": "Elektu la rolojn postulatajn por ŝanĝado de diversaj partoj de la ĉambro",
"Once enabled, encryption for a room cannot be disabled. Messages sent in an encrypted room cannot be seen by the server, only by the participants of the room. Enabling encryption may prevent many bots and bridges from working correctly. Learn more about encryption.": "Post ŝalto, ĉifrado de ĉambro ne povas esti malŝaltita. Mesaĝoj senditaj al ĉifrata ĉambro ne estas videblaj por la servilo, nur por la partoprenantoj de la ĉambro. Ŝalto de ĉifrado eble malfunkciigos iujn robotojn kaj pontojn. Eksciu plion pri ĉifrado.",
"To link to this room, please add an alias.": "Por ligili al la ĉambro, bonvolu aldoni kromnomon.",
@@ -1756,7 +1756,7 @@
"Once enabled, encryption cannot be disabled.": "Post ŝalto, ne plu eblas malŝalti ĉifradon.",
"Encrypted": "Ĉifrata",
"Some devices in this encrypted room are not trusted": "Iuj aparatoj en ĉi tiu ĉifrata ĉambro ne estas fidataj",
- "Key share requests are sent to your other devices automatically. If you rejected or dismissed the key share request on your other devices, click here to request the keys for this session again.": "Petoj pri kunhavigo de ŝlosiloj sendiĝas al viaj aliaj aparatoj memfare. Se vi rifuzis aŭ forlasis la peton en viaj aliaj aparatoj, klaku ĉi tien por repeti la ŝlosilojn por tiu ĉi kunsido.",
+ "Key share requests are sent to your other devices automatically. If you rejected or dismissed the key share request on your other devices, click here to request the keys for this session again.": "Petoj pri kunhavigo de ŝlosiloj sendiĝas al viaj aliaj aparatoj memage. Se vi rifuzis aŭ forlasis la peton en viaj aliaj aparatoj, klaku ĉi tien por repeti la ŝlosilojn por tiu ĉi kunsido.",
"The conversation continues here.": "La interparolo pluas ĉi tie.",
"This room has been replaced and is no longer active.": "Ĉi tiu ĉambro estas anstataŭita, kaj ne plu aktivas.",
"Loading room preview": "Preparas antaŭrigardon al la ĉambro",
@@ -1790,7 +1790,7 @@
"Message edits": "Redaktoj de mesaĝoj",
"If you run into any bugs or have feedback you'd like to share, please let us know on GitHub.": "Se vi renkontas problemojn aŭ havas prikomentojn, bonvolu sciigi nin per GitHub.",
"To help avoid duplicate issues, please view existing issues first (and add a +1) or create a new issue if you can't find it.": "Por eviti duoblajn raportojn, bonvolu unue rigardi jamajn raportojn (kaj meti +1) aŭ raporti novan problemon se vi neniun trovos.",
- "Report bugs & give feedback": "Raporti erarojn ϗ sendi prikomentojn",
+ "Report bugs & give feedback": "Raporti erarojn kaj sendi prikomentojn",
"Clear Storage and Sign Out": "Vakigi memoron kaj adiaŭi",
"We encountered an error trying to restore your previous session.": "Ni renkontis eraron provante rehavi vian antaŭan kunsidon.",
"Clearing your browser's storage may fix the problem, but will sign you out and cause any encrypted chat history to become unreadable.": "Vakigo de la memoro de via foliumilo eble korektos la problemon, sed adiaŭigos vin, kaj malebligos legadon de historio de ĉifritaj babiloj.",
@@ -1865,7 +1865,7 @@
"Upgrading this room requires closing down the current instance of the room and creating a new room in its place. To give room members the best possible experience, we will:": "Gradaltigo de ĉi tiu ĉambro bezonas fermi ĝin, kaj krei novan por anstataŭi ĝin. Por plejbonigi sperton de la ĉambranoj, ni:",
"You have been logged out of all devices and will no longer receive push notifications. To re-enable notifications, sign in again on each device.": "Vi adiaŭis ĉiujn aparatojn kaj ne plu ricevados sciigojn. Por reŝalti ilin, resalutu per ĉiu el la aparatoj.",
"Invalid homeserver discovery response": "Nevalida eltrova respondo de hejmservilo",
- "Failed to get autodiscovery configuration from server": "Malsukcesis akiri agordojn de memfara eltrovado de la servilo",
+ "Failed to get autodiscovery configuration from server": "Malsukcesis akiri agordojn de memaga eltrovado de la servilo",
"Homeserver URL does not appear to be a valid Matrix homeserver": "URL por hejmservilo ŝajne ne ligas al valida hejmservilo de Matrix",
"Invalid identity server discovery response": "Nevalida eltrova respondo de identiga servilo",
"Identity server URL does not appear to be a valid identity server": "URL por identiga servilo ŝajne ne ligas al valida identiga servilo",
@@ -1932,7 +1932,7 @@
"Failed to start chat": "Malsukcesis komenci babilon",
"Messages": "Mesaĝoj",
"Actions": "Agoj",
- "Use an identity server to invite by email. Click continue to use the default identity server (%(defaultIdentityServerName)s) or manage in Settings.": "Uzu identigan servilon por inviti retpoŝte. Klaku al »[…]« por uzi la implicitan identigan servilon (%(defaultIdentityServerName)s) aŭ administru tion en Agordoj.",
+ "Use an identity server to invite by email. Click continue to use the default identity server (%(defaultIdentityServerName)s) or manage in Settings.": "Uzu identigan servilon por inviti retpoŝte. Klaku al « daŭrigi » por uzi la norman identigan servilon (%(defaultIdentityServerName)s) aŭ administru tion en Agordoj.",
"Displays list of commands with usages and descriptions": "Montras liston de komandoj kun priskribo de uzo",
"Use the new, faster, but still experimental composer for writing messages (requires refresh)": "Uzi la novan, pli rapidan, sed ankoraŭ eksperimentan komponilon de mesaĝoj (bezonas aktualigon)",
"Send read receipts for messages (requires compatible homeserver to disable)": "Sendi legokonfirmojn de mesaĝoj (bezonas akordan hejmservilon por malŝalto)",
@@ -1957,8 +1957,8 @@
"You are currently using to discover and be discoverable by existing contacts you know. You can change your identity server below.": "Vi nun uzas servilon por trovi kontaktojn, kaj troviĝi de ili. Vi povas ŝanĝi vian identigan servilon sube.",
"If you don't want to use to discover and be discoverable by existing contacts you know, enter another identity server below.": "Se vi ne volas uzi servilon por trovi kontaktojn kaj troviĝi mem, enigu alian identigan servilon sube.",
"You are not currently using an identity server. To discover and be discoverable by existing contacts you know, add one below.": "Vi nun ne uzas identigan servilon. Por trovi kontaktojn kaj troviĝi de ili mem, aldonu iun sube.",
- "Disconnecting from your identity server will mean you won't be discoverable by other users and you won't be able to invite others by email or phone.": "Malkonektiĝo de via identiga servilo signifas, ke vi ne povos troviĝi de aliaj uzantoj, kaj vi ne povos memfare inviti aliajn per retpoŝto aŭ telefono.",
- "Using an identity server is optional. If you choose not to use an identity server, you won't be discoverable by other users and you won't be able to invite others by email or phone.": "Vi ne devas uzi identigan servilon. Se vi tion elektos, vi ne povos troviĝi de aliaj uzantoj, kaj vi ne povos memfare inviti ilin per retpoŝto aŭ telefono.",
+ "Disconnecting from your identity server will mean you won't be discoverable by other users and you won't be able to invite others by email or phone.": "Malkonektiĝo de via identiga servilo signifas, ke vi ne povos troviĝi de aliaj uzantoj, kaj vi ne povos memage inviti aliajn per retpoŝto aŭ telefono.",
+ "Using an identity server is optional. If you choose not to use an identity server, you won't be discoverable by other users and you won't be able to invite others by email or phone.": "Vi ne devas uzi identigan servilon. Se vi tion elektos, vi ne povos troviĝi de aliaj uzantoj, kaj vi ne povos memage inviti ilin per retpoŝto aŭ telefono.",
"Agree to the identity server (%(serverName)s) Terms of Service to allow yourself to be discoverable by email address or phone number.": "Konsentu al uzkondiĉoj de la identiga servilo (%(serverName)s) por esti trovita per retpoŝtadreso aŭ telefonnumero.",
"Discovery": "Trovado",
"Deactivate account": "Malaktivigi konton",
@@ -1996,5 +1996,136 @@
"View": "Rigardo",
"Find a room…": "Trovi ĉambron…",
"If you can't find the room you're looking for, ask for an invite or Create a new room.": "Se vi ne povas travi la serĉatan ĉambron, petu inviton aŭ kreu novan ĉambron.",
- "Explore rooms": "Esplori ĉambrojn"
+ "Explore rooms": "Esplori ĉambrojn",
+ "Add Email Address": "Aldoni retpoŝtadreson",
+ "Add Phone Number": "Aldoni telefonnumeron",
+ "This action requires accessing the default identity server to validate an email address or phone number, but the server does not have any terms of service.": "Ĉi tiu ago bezonas atingi la norman identigan servilon por kontroli retpoŝtadreson aŭ telefonnumeron, sed la servilo ne havas uzokondiĉojn.",
+ "Trust": "Fido",
+ "%(name)s (%(userId)s)": "%(name)s (%(userId)s)",
+ "Multiple integration managers": "Pluraj kunigiloj",
+ "Use the new, consistent UserInfo panel for Room Members and Group Members": "Uzi la novan, koheran informan panelon por ĉambranoj kaj grupanoj",
+ "Send verification requests in direct message": "Sendi kontrolajn petojn per rekta mesaĝo",
+ "Use the new, faster, composer for writing messages": "Uzi la novan, pli rapidan verkilon de mesaĝoj",
+ "Show previews/thumbnails for images": "Montri antaŭrigardojn/bildetojn je bildoj",
+ "You should remove your personal data from identity server before disconnecting. Unfortunately, identity server is currently offline or cannot be reached.": "Vi forigu viajn personajn datumojn de identiga servilo antaŭ ol vi malkonektiĝos. Bedaŭrinde, identiga servilo estas nuntempe eksterreta kaj ne eblas ĝin atingi.",
+ "You should:": "Vi:",
+ "check your browser plugins for anything that might block the identity server (such as Privacy Badger)": "kontrolu kromprogramojn de via foliumilo je ĉio, kio povus malhelpi konekton al la identiga servilo (ekzemple « Privacy Badger »)",
+ "contact the administrators of identity server ": "kontaktu la administrantojn de la identiga servilo ",
+ "wait and try again later": "atendu kaj reprovu pli poste",
+ "Failed to update integration manager": "Malsukcesis ĝisdatigi kunigilon",
+ "Integration manager offline or not accessible.": "Kunigilo estas eksterreta aŭ alie neatingebla",
+ "Terms of service not accepted or the integration manager is invalid.": "Aŭ zokondiĉoj ne akceptiĝis, aŭ la kunigilo estas nevalida.",
+ "Integration manager has no terms of service": "Kunigilo havas neniujn uzokondiĉojn",
+ "The integration manager you have chosen does not have any terms of service.": "La elektita kunigilo havas neniujn uzokondiĉojn.",
+ "You are currently using %(serverName)s to manage your bots, widgets, and sticker packs.": "Vi nun mastrumas viajn robotojn, fenestaĵojn, kaj glumarkarojn per %(serverName)s.",
+ "Add which integration manager you want to manage your bots, widgets, and sticker packs.": "Aldonu kiun kunigilon vi volas por mastrumi viajn robotojn, fenestraĵojn, kaj glumarkarojn.",
+ "Integration Manager": "Kunigilo",
+ "Enter a new integration manager": "Metu novan kunigilon",
+ "Clear cache and reload": "Vakigi kaŝmemoron kaj relegi",
+ "Show tray icon and minimize window to it on close": "Montri pletan bildsimbolon kaj tien plejetigi la fenestron je fermo",
+ "Read Marker lifetime (ms)": "Vivodaŭro de legomarko (ms)",
+ "Read Marker off-screen lifetime (ms)": "Vivodaŭro de eksterekrana legomarko (ms)",
+ "Unable to revoke sharing for email address": "Ne povas senvalidigi havigadon je retpoŝtadreso",
+ "Unable to share email address": "Ne povas havigi vian retpoŝtadreson",
+ "Your email address hasn't been verified yet": "Via retpoŝtadreso ankoraŭ ne kontroliĝis",
+ "Click the link in the email you received to verify and then click continue again.": "Klaku la ligilon en la ricevita retletero por kontroli, kaj poste reklaku al « daŭrigi ».",
+ "Verify the link in your inbox": "Kontrolu la ligilon en via ricevujo.",
+ "Complete": "Fini",
+ "Revoke": "Senvalidigi",
+ "Share": "Havigi",
+ "Discovery options will appear once you have added an email above.": "Eltrovaj agordoj aperos kiam vi aldonos supre retpoŝtadreson.",
+ "Unable to revoke sharing for phone number": "Ne povas senvalidigi havigadon je telefonnumero",
+ "Unable to share phone number": "Ne povas havigi telefonnumeron",
+ "Please enter verification code sent via text.": "Bonvolu enigi kontrolan kodon senditan per tekstmesaĝo.",
+ "Discovery options will appear once you have added a phone number above.": "Eltrovaj agordoj aperos kiam vi aldonos telefonnumeron supre.",
+ "A text message has been sent to +%(msisdn)s. Please enter the verification code it contains.": "Tekstmesaĝo sendiĝis al +%(msisdn)s. Bonvolu enigi la kontrolan kodon enhavitan.",
+ "Try scrolling up in the timeline to see if there are any earlier ones.": "Provu rulumi supren tra la historio por kontroli, ĉu ne estas iuj pli fruaj.",
+ "You are about to remove %(count)s messages by %(user)s. This cannot be undone. Do you wish to continue?|one": "Vi estas forigonta 1 mesaĝon de %(user)s. Ne eblas tion malfari. Ĉu vi volas pluigi?",
+ "Remove %(count)s messages|one": "Forigi 1 mesaĝon",
+ "Room %(name)s": "Ĉambro %(name)s",
+ "Recent rooms": "Freŝaj vizititaj ĉambroj",
+ "An error (%(errcode)s) was returned while trying to validate your invite. You could try to pass this information on to a room admin.": "Eraris (%(errcode)s) validigo de via invito. Vi povas transdoni ĉi tiun informon al ĉambra administranto.",
+ "This invite to %(roomName)s was sent to %(email)s which is not associated with your account": "Ĉi tiu invito al %(roomName)s sendiĝis al %(email)s, kiu ne estas ligita al via konto",
+ "Link this email with your account in Settings to receive invites directly in Riot.": "Ligu ĉi tiun retpoŝtadreson al via konto en Agordoj por ricevadi invitojn rekte per Riot.",
+ "This invite to %(roomName)s was sent to %(email)s": "La invito al %(roomName)s sendiĝis al %(email)s",
+ "Use an identity server in Settings to receive invites directly in Riot.": "Uzu identigan servilon en Agordoj por ricevadi invitojn rekte per Riot.",
+ "Share this email in Settings to receive invites directly in Riot.": "Havigu ĉi tiun retpoŝtadreson per Agordoj por ricevadi invitojn rekte per Riot.",
+ "%(count)s unread messages including mentions.|other": "%(count)s nelegitaj mesaĝoj, inkluzive menciojn.",
+ "%(count)s unread messages including mentions.|one": "1 nelegita mencio.",
+ "%(count)s unread messages.|other": "%(count)s nelegitaj mesaĝoj.",
+ "%(count)s unread messages.|one": "1 nelegita mesaĝo.",
+ "Unread mentions.": "Nelegitaj mencioj.",
+ "Unread messages.": "Nelegitaj mesaĝoj.",
+ "Trust & Devices": "Fido kaj Aparatoj",
+ "Direct messages": "Rektaj mesaĝoj",
+ "Failed to deactivate user": "Malsukcesis malaktivigi uzanton",
+ "This client does not support end-to-end encryption.": "Ĉi tiu kliento ne subtenas tutvojan ĉifradon.",
+ "Messages in this room are not end-to-end encrypted.": "Mesaĝoj en ĉi tiu ĉambro ne estas tutvoje ĉifrataj.",
+ "React": "Reagi",
+ "Message Actions": "Mesaĝaj agoj",
+ "Show image": "Montri bildon",
+ "You verified %(name)s": "Vi kontrolis %(name)s",
+ "You cancelled verifying %(name)s": "Vi nuligis kontrolon de %(name)s",
+ "%(name)s cancelled verifying": "%(name)s nuligis kontrolon",
+ "You accepted": "Vi akceptis",
+ "%(name)s accepted": "%(name)s akceptis",
+ "You cancelled": "Vi nuligis",
+ "%(name)s cancelled": "%(name)s nuligis",
+ "%(name)s wants to verify": "%(name)s volas kontroli",
+ "You sent a verification request": "Vi sendis peton de kontrolo",
+ "Frequently Used": "Ofte uzataj",
+ "Smileys & People": "Mienoj kaj homoj",
+ "Animals & Nature": "Bestoj kaj naturo",
+ "Food & Drink": "Manĝaĵoj kaj trinkaĵoj",
+ "Activities": "Agadoj",
+ "Travel & Places": "Lokoj kaj vojaĝado",
+ "Objects": "Aĵoj",
+ "Symbols": "Simboloj",
+ "Flags": "Flagoj",
+ "Quick Reactions": "Rapidaj reagoj",
+ "Cancel search": "Nuligi serĉon",
+ "Please create a new issue on GitHub so that we can investigate this bug.": "Bonvolu raporti novan problemon je GitHub, por ke ni povu ĝin esplori.",
+ "Room alias": "Kromnomo de ĉambro",
+ "e.g. my-room": "ekzemple mia-chambro",
+ "Please provide a room alias": "Bonvolu doni kromnomon de ĉambro",
+ "This alias is available to use": "Ĉi tiu kromnomo estas disponebla",
+ "This alias is already in use": "Ĉi tiu kromnomo jam estas uzata",
+ "Use an identity server to invite by email. Use the default (%(defaultIdentityServerName)s) or manage in Settings.": "Uzu identigan servilon por inviti per retpoŝto. Uzu la norman (%(defaultIdentityServerName)s) aŭ mastrumu per Agordoj.",
+ "Use an identity server to invite by email. Manage in Settings.": "Uzu identigan servilon por inviti per retpoŝto. Mastrumu per Agordoj.",
+ "Close dialog": "Fermi interagujon",
+ "Please enter a name for the room": "Bonvolu enigi nomon por la ĉambro",
+ "Set a room alias to easily share your room with other people.": "Agordu kromnomon de la ĉambro por facile ĝin kunhavigi al aliaj homoj.",
+ "This room is private, and can only be joined by invitation.": "Ĉi tiu ĉambro estas privata, kaj eblas aliĝi nur per invito.",
+ "Create a public room": "Krei publikan ĉambron",
+ "Create a private room": "Krei privatan ĉambron",
+ "Topic (optional)": "Temo (malnepra)",
+ "Make this room public": "Publikigi ĉi tiun ĉambron",
+ "Hide advanced": "Kaŝi specialajn",
+ "Show advanced": "Montri specialajn",
+ "Block users on other matrix homeservers from joining this room (This setting cannot be changed later!)": "Bloki aliĝojn al ĉi tiu ĉambro de uzantoj el aliaj Matrix-serviloj (Ĉi tiun agordon ne eblas poste ŝanĝi!)",
+ "To verify that this device can be trusted, please check that the key you see in User Settings on that device matches the key below:": "Por kontroli ke tiu ĉi aparato estas fidinda, bonvolu kontroli, ke la ŝlosilo, kiun vi vidas en viaj Agordoj de uzanto je tiu aparato, akordas kun la ŝlosilo sube:",
+ "Please fill why you're reporting.": "Bonvolu skribi, kial vi raportas.",
+ "Report Content to Your Homeserver Administrator": "Raporti enhavon al la administrantode via hejmservilo",
+ "Reporting this message will send its unique 'event ID' to the administrator of your homeserver. If messages in this room are encrypted, your homeserver administrator will not be able to read the message text or view any files or images.": "Per raporto de ĝi tiu mesaĝo vi sendos ĝian unikan « eventan identigilon » al la administranto de via hejmservilo. Se mesaĝoj en ĉi tiu ĉambro estas ĉifrataj, la administranto de via hejmservilo ne povos legi la tekston de la mesaĝo, nek rigardi dosierojn aŭ bildojn.",
+ "Send report": "Sendi raporton",
+ "Command Help": "Helpo pri komando",
+ "Integrations Manager": "Kunigilo",
+ "To continue you need to accept the terms of this service.": "Por pluigi, vi devas akcepti la uzokondiĉojn de ĉi tiu servo.",
+ "Document": "Dokumento",
+ "Report Content": "Raporti enhavon",
+ "No identity server is configured so you cannot add an email address in order to reset your password in the future.": "Neniu identiga servilo estas agordita, kaj tial vi ne povas aldoni retpoŝtadreson por ose restarigi vian pasvorton.",
+ "Enter your custom homeserver URL What does this mean?": "Enigu vian propran hejmservilan URL-on. Kion tio signifas?",
+ "Enter your custom identity server URL What does this mean?": "Enigu vian propran URL-on de identiga servilo. Kion tio signifas?",
+ "%(creator)s created and configured the room.": "%(creator)s kreis kaj agordis la ĉambron.",
+ "Find a room… (e.g. %(exampleRoom)s)": "Trovi ĉambron… (ekzemple (%(exampleRoom)s)",
+ "Jump to first unread room.": "Salti al unua nelegita ĉambro.",
+ "Jump to first invite.": "Salti al unua invito.",
+ "No identity server is configured: add one in server settings to reset your password.": "Neniu identiga servilo estas agordita: aldonu iun per la servilaj agordoj, por restarigi vian pasvorton.",
+ "Command Autocomplete": "Memkompletigo de komandoj",
+ "Community Autocomplete": "Memkompletigo de komunumoj",
+ "DuckDuckGo Results": "Rezultoj de DuckDuckGo",
+ "Emoji Autocomplete": "Memkompletigo de mienetoj",
+ "Notification Autocomplete": "Memkompletigo de sciigoj",
+ "Room Autocomplete": "Memkompletigo de ĉambroj",
+ "User Autocomplete": "Memkompletigo de uzantoj"
}
From d545a1e0b2655a55766ef9bb3016830536ea3b0b Mon Sep 17 00:00:00 2001
From: Szimszon
Date: Sun, 10 Nov 2019 12:40:40 +0000
Subject: [PATCH 042/334] Translated using Weblate (Hungarian)
Currently translated at 100.0% (1864 of 1864 strings)
Translation: Riot Web/matrix-react-sdk
Translate-URL: https://translate.riot.im/projects/riot-web/matrix-react-sdk/hu/
---
src/i18n/strings/hu.json | 13 ++++++++++++-
1 file changed, 12 insertions(+), 1 deletion(-)
diff --git a/src/i18n/strings/hu.json b/src/i18n/strings/hu.json
index 5d21a17e37..67af8a6a57 100644
--- a/src/i18n/strings/hu.json
+++ b/src/i18n/strings/hu.json
@@ -2267,5 +2267,16 @@
"Show tray icon and minimize window to it on close": "Tálcaikon mutatása és az ablak összecsukása bezáráskor",
"This action requires accessing the default identity server to validate an email address or phone number, but the server does not have any terms of service.": "Ez a művelet az e-mail cím vagy telefonszám ellenőrzése miatt hozzáférést igényel az alapértelmezett azonosítási szerverhez (), de a szervernek nincsen semmilyen felhasználási feltétele.",
"Trust": "Megbízom benne",
- "Message Actions": "Üzenet Műveletek"
+ "Message Actions": "Üzenet Műveletek",
+ "%(name)s (%(userId)s)": "%(name)s (%(userId)s)",
+ "Send verification requests in direct message": "Ellenőrzés kérés küldése közvetlen üzenetben",
+ "You verified %(name)s": "Ellenőrizted: %(name)s",
+ "You cancelled verifying %(name)s": "Az ellenőrzést megszakítottad ehhez: %(name)s",
+ "%(name)s cancelled verifying": "%(name)s megszakította az ellenőrzést",
+ "You accepted": "Elfogadtad",
+ "%(name)s accepted": "%(name)s elfogadta",
+ "You cancelled": "Megszakítottad",
+ "%(name)s cancelled": "%(name)s megszakította",
+ "%(name)s wants to verify": "%(name)s ellenőrizni szeretné",
+ "You sent a verification request": "Ellenőrzési kérést küldtél"
}
From ab9f3780194637574b28f97c4f36719b054edee3 Mon Sep 17 00:00:00 2001
From: Elwyn Malethan
Date: Sat, 9 Nov 2019 19:01:03 +0000
Subject: [PATCH 043/334] Translated using Weblate (Welsh)
Currently translated at 0.5% (9 of 1864 strings)
Translation: Riot Web/matrix-react-sdk
Translate-URL: https://translate.riot.im/projects/riot-web/matrix-react-sdk/cy/
---
src/i18n/strings/cy.json | 12 +++++++++++-
1 file changed, 11 insertions(+), 1 deletion(-)
diff --git a/src/i18n/strings/cy.json b/src/i18n/strings/cy.json
index 0967ef424b..951468c747 100644
--- a/src/i18n/strings/cy.json
+++ b/src/i18n/strings/cy.json
@@ -1 +1,11 @@
-{}
+{
+ "This email address is already in use": "Mae'r cyfeiriad e-bost yma yn cael ei ddefnyddio eisoes",
+ "This phone number is already in use": "Mae'r rhif ffôn yma yn cael ei ddefnyddio eisoes",
+ "Add Email Address": "Ychwanegu Cyfeiriad E-bost",
+ "Failed to verify email address: make sure you clicked the link in the email": "Methiant gwirio cyfeiriad e-bost: gwnewch yn siŵr eich bod wedi clicio'r ddolen yn yr e-bost",
+ "Add Phone Number": "Ychwanegu Rhif Ffôn",
+ "The platform you're on": "Y platfform rydych chi arno",
+ "The version of Riot.im": "Fersiwn Riot.im",
+ "Whether or not you're logged in (we don't record your username)": "Os ydych wedi mewngofnodi ai peidio (nid ydym yn cofnodi'ch enw defnyddiwr)",
+ "Your language of choice": "Eich iaith o ddewis"
+}
From ef0529413308504b6fde2a1d177a6db00c98dfac Mon Sep 17 00:00:00 2001
From: "J. Ryan Stinnett"
Date: Mon, 11 Nov 2019 10:24:40 +0000
Subject: [PATCH 044/334] Fix draw order when hovering composer format buttons
This ensures all 4 sides of a button show the hover border colour as intended.
Another part of https://github.com/vector-im/riot-web/issues/11203
---
res/css/views/rooms/_MessageComposerFormatBar.scss | 1 +
1 file changed, 1 insertion(+)
diff --git a/res/css/views/rooms/_MessageComposerFormatBar.scss b/res/css/views/rooms/_MessageComposerFormatBar.scss
index 80909529ee..1b5a21bed0 100644
--- a/res/css/views/rooms/_MessageComposerFormatBar.scss
+++ b/res/css/views/rooms/_MessageComposerFormatBar.scss
@@ -40,6 +40,7 @@ limitations under the License.
&:hover {
border-color: $message-action-bar-hover-border-color;
+ z-index: 1;
}
&:first-child {
From 2c5565e5020edfe0306836fb37d49a8f410df2ab Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Damir=20Jeli=C4=87?=
Date: Fri, 18 Oct 2019 16:36:31 +0200
Subject: [PATCH 045/334] MatrixChat: Add some missing semicolons.
---
src/components/structures/MatrixChat.js | 7 ++++---
1 file changed, 4 insertions(+), 3 deletions(-)
diff --git a/src/components/structures/MatrixChat.js b/src/components/structures/MatrixChat.js
index 51cf92da5f..402790df98 100644
--- a/src/components/structures/MatrixChat.js
+++ b/src/components/structures/MatrixChat.js
@@ -2130,13 +2130,13 @@ export default createReactClass({
checkpoint.roomId, checkpoint.token, 100,
checkpoint.direction);
} catch (e) {
- console.log("Seshat: Error crawling events:", e)
+ console.log("Seshat: Error crawling events:", e);
this.crawlerChekpoints.push(checkpoint);
continue
}
if (res.chunk.length === 0) {
- console.log("Seshat: Done with the checkpoint", checkpoint)
+ console.log("Seshat: Done with the checkpoint", checkpoint);
// We got to the start/end of our timeline, lets just
// delete our checkpoint and go back to sleep.
await platform.removeCrawlerCheckpoint(checkpoint);
@@ -2239,7 +2239,8 @@ export default createReactClass({
// Let us delete the checkpoint in that case, otherwise push
// the new checkpoint to be used by the crawler.
if (eventsAlreadyAdded === true && newCheckpoint.fullCrawl !== true) {
- console.log("Seshat: Checkpoint had already all events added, stopping the crawl", checkpoint);
+ console.log("Seshat: Checkpoint had already all events",
+ "added, stopping the crawl", checkpoint);
await platform.removeCrawlerCheckpoint(newCheckpoint);
} else {
this.crawlerChekpoints.push(newCheckpoint);
From 44401d73b44b6de4daeb91613456d2760d50456d Mon Sep 17 00:00:00 2001
From: Michael Telatynski <7t3chguy@gmail.com>
Date: Tue, 12 Nov 2019 11:40:38 +0000
Subject: [PATCH 046/334] Replace all trivial Promise.defer usages with regular
Promises
---
src/ContentMessages.js | 174 +++++++++---------
src/Lifecycle.js | 8 +-
src/components/views/rooms/Autocomplete.js | 29 ++-
.../views/settings/ChangePassword.js | 9 +-
src/rageshake/submit-rageshake.js | 40 ++--
5 files changed, 122 insertions(+), 138 deletions(-)
diff --git a/src/ContentMessages.js b/src/ContentMessages.js
index 2d58622db8..dab8de2465 100644
--- a/src/ContentMessages.js
+++ b/src/ContentMessages.js
@@ -59,40 +59,38 @@ export class UploadCanceledError extends Error {}
* and a thumbnail key.
*/
function createThumbnail(element, inputWidth, inputHeight, mimeType) {
- const deferred = Promise.defer();
+ return new Promise((resolve) => {
+ let targetWidth = inputWidth;
+ let targetHeight = inputHeight;
+ if (targetHeight > MAX_HEIGHT) {
+ targetWidth = Math.floor(targetWidth * (MAX_HEIGHT / targetHeight));
+ targetHeight = MAX_HEIGHT;
+ }
+ if (targetWidth > MAX_WIDTH) {
+ targetHeight = Math.floor(targetHeight * (MAX_WIDTH / targetWidth));
+ targetWidth = MAX_WIDTH;
+ }
- let targetWidth = inputWidth;
- let targetHeight = inputHeight;
- if (targetHeight > MAX_HEIGHT) {
- targetWidth = Math.floor(targetWidth * (MAX_HEIGHT / targetHeight));
- targetHeight = MAX_HEIGHT;
- }
- if (targetWidth > MAX_WIDTH) {
- targetHeight = Math.floor(targetHeight * (MAX_WIDTH / targetWidth));
- targetWidth = MAX_WIDTH;
- }
-
- const canvas = document.createElement("canvas");
- canvas.width = targetWidth;
- canvas.height = targetHeight;
- canvas.getContext("2d").drawImage(element, 0, 0, targetWidth, targetHeight);
- canvas.toBlob(function(thumbnail) {
- deferred.resolve({
- info: {
- thumbnail_info: {
- w: targetWidth,
- h: targetHeight,
- mimetype: thumbnail.type,
- size: thumbnail.size,
+ const canvas = document.createElement("canvas");
+ canvas.width = targetWidth;
+ canvas.height = targetHeight;
+ canvas.getContext("2d").drawImage(element, 0, 0, targetWidth, targetHeight);
+ canvas.toBlob(function(thumbnail) {
+ resolve({
+ info: {
+ thumbnail_info: {
+ w: targetWidth,
+ h: targetHeight,
+ mimetype: thumbnail.type,
+ size: thumbnail.size,
+ },
+ w: inputWidth,
+ h: inputHeight,
},
- w: inputWidth,
- h: inputHeight,
- },
- thumbnail: thumbnail,
- });
- }, mimeType);
-
- return deferred.promise;
+ thumbnail: thumbnail,
+ });
+ }, mimeType);
+ });
}
/**
@@ -179,30 +177,29 @@ function infoForImageFile(matrixClient, roomId, imageFile) {
* @return {Promise} A promise that resolves with the video image element.
*/
function loadVideoElement(videoFile) {
- const deferred = Promise.defer();
+ return new Promise((resolve, reject) => {
+ // Load the file into an html element
+ const video = document.createElement("video");
- // Load the file into an html element
- const video = document.createElement("video");
+ const reader = new FileReader();
- const reader = new FileReader();
- reader.onload = function(e) {
- video.src = e.target.result;
+ reader.onload = function(e) {
+ video.src = e.target.result;
- // Once ready, returns its size
- // Wait until we have enough data to thumbnail the first frame.
- video.onloadeddata = function() {
- deferred.resolve(video);
+ // Once ready, returns its size
+ // Wait until we have enough data to thumbnail the first frame.
+ video.onloadeddata = function() {
+ resolve(video);
+ };
+ video.onerror = function(e) {
+ reject(e);
+ };
};
- video.onerror = function(e) {
- deferred.reject(e);
+ reader.onerror = function(e) {
+ reject(e);
};
- };
- reader.onerror = function(e) {
- deferred.reject(e);
- };
- reader.readAsDataURL(videoFile);
-
- return deferred.promise;
+ reader.readAsDataURL(videoFile);
+ });
}
/**
@@ -236,16 +233,16 @@ function infoForVideoFile(matrixClient, roomId, videoFile) {
* is read.
*/
function readFileAsArrayBuffer(file) {
- const deferred = Promise.defer();
- const reader = new FileReader();
- reader.onload = function(e) {
- deferred.resolve(e.target.result);
- };
- reader.onerror = function(e) {
- deferred.reject(e);
- };
- reader.readAsArrayBuffer(file);
- return deferred.promise;
+ return new Promise((resolve, reject) => {
+ const reader = new FileReader();
+ reader.onload = function(e) {
+ resolve(e.target.result);
+ };
+ reader.onerror = function(e) {
+ reject(e);
+ };
+ reader.readAsArrayBuffer(file);
+ });
}
/**
@@ -461,33 +458,34 @@ export default class ContentMessages {
content.info.mimetype = file.type;
}
- const def = Promise.defer();
- if (file.type.indexOf('image/') == 0) {
- content.msgtype = 'm.image';
- infoForImageFile(matrixClient, roomId, file).then((imageInfo)=>{
- extend(content.info, imageInfo);
- def.resolve();
- }, (error)=>{
- console.error(error);
+ const prom = new Promise((resolve) => {
+ if (file.type.indexOf('image/') == 0) {
+ content.msgtype = 'm.image';
+ infoForImageFile(matrixClient, roomId, file).then((imageInfo)=>{
+ extend(content.info, imageInfo);
+ resolve();
+ }, (error)=>{
+ console.error(error);
+ content.msgtype = 'm.file';
+ resolve();
+ });
+ } else if (file.type.indexOf('audio/') == 0) {
+ content.msgtype = 'm.audio';
+ resolve();
+ } else if (file.type.indexOf('video/') == 0) {
+ content.msgtype = 'm.video';
+ infoForVideoFile(matrixClient, roomId, file).then((videoInfo)=>{
+ extend(content.info, videoInfo);
+ resolve();
+ }, (error)=>{
+ content.msgtype = 'm.file';
+ resolve();
+ });
+ } else {
content.msgtype = 'm.file';
- def.resolve();
- });
- } else if (file.type.indexOf('audio/') == 0) {
- content.msgtype = 'm.audio';
- def.resolve();
- } else if (file.type.indexOf('video/') == 0) {
- content.msgtype = 'm.video';
- infoForVideoFile(matrixClient, roomId, file).then((videoInfo)=>{
- extend(content.info, videoInfo);
- def.resolve();
- }, (error)=>{
- content.msgtype = 'm.file';
- def.resolve();
- });
- } else {
- content.msgtype = 'm.file';
- def.resolve();
- }
+ resolve();
+ }
+ });
const upload = {
fileName: file.name || 'Attachment',
@@ -509,7 +507,7 @@ export default class ContentMessages {
dis.dispatch({action: 'upload_progress', upload: upload});
}
- return def.promise.then(function() {
+ return prom.then(function() {
// XXX: upload.promise must be the promise that
// is returned by uploadFile as it has an abort()
// method hacked onto it.
diff --git a/src/Lifecycle.js b/src/Lifecycle.js
index 13f3abccb1..53a9b7a998 100644
--- a/src/Lifecycle.js
+++ b/src/Lifecycle.js
@@ -312,18 +312,14 @@ async function _restoreFromLocalStorage(opts) {
function _handleLoadSessionFailure(e) {
console.error("Unable to load session", e);
- const def = Promise.defer();
const SessionRestoreErrorDialog =
sdk.getComponent('views.dialogs.SessionRestoreErrorDialog');
- Modal.createTrackedDialog('Session Restore Error', '', SessionRestoreErrorDialog, {
+ const modal = Modal.createTrackedDialog('Session Restore Error', '', SessionRestoreErrorDialog, {
error: e.message,
- onFinished: (success) => {
- def.resolve(success);
- },
});
- return def.promise.then((success) => {
+ return modal.finished.then(([success]) => {
if (success) {
// user clicked continue.
_clearStorage();
diff --git a/src/components/views/rooms/Autocomplete.js b/src/components/views/rooms/Autocomplete.js
index ad5fa198a3..d4b51081f4 100644
--- a/src/components/views/rooms/Autocomplete.js
+++ b/src/components/views/rooms/Autocomplete.js
@@ -26,6 +26,7 @@ import { Room } from 'matrix-js-sdk';
import SettingsStore from "../../../settings/SettingsStore";
import Autocompleter from '../../../autocomplete/Autocompleter';
+import {sleep} from "../../../utils/promise";
const COMPOSER_SELECTED = 0;
@@ -105,13 +106,11 @@ export default class Autocomplete extends React.Component {
autocompleteDelay = 0;
}
- const deferred = Promise.defer();
- this.debounceCompletionsRequest = setTimeout(() => {
- this.processQuery(query, selection).then(() => {
- deferred.resolve();
- });
- }, autocompleteDelay);
- return deferred.promise;
+ return new Promise((resolve) => {
+ this.debounceCompletionsRequest = setTimeout(() => {
+ resolve(this.processQuery(query, selection));
+ }, autocompleteDelay);
+ });
}
processQuery(query, selection) {
@@ -197,16 +196,16 @@ export default class Autocomplete extends React.Component {
}
forceComplete() {
- const done = Promise.defer();
- this.setState({
- forceComplete: true,
- hide: false,
- }, () => {
- this.complete(this.props.query, this.props.selection).then(() => {
- done.resolve(this.countCompletions());
+ return new Promise((resolve) => {
+ this.setState({
+ forceComplete: true,
+ hide: false,
+ }, () => {
+ this.complete(this.props.query, this.props.selection).then(() => {
+ resolve(this.countCompletions());
+ });
});
});
- return done.promise;
}
onCompletionClicked(selectionOffset: number): boolean {
diff --git a/src/components/views/settings/ChangePassword.js b/src/components/views/settings/ChangePassword.js
index a086efaa6d..91292b19f9 100644
--- a/src/components/views/settings/ChangePassword.js
+++ b/src/components/views/settings/ChangePassword.js
@@ -178,17 +178,12 @@ module.exports = createReactClass({
},
_optionallySetEmail: function() {
- const deferred = Promise.defer();
// Ask for an email otherwise the user has no way to reset their password
const SetEmailDialog = sdk.getComponent("dialogs.SetEmailDialog");
- Modal.createTrackedDialog('Do you want to set an email address?', '', SetEmailDialog, {
+ const modal = Modal.createTrackedDialog('Do you want to set an email address?', '', SetEmailDialog, {
title: _t('Do you want to set an email address?'),
- onFinished: (confirmed) => {
- // ignore confirmed, setting an email is optional
- deferred.resolve(confirmed);
- },
});
- return deferred.promise;
+ return modal.finished.then(([confirmed]) => confirmed);
},
_onExportE2eKeysClicked: function() {
diff --git a/src/rageshake/submit-rageshake.js b/src/rageshake/submit-rageshake.js
index 99c412a6ab..e772912e48 100644
--- a/src/rageshake/submit-rageshake.js
+++ b/src/rageshake/submit-rageshake.js
@@ -105,26 +105,22 @@ export default async function sendBugReport(bugReportEndpoint, opts) {
}
function _submitReport(endpoint, body, progressCallback) {
- const deferred = Promise.defer();
-
- const req = new XMLHttpRequest();
- req.open("POST", endpoint);
- req.timeout = 5 * 60 * 1000;
- req.onreadystatechange = function() {
- if (req.readyState === XMLHttpRequest.LOADING) {
- progressCallback(_t("Waiting for response from server"));
- } else if (req.readyState === XMLHttpRequest.DONE) {
- on_done();
- }
- };
- req.send(body);
- return deferred.promise;
-
- function on_done() {
- if (req.status < 200 || req.status >= 400) {
- deferred.reject(new Error(`HTTP ${req.status}`));
- return;
- }
- deferred.resolve();
- }
+ return new Promise((resolve, reject) => {
+ const req = new XMLHttpRequest();
+ req.open("POST", endpoint);
+ req.timeout = 5 * 60 * 1000;
+ req.onreadystatechange = function() {
+ if (req.readyState === XMLHttpRequest.LOADING) {
+ progressCallback(_t("Waiting for response from server"));
+ } else if (req.readyState === XMLHttpRequest.DONE) {
+ // on done
+ if (req.status < 200 || req.status >= 400) {
+ reject(new Error(`HTTP ${req.status}`));
+ return;
+ }
+ resolve();
+ }
+ };
+ req.send(body);
+ });
}
From 6850c147393ba7be8b98d97dfc8d7244fa503461 Mon Sep 17 00:00:00 2001
From: Michael Telatynski <7t3chguy@gmail.com>
Date: Tue, 12 Nov 2019 11:45:28 +0000
Subject: [PATCH 047/334] Replace rest of defer usages using small shim. Add
homebrew promise utils
---
src/Modal.js | 3 +-
src/components/structures/MatrixChat.js | 5 +--
src/utils/MultiInviter.js | 3 +-
src/utils/promise.js | 46 +++++++++++++++++++++++++
4 files changed, 53 insertions(+), 4 deletions(-)
create mode 100644 src/utils/promise.js
diff --git a/src/Modal.js b/src/Modal.js
index 26c9da8bbb..cb19731f01 100644
--- a/src/Modal.js
+++ b/src/Modal.js
@@ -24,6 +24,7 @@ import sdk from './index';
import dis from './dispatcher';
import { _t } from './languageHandler';
import Promise from "bluebird";
+import {defer} from "./utils/promise";
const DIALOG_CONTAINER_ID = "mx_Dialog_Container";
const STATIC_DIALOG_CONTAINER_ID = "mx_Dialog_StaticContainer";
@@ -202,7 +203,7 @@ class ModalManager {
}
_getCloseFn(modal, props) {
- const deferred = Promise.defer();
+ const deferred = defer();
return [(...args) => {
deferred.resolve(args);
if (props && props.onFinished) props.onFinished.apply(null, args);
diff --git a/src/components/structures/MatrixChat.js b/src/components/structures/MatrixChat.js
index da67416400..d12eba88f7 100644
--- a/src/components/structures/MatrixChat.js
+++ b/src/components/structures/MatrixChat.js
@@ -60,6 +60,7 @@ import AutoDiscoveryUtils from "../../utils/AutoDiscoveryUtils";
import DMRoomMap from '../../utils/DMRoomMap';
import { countRoomsWithNotif } from '../../RoomNotifs';
import { setTheme } from "../../theme";
+import {defer} from "../../utils/promise";
// Disable warnings for now: we use deprecated bluebird functions
// and need to migrate, but they spam the console with warnings.
@@ -236,7 +237,7 @@ export default createReactClass({
// Used by _viewRoom before getting state from sync
this.firstSyncComplete = false;
- this.firstSyncPromise = Promise.defer();
+ this.firstSyncPromise = defer();
if (this.props.config.sync_timeline_limit) {
MatrixClientPeg.opts.initialSyncLimit = this.props.config.sync_timeline_limit;
@@ -1261,7 +1262,7 @@ export default createReactClass({
// since we're about to start the client and therefore about
// to do the first sync
this.firstSyncComplete = false;
- this.firstSyncPromise = Promise.defer();
+ this.firstSyncPromise = defer();
const cli = MatrixClientPeg.get();
const IncomingSasDialog = sdk.getComponent('views.dialogs.IncomingSasDialog');
diff --git a/src/utils/MultiInviter.js b/src/utils/MultiInviter.js
index e8995b46d7..de5c2e7610 100644
--- a/src/utils/MultiInviter.js
+++ b/src/utils/MultiInviter.js
@@ -24,6 +24,7 @@ import {_t} from "../languageHandler";
import sdk from "../index";
import Modal from "../Modal";
import SettingsStore from "../settings/SettingsStore";
+import {defer} from "./promise";
/**
* Invites multiple addresses to a room or group, handling rate limiting from the server
@@ -71,7 +72,7 @@ export default class MultiInviter {
};
}
}
- this.deferred = Promise.defer();
+ this.deferred = defer();
this._inviteMore(0);
return this.deferred.promise;
diff --git a/src/utils/promise.js b/src/utils/promise.js
new file mode 100644
index 0000000000..dd10f7fdd7
--- /dev/null
+++ b/src/utils/promise.js
@@ -0,0 +1,46 @@
+/*
+Copyright 2019 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.
+*/
+
+// @flow
+
+// Returns a promise which resolves with a given value after the given number of ms
+export const sleep = (ms: number, value: any): Promise => new Promise((resolve => { setTimeout(resolve, ms, value); }));
+
+// Returns a promise which resolves when the input promise resolves with its value
+// or when the timeout of ms is reached with the value of given timeoutValue
+export async function timeout(promise: Promise, timeoutValue: any, ms: number): Promise {
+ const timeoutPromise = new Promise((resolve) => {
+ const timeoutId = setTimeout(resolve, ms, timeoutValue);
+ promise.then(() => {
+ clearTimeout(timeoutId);
+ });
+ });
+
+ return Promise.race([promise, timeoutPromise]);
+}
+
+// Returns a Deferred
+export function defer(): {resolve: () => {}, reject: () => {}, promise: Promise} {
+ let resolve;
+ let reject;
+
+ const promise = new Promise((_resolve, _reject) => {
+ resolve = _resolve;
+ reject = _reject;
+ });
+
+ return {resolve, reject, promise};
+}
From 0a21957b2cac0cc2d0169f428187bc2e468251a9 Mon Sep 17 00:00:00 2001
From: Michael Telatynski <7t3chguy@gmail.com>
Date: Tue, 12 Nov 2019 11:46:58 +0000
Subject: [PATCH 048/334] Replace Promise.delay with promise utils sleep
---
src/components/structures/GroupView.js | 9 +++++----
.../views/context_menus/RoomTileContextMenu.js | 7 ++++---
src/components/views/dialogs/AddressPickerDialog.js | 3 ++-
.../views/settings/tabs/user/SecurityUserSettingsTab.js | 3 ++-
4 files changed, 13 insertions(+), 9 deletions(-)
diff --git a/src/components/structures/GroupView.js b/src/components/structures/GroupView.js
index 4d8f47003c..4056557a7c 100644
--- a/src/components/structures/GroupView.js
+++ b/src/components/structures/GroupView.js
@@ -38,6 +38,7 @@ import FlairStore from '../../stores/FlairStore';
import { showGroupAddRoomDialog } from '../../GroupAddressPicker';
import {makeGroupPermalink, makeUserPermalink} from "../../utils/permalinks/Permalinks";
import {Group} from "matrix-js-sdk";
+import {sleep} from "../../utils/promise";
const LONG_DESC_PLACEHOLDER = _td(
`
HTML for your community's page
@@ -692,7 +693,7 @@ export default createReactClass({
// Wait 500ms to prevent flashing. Do this before sending a request otherwise we risk the
// spinner disappearing after we have fetched new group data.
- await Promise.delay(500);
+ await sleep(500);
GroupStore.acceptGroupInvite(this.props.groupId).then(() => {
// don't reset membershipBusy here: wait for the membership change to come down the sync
@@ -711,7 +712,7 @@ export default createReactClass({
// Wait 500ms to prevent flashing. Do this before sending a request otherwise we risk the
// spinner disappearing after we have fetched new group data.
- await Promise.delay(500);
+ await sleep(500);
GroupStore.leaveGroup(this.props.groupId).then(() => {
// don't reset membershipBusy here: wait for the membership change to come down the sync
@@ -735,7 +736,7 @@ export default createReactClass({
// Wait 500ms to prevent flashing. Do this before sending a request otherwise we risk the
// spinner disappearing after we have fetched new group data.
- await Promise.delay(500);
+ await sleep(500);
GroupStore.joinGroup(this.props.groupId).then(() => {
// don't reset membershipBusy here: wait for the membership change to come down the sync
@@ -787,7 +788,7 @@ export default createReactClass({
// Wait 500ms to prevent flashing. Do this before sending a request otherwise we risk the
// spinner disappearing after we have fetched new group data.
- await Promise.delay(500);
+ await sleep(500);
GroupStore.leaveGroup(this.props.groupId).then(() => {
// don't reset membershipBusy here: wait for the membership change to come down the sync
diff --git a/src/components/views/context_menus/RoomTileContextMenu.js b/src/components/views/context_menus/RoomTileContextMenu.js
index 9bb573026f..541daef27f 100644
--- a/src/components/views/context_menus/RoomTileContextMenu.js
+++ b/src/components/views/context_menus/RoomTileContextMenu.js
@@ -32,6 +32,7 @@ import * as RoomNotifs from '../../../RoomNotifs';
import Modal from '../../../Modal';
import RoomListActions from '../../../actions/RoomListActions';
import RoomViewStore from '../../../stores/RoomViewStore';
+import {sleep} from "../../../utils/promise";
module.exports = createReactClass({
displayName: 'RoomTileContextMenu',
@@ -62,7 +63,7 @@ module.exports = createReactClass({
_toggleTag: function(tagNameOn, tagNameOff) {
if (!MatrixClientPeg.get().isGuest()) {
- Promise.delay(500).then(() => {
+ sleep(500).then(() => {
dis.dispatch(RoomListActions.tagRoom(
MatrixClientPeg.get(),
this.props.room,
@@ -119,7 +120,7 @@ module.exports = createReactClass({
Rooms.guessAndSetDMRoom(
this.props.room, newIsDirectMessage,
- ).delay(500).finally(() => {
+ ).then(sleep(500)).finally(() => {
// Close the context menu
if (this.props.onFinished) {
this.props.onFinished();
@@ -193,7 +194,7 @@ module.exports = createReactClass({
RoomNotifs.setRoomNotifsState(roomId, newState).done(() => {
// delay slightly so that the user can see their state change
// before closing the menu
- return Promise.delay(500).then(() => {
+ return sleep(500).then(() => {
if (this._unmounted) return;
// Close the context menu
if (this.props.onFinished) {
diff --git a/src/components/views/dialogs/AddressPickerDialog.js b/src/components/views/dialogs/AddressPickerDialog.js
index fb779fa96f..dc61f23956 100644
--- a/src/components/views/dialogs/AddressPickerDialog.js
+++ b/src/components/views/dialogs/AddressPickerDialog.js
@@ -32,6 +32,7 @@ import * as Email from '../../../email';
import IdentityAuthClient from '../../../IdentityAuthClient';
import { getDefaultIdentityServerUrl, useDefaultIdentityServer } from '../../../utils/IdentityServerUtils';
import { abbreviateUrl } from '../../../utils/UrlUtils';
+import {sleep} from "../../../utils/promise";
const TRUNCATE_QUERY_LIST = 40;
const QUERY_USER_DIRECTORY_DEBOUNCE_MS = 200;
@@ -533,7 +534,7 @@ module.exports = createReactClass({
};
// wait a bit to let the user finish typing
- await Promise.delay(500);
+ await sleep(500);
if (cancelled) return null;
try {
diff --git a/src/components/views/settings/tabs/user/SecurityUserSettingsTab.js b/src/components/views/settings/tabs/user/SecurityUserSettingsTab.js
index e619791b01..222af48fa1 100644
--- a/src/components/views/settings/tabs/user/SecurityUserSettingsTab.js
+++ b/src/components/views/settings/tabs/user/SecurityUserSettingsTab.js
@@ -25,6 +25,7 @@ import Analytics from "../../../../../Analytics";
import Promise from "bluebird";
import Modal from "../../../../../Modal";
import sdk from "../../../../..";
+import {sleep} from "../../../../../utils/promise";
export class IgnoredUser extends React.Component {
static propTypes = {
@@ -129,7 +130,7 @@ export default class SecurityUserSettingsTab extends React.Component {
if (e.errcode === "M_LIMIT_EXCEEDED") {
// Add a delay between each invite change in order to avoid rate
// limiting by the server.
- await Promise.delay(e.retry_after_ms || 2500);
+ await sleep(e.retry_after_ms || 2500);
// Redo last action
i--;
From 09a8fec26187f12fe9b2d8d201c4b4fcb86af82a Mon Sep 17 00:00:00 2001
From: Michael Telatynski <7t3chguy@gmail.com>
Date: Tue, 12 Nov 2019 11:51:23 +0000
Subject: [PATCH 049/334] s/.done(/.then(/ since modern es6 track unhandled
promise exceptions
---
src/Lifecycle.js | 4 ++--
src/Notifier.js | 2 +-
src/Resend.js | 2 +-
src/ScalarMessaging.js | 8 ++++----
src/components/structures/GroupView.js | 4 ++--
src/components/structures/InteractiveAuth.js | 2 +-
src/components/structures/MatrixChat.js | 8 ++++----
src/components/structures/MyGroups.js | 2 +-
src/components/structures/RoomDirectory.js | 8 ++++----
src/components/structures/RoomView.js | 8 ++++----
src/components/structures/TimelinePanel.js | 4 ++--
src/components/structures/auth/ForgotPassword.js | 2 +-
src/components/structures/auth/Login.js | 4 ++--
src/components/structures/auth/PostRegistration.js | 2 +-
src/components/structures/auth/Registration.js | 2 +-
.../views/auth/InteractiveAuthEntryComponents.js | 2 +-
.../views/context_menus/RoomTileContextMenu.js | 4 ++--
.../views/dialogs/AddressPickerDialog.js | 4 ++--
src/components/views/dialogs/CreateGroupDialog.js | 2 +-
src/components/views/dialogs/KeyShareDialog.js | 2 +-
src/components/views/dialogs/SetEmailDialog.js | 4 ++--
src/components/views/elements/AppTile.js | 2 +-
.../views/elements/EditableTextContainer.js | 4 ++--
src/components/views/elements/ErrorBoundary.js | 2 +-
src/components/views/elements/ImageView.js | 2 +-
src/components/views/elements/LanguageDropdown.js | 2 +-
src/components/views/groups/GroupUserSettings.js | 2 +-
src/components/views/messages/MAudioBody.js | 2 +-
src/components/views/messages/MImageBody.js | 2 +-
src/components/views/messages/MVideoBody.js | 2 +-
src/components/views/right_panel/UserInfo.js | 2 +-
src/components/views/rooms/LinkPreviewWidget.js | 2 +-
src/components/views/rooms/MemberInfo.js | 6 +++---
src/components/views/settings/ChangeAvatar.js | 2 +-
src/components/views/settings/ChangePassword.js | 2 +-
src/components/views/settings/DevicesPanel.js | 2 +-
src/components/views/settings/Notifications.js | 14 +++++++-------
.../settings/tabs/user/HelpUserSettingsTab.js | 2 +-
src/stores/RoomViewStore.js | 4 ++--
.../views/dialogs/InteractiveAuthDialog-test.js | 3 ++-
.../views/elements/MemberEventListSummary-test.js | 2 +-
.../views/rooms/MessageComposerInput-test.js | 3 ++-
test/i18n-test/languageHandler-test.js | 2 +-
43 files changed, 74 insertions(+), 72 deletions(-)
diff --git a/src/Lifecycle.js b/src/Lifecycle.js
index 53a9b7a998..9bada98168 100644
--- a/src/Lifecycle.js
+++ b/src/Lifecycle.js
@@ -524,7 +524,7 @@ export function logout() {
console.log("Failed to call logout API: token will not be invalidated");
onLoggedOut();
},
- ).done();
+ ).then();
}
export function softLogout() {
@@ -608,7 +608,7 @@ export function onLoggedOut() {
// that can occur when components try to use a null client.
dis.dispatch({action: 'on_logged_out'}, true);
stopMatrixClient();
- _clearStorage().done();
+ _clearStorage().then();
}
/**
diff --git a/src/Notifier.js b/src/Notifier.js
index cca0ea2b89..edb9850dfe 100644
--- a/src/Notifier.js
+++ b/src/Notifier.js
@@ -198,7 +198,7 @@ const Notifier = {
if (enable) {
// Attempt to get permission from user
- plaf.requestNotificationPermission().done((result) => {
+ plaf.requestNotificationPermission().then((result) => {
if (result !== 'granted') {
// The permission request was dismissed or denied
// TODO: Support alternative branding in messaging
diff --git a/src/Resend.js b/src/Resend.js
index 4eaee16d1b..51ec804c01 100644
--- a/src/Resend.js
+++ b/src/Resend.js
@@ -35,7 +35,7 @@ module.exports = {
},
resend: function(event) {
const room = MatrixClientPeg.get().getRoom(event.getRoomId());
- MatrixClientPeg.get().resendEvent(event, room).done(function(res) {
+ MatrixClientPeg.get().resendEvent(event, room).then(function(res) {
dis.dispatch({
action: 'message_sent',
event: event,
diff --git a/src/ScalarMessaging.js b/src/ScalarMessaging.js
index 910a6c4f13..c0ffc3022d 100644
--- a/src/ScalarMessaging.js
+++ b/src/ScalarMessaging.js
@@ -279,7 +279,7 @@ function inviteUser(event, roomId, userId) {
}
}
- client.invite(roomId, userId).done(function() {
+ client.invite(roomId, userId).then(function() {
sendResponse(event, {
success: true,
});
@@ -398,7 +398,7 @@ function setPlumbingState(event, roomId, status) {
sendError(event, _t('You need to be logged in.'));
return;
}
- client.sendStateEvent(roomId, "m.room.plumbing", { status: status }).done(() => {
+ client.sendStateEvent(roomId, "m.room.plumbing", { status: status }).then(() => {
sendResponse(event, {
success: true,
});
@@ -414,7 +414,7 @@ function setBotOptions(event, roomId, userId) {
sendError(event, _t('You need to be logged in.'));
return;
}
- client.sendStateEvent(roomId, "m.room.bot.options", event.data.content, "_" + userId).done(() => {
+ client.sendStateEvent(roomId, "m.room.bot.options", event.data.content, "_" + userId).then(() => {
sendResponse(event, {
success: true,
});
@@ -444,7 +444,7 @@ function setBotPower(event, roomId, userId, level) {
},
);
- client.setPowerLevel(roomId, userId, level, powerEvent).done(() => {
+ client.setPowerLevel(roomId, userId, level, powerEvent).then(() => {
sendResponse(event, {
success: true,
});
diff --git a/src/components/structures/GroupView.js b/src/components/structures/GroupView.js
index 4056557a7c..b97d76d72a 100644
--- a/src/components/structures/GroupView.js
+++ b/src/components/structures/GroupView.js
@@ -639,7 +639,7 @@ export default createReactClass({
title: _t('Error'),
description: _t('Failed to upload image'),
});
- }).done();
+ }).then();
},
_onJoinableChange: function(ev) {
@@ -678,7 +678,7 @@ export default createReactClass({
this.setState({
avatarChanged: false,
});
- }).done();
+ }).then();
},
_saveGroup: async function() {
diff --git a/src/components/structures/InteractiveAuth.js b/src/components/structures/InteractiveAuth.js
index 5e06d124c4..86fc351515 100644
--- a/src/components/structures/InteractiveAuth.js
+++ b/src/components/structures/InteractiveAuth.js
@@ -121,7 +121,7 @@ export default createReactClass({
this.setState({
errorText: msg,
});
- }).done();
+ }).then();
this._intervalId = null;
if (this.props.poll) {
diff --git a/src/components/structures/MatrixChat.js b/src/components/structures/MatrixChat.js
index d12eba88f7..c7bf2f181f 100644
--- a/src/components/structures/MatrixChat.js
+++ b/src/components/structures/MatrixChat.js
@@ -541,7 +541,7 @@ export default createReactClass({
const Loader = sdk.getComponent("elements.Spinner");
const modal = Modal.createDialog(Loader, null, 'mx_Dialog_spinner');
- MatrixClientPeg.get().leave(payload.room_id).done(() => {
+ MatrixClientPeg.get().leave(payload.room_id).then(() => {
modal.close();
if (this.state.currentRoomId === payload.room_id) {
dis.dispatch({action: 'view_next_room'});
@@ -862,7 +862,7 @@ export default createReactClass({
waitFor = this.firstSyncPromise.promise;
}
- waitFor.done(() => {
+ waitFor.then(() => {
let presentedId = roomInfo.room_alias || roomInfo.room_id;
const room = MatrixClientPeg.get().getRoom(roomInfo.room_id);
if (room) {
@@ -974,7 +974,7 @@ export default createReactClass({
const [shouldCreate, createOpts] = await modal.finished;
if (shouldCreate) {
- createRoom({createOpts}).done();
+ createRoom({createOpts}).then();
}
},
@@ -1750,7 +1750,7 @@ export default createReactClass({
return;
}
- cli.sendEvent(roomId, event.getType(), event.getContent()).done(() => {
+ cli.sendEvent(roomId, event.getType(), event.getContent()).then(() => {
dis.dispatch({action: 'message_sent'});
}, (err) => {
dis.dispatch({action: 'message_send_failed'});
diff --git a/src/components/structures/MyGroups.js b/src/components/structures/MyGroups.js
index 2de15a5444..63ae14ba09 100644
--- a/src/components/structures/MyGroups.js
+++ b/src/components/structures/MyGroups.js
@@ -47,7 +47,7 @@ export default createReactClass({
},
_fetch: function() {
- this.context.matrixClient.getJoinedGroups().done((result) => {
+ this.context.matrixClient.getJoinedGroups().then((result) => {
this.setState({groups: result.groups, error: null});
}, (err) => {
if (err.errcode === 'M_GUEST_ACCESS_FORBIDDEN') {
diff --git a/src/components/structures/RoomDirectory.js b/src/components/structures/RoomDirectory.js
index 84f402e484..941381726d 100644
--- a/src/components/structures/RoomDirectory.js
+++ b/src/components/structures/RoomDirectory.js
@@ -89,7 +89,7 @@ module.exports = createReactClass({
this.setState({protocolsLoading: false});
return;
}
- MatrixClientPeg.get().getThirdpartyProtocols().done((response) => {
+ MatrixClientPeg.get().getThirdpartyProtocols().then((response) => {
this.protocols = response;
this.setState({protocolsLoading: false});
}, (err) => {
@@ -135,7 +135,7 @@ module.exports = createReactClass({
publicRooms: [],
loading: true,
});
- this.getMoreRooms().done();
+ this.getMoreRooms().then();
},
getMoreRooms: function() {
@@ -246,7 +246,7 @@ module.exports = createReactClass({
if (!alias) return;
step = _t('delete the alias.');
return MatrixClientPeg.get().deleteAlias(alias);
- }).done(() => {
+ }).then(() => {
modal.close();
this.refreshRoomList();
}, (err) => {
@@ -348,7 +348,7 @@ module.exports = createReactClass({
});
return;
}
- MatrixClientPeg.get().getThirdpartyLocation(protocolName, fields).done((resp) => {
+ MatrixClientPeg.get().getThirdpartyLocation(protocolName, fields).then((resp) => {
if (resp.length > 0 && resp[0].alias) {
this.showRoomAlias(resp[0].alias, true);
} else {
diff --git a/src/components/structures/RoomView.js b/src/components/structures/RoomView.js
index 4de573479d..d3ba517264 100644
--- a/src/components/structures/RoomView.js
+++ b/src/components/structures/RoomView.js
@@ -1101,7 +1101,7 @@ module.exports = createReactClass({
}
ContentMessages.sharedInstance().sendStickerContentToRoom(url, this.state.room.roomId, info, text, MatrixClientPeg.get())
- .done(undefined, (error) => {
+ .then(undefined, (error) => {
if (error.name === "UnknownDeviceError") {
// Let the staus bar handle this
return;
@@ -1145,7 +1145,7 @@ module.exports = createReactClass({
filter: filter,
term: term,
});
- this._handleSearchResult(searchPromise).done();
+ this._handleSearchResult(searchPromise).then();
},
_handleSearchResult: function(searchPromise) {
@@ -1316,7 +1316,7 @@ module.exports = createReactClass({
},
onForgetClick: function() {
- MatrixClientPeg.get().forget(this.state.room.roomId).done(function() {
+ MatrixClientPeg.get().forget(this.state.room.roomId).then(function() {
dis.dispatch({ action: 'view_next_room' });
}, function(err) {
const errCode = err.errcode || _t("unknown error code");
@@ -1333,7 +1333,7 @@ module.exports = createReactClass({
this.setState({
rejecting: true,
});
- MatrixClientPeg.get().leave(this.state.roomId).done(function() {
+ MatrixClientPeg.get().leave(this.state.roomId).then(function() {
dis.dispatch({ action: 'view_next_room' });
self.setState({
rejecting: false,
diff --git a/src/components/structures/TimelinePanel.js b/src/components/structures/TimelinePanel.js
index faa6f2564a..573d82bb9d 100644
--- a/src/components/structures/TimelinePanel.js
+++ b/src/components/structures/TimelinePanel.js
@@ -462,7 +462,7 @@ const TimelinePanel = createReactClass({
// timeline window.
//
// see https://github.com/vector-im/vector-web/issues/1035
- this._timelineWindow.paginate(EventTimeline.FORWARDS, 1, false).done(() => {
+ this._timelineWindow.paginate(EventTimeline.FORWARDS, 1, false).then(() => {
if (this.unmounted) { return; }
const { events, liveEvents } = this._getEvents();
@@ -1088,7 +1088,7 @@ const TimelinePanel = createReactClass({
prom = prom.then(onLoaded, onError);
}
- prom.done();
+ prom.then();
},
// handle the completion of a timeline load or localEchoUpdate, by
diff --git a/src/components/structures/auth/ForgotPassword.js b/src/components/structures/auth/ForgotPassword.js
index 46a5fa7bd7..6f68293caa 100644
--- a/src/components/structures/auth/ForgotPassword.js
+++ b/src/components/structures/auth/ForgotPassword.js
@@ -105,7 +105,7 @@ module.exports = createReactClass({
phase: PHASE_SENDING_EMAIL,
});
this.reset = new PasswordReset(this.props.serverConfig.hsUrl, this.props.serverConfig.isUrl);
- this.reset.resetPassword(email, password).done(() => {
+ this.reset.resetPassword(email, password).then(() => {
this.setState({
phase: PHASE_EMAIL_SENT,
});
diff --git a/src/components/structures/auth/Login.js b/src/components/structures/auth/Login.js
index ad77ed49a5..84209e514f 100644
--- a/src/components/structures/auth/Login.js
+++ b/src/components/structures/auth/Login.js
@@ -253,7 +253,7 @@ module.exports = createReactClass({
this.setState({
busy: false,
});
- }).done();
+ }).then();
},
onUsernameChanged: function(username) {
@@ -424,7 +424,7 @@ module.exports = createReactClass({
this.setState({
busy: false,
});
- }).done();
+ }).then();
},
_isSupportedFlow: function(flow) {
diff --git a/src/components/structures/auth/PostRegistration.js b/src/components/structures/auth/PostRegistration.js
index 66075c80f7..760163585d 100644
--- a/src/components/structures/auth/PostRegistration.js
+++ b/src/components/structures/auth/PostRegistration.js
@@ -43,7 +43,7 @@ module.exports = createReactClass({
const cli = MatrixClientPeg.get();
this.setState({busy: true});
const self = this;
- cli.getProfileInfo(cli.credentials.userId).done(function(result) {
+ cli.getProfileInfo(cli.credentials.userId).then(function(result) {
self.setState({
avatarUrl: MatrixClientPeg.get().mxcUrlToHttp(result.avatar_url),
busy: false,
diff --git a/src/components/structures/auth/Registration.js b/src/components/structures/auth/Registration.js
index 6321028457..6e0fc246d2 100644
--- a/src/components/structures/auth/Registration.js
+++ b/src/components/structures/auth/Registration.js
@@ -371,7 +371,7 @@ module.exports = createReactClass({
if (pushers[i].kind === 'email') {
const emailPusher = pushers[i];
emailPusher.data = { brand: this.props.brand };
- matrixClient.setPusher(emailPusher).done(() => {
+ matrixClient.setPusher(emailPusher).then(() => {
console.log("Set email branding to " + this.props.brand);
}, (error) => {
console.error("Couldn't set email branding: " + error);
diff --git a/src/components/views/auth/InteractiveAuthEntryComponents.js b/src/components/views/auth/InteractiveAuthEntryComponents.js
index d19ce95b33..d61035210b 100644
--- a/src/components/views/auth/InteractiveAuthEntryComponents.js
+++ b/src/components/views/auth/InteractiveAuthEntryComponents.js
@@ -441,7 +441,7 @@ export const MsisdnAuthEntry = createReactClass({
this.props.fail(e);
}).finally(() => {
this.setState({requestingToken: false});
- }).done();
+ }).then();
},
/*
diff --git a/src/components/views/context_menus/RoomTileContextMenu.js b/src/components/views/context_menus/RoomTileContextMenu.js
index 541daef27f..98628979e5 100644
--- a/src/components/views/context_menus/RoomTileContextMenu.js
+++ b/src/components/views/context_menus/RoomTileContextMenu.js
@@ -161,7 +161,7 @@ module.exports = createReactClass({
_onClickForget: function() {
// FIXME: duplicated with RoomSettings (and dead code in RoomView)
- MatrixClientPeg.get().forget(this.props.room.roomId).done(() => {
+ MatrixClientPeg.get().forget(this.props.room.roomId).then(() => {
// Switch to another room view if we're currently viewing the
// historical room
if (RoomViewStore.getRoomId() === this.props.room.roomId) {
@@ -191,7 +191,7 @@ module.exports = createReactClass({
this.setState({
roomNotifState: newState,
});
- RoomNotifs.setRoomNotifsState(roomId, newState).done(() => {
+ RoomNotifs.setRoomNotifsState(roomId, newState).then(() => {
// delay slightly so that the user can see their state change
// before closing the menu
return sleep(500).then(() => {
diff --git a/src/components/views/dialogs/AddressPickerDialog.js b/src/components/views/dialogs/AddressPickerDialog.js
index dc61f23956..caf6bc18c5 100644
--- a/src/components/views/dialogs/AddressPickerDialog.js
+++ b/src/components/views/dialogs/AddressPickerDialog.js
@@ -267,7 +267,7 @@ module.exports = createReactClass({
this.setState({
searchError: err.errcode ? err.message : _t('Something went wrong!'),
});
- }).done(() => {
+ }).then(() => {
this.setState({
busy: false,
});
@@ -380,7 +380,7 @@ module.exports = createReactClass({
// Do a local search immediately
this._doLocalSearch(query);
}
- }).done(() => {
+ }).then(() => {
this.setState({
busy: false,
});
diff --git a/src/components/views/dialogs/CreateGroupDialog.js b/src/components/views/dialogs/CreateGroupDialog.js
index 11f4c21366..191d797a1e 100644
--- a/src/components/views/dialogs/CreateGroupDialog.js
+++ b/src/components/views/dialogs/CreateGroupDialog.js
@@ -93,7 +93,7 @@ export default createReactClass({
this.setState({createError: e});
}).finally(() => {
this.setState({creating: false});
- }).done();
+ }).then();
},
_onCancel: function() {
diff --git a/src/components/views/dialogs/KeyShareDialog.js b/src/components/views/dialogs/KeyShareDialog.js
index a10c25a0fb..51b02f1adf 100644
--- a/src/components/views/dialogs/KeyShareDialog.js
+++ b/src/components/views/dialogs/KeyShareDialog.js
@@ -78,7 +78,7 @@ export default createReactClass({
true,
);
}
- }).done();
+ }).then();
},
componentWillUnmount: function() {
diff --git a/src/components/views/dialogs/SetEmailDialog.js b/src/components/views/dialogs/SetEmailDialog.js
index bedf713c4e..b527abffc9 100644
--- a/src/components/views/dialogs/SetEmailDialog.js
+++ b/src/components/views/dialogs/SetEmailDialog.js
@@ -62,7 +62,7 @@ export default createReactClass({
return;
}
this._addThreepid = new AddThreepid();
- this._addThreepid.addEmailAddress(emailAddress).done(() => {
+ this._addThreepid.addEmailAddress(emailAddress).then(() => {
Modal.createTrackedDialog('Verification Pending', '', QuestionDialog, {
title: _t("Verification Pending"),
description: _t(
@@ -96,7 +96,7 @@ export default createReactClass({
},
verifyEmailAddress: function() {
- this._addThreepid.checkEmailLinkClicked().done(() => {
+ this._addThreepid.checkEmailLinkClicked().then(() => {
this.props.onFinished(true);
}, (err) => {
this.setState({emailBusy: false});
diff --git a/src/components/views/elements/AppTile.js b/src/components/views/elements/AppTile.js
index 260b63dfd4..453630413c 100644
--- a/src/components/views/elements/AppTile.js
+++ b/src/components/views/elements/AppTile.js
@@ -205,7 +205,7 @@ export default class AppTile extends React.Component {
if (!this._scalarClient) {
this._scalarClient = defaultManager.getScalarClient();
}
- this._scalarClient.getScalarToken().done((token) => {
+ this._scalarClient.getScalarToken().then((token) => {
// Append scalar_token as a query param if not already present
this._scalarClient.scalarToken = token;
const u = url.parse(this._addWurlParams(this.props.url));
diff --git a/src/components/views/elements/EditableTextContainer.js b/src/components/views/elements/EditableTextContainer.js
index 3bf37df951..cc49c3c67f 100644
--- a/src/components/views/elements/EditableTextContainer.js
+++ b/src/components/views/elements/EditableTextContainer.js
@@ -51,7 +51,7 @@ export default class EditableTextContainer extends React.Component {
this.setState({busy: true});
- this.props.getInitialValue().done(
+ this.props.getInitialValue().then(
(result) => {
if (this._unmounted) { return; }
this.setState({
@@ -83,7 +83,7 @@ export default class EditableTextContainer extends React.Component {
errorString: null,
});
- this.props.onSubmit(value).done(
+ this.props.onSubmit(value).then(
() => {
if (this._unmounted) { return; }
this.setState({
diff --git a/src/components/views/elements/ErrorBoundary.js b/src/components/views/elements/ErrorBoundary.js
index e53e1ec0fa..e36464c4ef 100644
--- a/src/components/views/elements/ErrorBoundary.js
+++ b/src/components/views/elements/ErrorBoundary.js
@@ -54,7 +54,7 @@ export default class ErrorBoundary extends React.PureComponent {
if (!PlatformPeg.get()) return;
MatrixClientPeg.get().stopClient();
- MatrixClientPeg.get().store.deleteAllData().done(() => {
+ MatrixClientPeg.get().store.deleteAllData().then(() => {
PlatformPeg.get().reload();
});
};
diff --git a/src/components/views/elements/ImageView.js b/src/components/views/elements/ImageView.js
index 2772363bd0..09e0ff0e54 100644
--- a/src/components/views/elements/ImageView.js
+++ b/src/components/views/elements/ImageView.js
@@ -84,7 +84,7 @@ export default class ImageView extends React.Component {
title: _t('Error'),
description: _t('You cannot delete this image. (%(code)s)', {code: code}),
});
- }).done();
+ }).then();
},
});
};
diff --git a/src/components/views/elements/LanguageDropdown.js b/src/components/views/elements/LanguageDropdown.js
index 365f9ded61..0c4b2b9d6a 100644
--- a/src/components/views/elements/LanguageDropdown.js
+++ b/src/components/views/elements/LanguageDropdown.js
@@ -49,7 +49,7 @@ export default class LanguageDropdown extends React.Component {
this.setState({langs});
}).catch(() => {
this.setState({langs: ['en']});
- }).done();
+ }).then();
if (!this.props.value) {
// If no value is given, we start with the first
diff --git a/src/components/views/groups/GroupUserSettings.js b/src/components/views/groups/GroupUserSettings.js
index 7d80bdd209..3cd5731b99 100644
--- a/src/components/views/groups/GroupUserSettings.js
+++ b/src/components/views/groups/GroupUserSettings.js
@@ -36,7 +36,7 @@ export default createReactClass({
},
componentWillMount: function() {
- this.context.matrixClient.getJoinedGroups().done((result) => {
+ this.context.matrixClient.getJoinedGroups().then((result) => {
this.setState({groups: result.groups || [], error: null});
}, (err) => {
console.error(err);
diff --git a/src/components/views/messages/MAudioBody.js b/src/components/views/messages/MAudioBody.js
index b4f26d0cbd..0246d28542 100644
--- a/src/components/views/messages/MAudioBody.js
+++ b/src/components/views/messages/MAudioBody.js
@@ -55,7 +55,7 @@ export default class MAudioBody extends React.Component {
decryptFile(content.file).then(function(blob) {
decryptedBlob = blob;
return URL.createObjectURL(decryptedBlob);
- }).done((url) => {
+ }).then((url) => {
this.setState({
decryptedUrl: url,
decryptedBlob: decryptedBlob,
diff --git a/src/components/views/messages/MImageBody.js b/src/components/views/messages/MImageBody.js
index 640baa1966..abfd8b64cd 100644
--- a/src/components/views/messages/MImageBody.js
+++ b/src/components/views/messages/MImageBody.js
@@ -289,7 +289,7 @@ export default class MImageBody extends React.Component {
this.setState({
error: err,
});
- }).done();
+ }).then();
}
// Remember that the user wanted to show this particular image
diff --git a/src/components/views/messages/MVideoBody.js b/src/components/views/messages/MVideoBody.js
index d277b6eae9..bc4dbf3374 100644
--- a/src/components/views/messages/MVideoBody.js
+++ b/src/components/views/messages/MVideoBody.js
@@ -115,7 +115,7 @@ module.exports = createReactClass({
this.setState({
error: err,
});
- }).done();
+ }).then();
}
},
diff --git a/src/components/views/right_panel/UserInfo.js b/src/components/views/right_panel/UserInfo.js
index 207bf29998..192efcdd8a 100644
--- a/src/components/views/right_panel/UserInfo.js
+++ b/src/components/views/right_panel/UserInfo.js
@@ -870,7 +870,7 @@ const UserInfo = withLegacyMatrixClient(({matrixClient: cli, user, groupId, room
},
).finally(() => {
stopUpdating();
- }).done();
+ }).then();
};
const roomId = user.roomId;
diff --git a/src/components/views/rooms/LinkPreviewWidget.js b/src/components/views/rooms/LinkPreviewWidget.js
index d93fe76b46..b06a9b9a30 100644
--- a/src/components/views/rooms/LinkPreviewWidget.js
+++ b/src/components/views/rooms/LinkPreviewWidget.js
@@ -53,7 +53,7 @@ module.exports = createReactClass({
);
}, (error)=>{
console.error("Failed to get URL preview: " + error);
- }).done();
+ }).then();
},
componentDidMount: function() {
diff --git a/src/components/views/rooms/MemberInfo.js b/src/components/views/rooms/MemberInfo.js
index 2ea6392e96..68e494d5eb 100644
--- a/src/components/views/rooms/MemberInfo.js
+++ b/src/components/views/rooms/MemberInfo.js
@@ -248,7 +248,7 @@ module.exports = createReactClass({
return client.getStoredDevicesForUser(member.userId);
}).finally(function() {
self._cancelDeviceList = null;
- }).done(function(devices) {
+ }).then(function(devices) {
if (cancelled) {
// we got cancelled - presumably a different user now
return;
@@ -572,7 +572,7 @@ module.exports = createReactClass({
},
).finally(()=>{
this.setState({ updating: this.state.updating - 1 });
- }).done();
+ }).then();
},
onPowerChange: async function(powerLevel) {
@@ -629,7 +629,7 @@ module.exports = createReactClass({
this.setState({ updating: this.state.updating + 1 });
createRoom({dmUserId: this.props.member.userId}).finally(() => {
this.setState({ updating: this.state.updating - 1 });
- }).done();
+ }).then();
},
onLeaveClick: function() {
diff --git a/src/components/views/settings/ChangeAvatar.js b/src/components/views/settings/ChangeAvatar.js
index 32521006c7..904b17b15f 100644
--- a/src/components/views/settings/ChangeAvatar.js
+++ b/src/components/views/settings/ChangeAvatar.js
@@ -112,7 +112,7 @@ module.exports = createReactClass({
}
});
- httpPromise.done(function() {
+ httpPromise.then(function() {
self.setState({
phase: self.Phases.Display,
avatarUrl: MatrixClientPeg.get().mxcUrlToHttp(newUrl),
diff --git a/src/components/views/settings/ChangePassword.js b/src/components/views/settings/ChangePassword.js
index 91292b19f9..15aa6203d7 100644
--- a/src/components/views/settings/ChangePassword.js
+++ b/src/components/views/settings/ChangePassword.js
@@ -174,7 +174,7 @@ module.exports = createReactClass({
newPassword: "",
newPasswordConfirm: "",
});
- }).done();
+ }).then();
},
_optionallySetEmail: function() {
diff --git a/src/components/views/settings/DevicesPanel.js b/src/components/views/settings/DevicesPanel.js
index 30f507ea18..cb5db10be4 100644
--- a/src/components/views/settings/DevicesPanel.js
+++ b/src/components/views/settings/DevicesPanel.js
@@ -52,7 +52,7 @@ export default class DevicesPanel extends React.Component {
}
_loadDevices() {
- MatrixClientPeg.get().getDevices().done(
+ MatrixClientPeg.get().getDevices().then(
(resp) => {
if (this._unmounted) { return; }
this.setState({devices: resp.devices || []});
diff --git a/src/components/views/settings/Notifications.js b/src/components/views/settings/Notifications.js
index e3b4cfe122..e67c61dff5 100644
--- a/src/components/views/settings/Notifications.js
+++ b/src/components/views/settings/Notifications.js
@@ -97,7 +97,7 @@ module.exports = createReactClass({
phase: this.phases.LOADING,
});
- MatrixClientPeg.get().setPushRuleEnabled('global', self.state.masterPushRule.kind, self.state.masterPushRule.rule_id, !checked).done(function() {
+ MatrixClientPeg.get().setPushRuleEnabled('global', self.state.masterPushRule.kind, self.state.masterPushRule.rule_id, !checked).then(function() {
self._refreshFromServer();
});
},
@@ -170,7 +170,7 @@ module.exports = createReactClass({
emailPusher.kind = null;
emailPusherPromise = MatrixClientPeg.get().setPusher(emailPusher);
}
- emailPusherPromise.done(() => {
+ emailPusherPromise.then(() => {
this._refreshFromServer();
}, (error) => {
const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
@@ -274,7 +274,7 @@ module.exports = createReactClass({
}
}
- Promise.all(deferreds).done(function() {
+ Promise.all(deferreds).then(function() {
self._refreshFromServer();
}, function(error) {
const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
@@ -343,7 +343,7 @@ module.exports = createReactClass({
}
}
- Promise.all(deferreds).done(function(resps) {
+ Promise.all(deferreds).then(function(resps) {
self._refreshFromServer();
}, function(error) {
const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
@@ -398,7 +398,7 @@ module.exports = createReactClass({
};
// Then, add the new ones
- Promise.all(removeDeferreds).done(function(resps) {
+ Promise.all(removeDeferreds).then(function(resps) {
const deferreds = [];
let pushRuleVectorStateKind = self.state.vectorContentRules.vectorState;
@@ -434,7 +434,7 @@ module.exports = createReactClass({
}
}
- Promise.all(deferreds).done(function(resps) {
+ Promise.all(deferreds).then(function(resps) {
self._refreshFromServer();
}, onError);
}, onError);
@@ -650,7 +650,7 @@ module.exports = createReactClass({
externalContentRules: self.state.externalContentRules,
externalPushRules: self.state.externalPushRules,
});
- }).done();
+ }).then();
MatrixClientPeg.get().getThreePids().then((r) => this.setState({threepids: r.threepids}));
},
diff --git a/src/components/views/settings/tabs/user/HelpUserSettingsTab.js b/src/components/views/settings/tabs/user/HelpUserSettingsTab.js
index fbad327078..875f0bfc10 100644
--- a/src/components/views/settings/tabs/user/HelpUserSettingsTab.js
+++ b/src/components/views/settings/tabs/user/HelpUserSettingsTab.js
@@ -75,7 +75,7 @@ export default class HelpUserSettingsTab extends React.Component {
// stopping in the middle of the logs.
console.log("Clear cache & reload clicked");
MatrixClientPeg.get().stopClient();
- MatrixClientPeg.get().store.deleteAllData().done(() => {
+ MatrixClientPeg.get().store.deleteAllData().then(() => {
PlatformPeg.get().reload();
});
};
diff --git a/src/stores/RoomViewStore.js b/src/stores/RoomViewStore.js
index 7e1b06c0bf..177b88c3f2 100644
--- a/src/stores/RoomViewStore.js
+++ b/src/stores/RoomViewStore.js
@@ -186,7 +186,7 @@ class RoomViewStore extends Store {
roomLoading: true,
roomLoadError: null,
});
- MatrixClientPeg.get().getRoomIdForAlias(payload.room_alias).done(
+ MatrixClientPeg.get().getRoomIdForAlias(payload.room_alias).then(
(result) => {
dis.dispatch({
action: 'view_room',
@@ -223,7 +223,7 @@ class RoomViewStore extends Store {
});
MatrixClientPeg.get().joinRoom(
this._state.roomAlias || this._state.roomId, payload.opts,
- ).done(() => {
+ ).then(() => {
// We don't actually need to do anything here: we do *not*
// clear the 'joining' flag because the Room object and/or
// our 'joined' member event may not have come down the sync
diff --git a/test/components/views/dialogs/InteractiveAuthDialog-test.js b/test/components/views/dialogs/InteractiveAuthDialog-test.js
index b14ea7c242..7612b43b48 100644
--- a/test/components/views/dialogs/InteractiveAuthDialog-test.js
+++ b/test/components/views/dialogs/InteractiveAuthDialog-test.js
@@ -26,6 +26,7 @@ import sdk from 'matrix-react-sdk';
import MatrixClientPeg from '../../../../src/MatrixClientPeg';
import * as test_utils from '../../../test-utils';
+import {sleep} from "../../../../src/utils/promise";
const InteractiveAuthDialog = sdk.getComponent(
'views.dialogs.InteractiveAuthDialog',
@@ -107,7 +108,7 @@ describe('InteractiveAuthDialog', function() {
},
})).toBe(true);
// let the request complete
- return Promise.delay(1);
+ return sleep(1);
}).then(() => {
expect(onFinished.callCount).toEqual(1);
expect(onFinished.calledWithExactly(true, {a: 1})).toBe(true);
diff --git a/test/components/views/elements/MemberEventListSummary-test.js b/test/components/views/elements/MemberEventListSummary-test.js
index 95f7e7999a..a31cbdebb5 100644
--- a/test/components/views/elements/MemberEventListSummary-test.js
+++ b/test/components/views/elements/MemberEventListSummary-test.js
@@ -91,7 +91,7 @@ describe('MemberEventListSummary', function() {
testUtils.beforeEach(this);
sandbox = testUtils.stubClient();
- languageHandler.setLanguage('en').done(done);
+ languageHandler.setLanguage('en').then(done);
languageHandler.setMissingEntryGenerator(function(key) {
return key.split('|', 2)[1];
});
diff --git a/test/components/views/rooms/MessageComposerInput-test.js b/test/components/views/rooms/MessageComposerInput-test.js
index 1105a4af17..ed25c4d607 100644
--- a/test/components/views/rooms/MessageComposerInput-test.js
+++ b/test/components/views/rooms/MessageComposerInput-test.js
@@ -8,6 +8,7 @@ import * as testUtils from '../../../test-utils';
import sdk from 'matrix-react-sdk';
const MessageComposerInput = sdk.getComponent('views.rooms.MessageComposerInput');
import MatrixClientPeg from '../../../../src/MatrixClientPeg';
+import {sleep} from "../../../../src/utils/promise";
function addTextToDraft(text) {
const components = document.getElementsByClassName('public-DraftEditor-content');
@@ -49,7 +50,7 @@ xdescribe('MessageComposerInput', () => {
// warnings
// (please can we make the components not setState() after
// they are unmounted?)
- Promise.delay(10).done(() => {
+ sleep(10).then(() => {
if (parentDiv) {
ReactDOM.unmountComponentAtNode(parentDiv);
parentDiv.remove();
diff --git a/test/i18n-test/languageHandler-test.js b/test/i18n-test/languageHandler-test.js
index 0d96bc15ab..8f21638703 100644
--- a/test/i18n-test/languageHandler-test.js
+++ b/test/i18n-test/languageHandler-test.js
@@ -11,7 +11,7 @@ describe('languageHandler', function() {
testUtils.beforeEach(this);
sandbox = testUtils.stubClient();
- languageHandler.setLanguage('en').done(done);
+ languageHandler.setLanguage('en').then(done);
});
afterEach(function() {
From d72dedb0cee9868792256bc8e34ee1e76e38c8dc Mon Sep 17 00:00:00 2001
From: "J. Ryan Stinnett"
Date: Tue, 12 Nov 2019 11:43:18 +0000
Subject: [PATCH 050/334] Cache room alias to room ID mapping in memory
This adds very basic cache (literally just a `Map` for now) to store room alias
to room ID mappings. The improves the perceived performance of Riot when
switching rooms via browser navigation (back / forward), as we no longer try to
resolve the room alias every time.
The cache is only in memory, so reloading manually or as part of the clear cache
process will start afresh.
Fixes https://github.com/vector-im/riot-web/issues/10020
---
src/RoomAliasCache.js | 35 +++++++++++++++++++++++++
src/components/structures/MatrixChat.js | 8 +++++-
src/stores/RoomViewStore.js | 30 ++++++++++++++++-----
3 files changed, 66 insertions(+), 7 deletions(-)
create mode 100644 src/RoomAliasCache.js
diff --git a/src/RoomAliasCache.js b/src/RoomAliasCache.js
new file mode 100644
index 0000000000..bb511ba4d7
--- /dev/null
+++ b/src/RoomAliasCache.js
@@ -0,0 +1,35 @@
+/*
+Copyright 2019 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.
+*/
+
+/**
+ * This is meant to be a cache of room alias to room ID so that moving between
+ * rooms happens smoothly (for example using browser back / forward buttons).
+ *
+ * For the moment, it's in memory only and so only applies for the current
+ * session for simplicity, but could be extended further in the future.
+ *
+ * A similar thing could also be achieved via `pushState` with a state object,
+ * but keeping it separate like this seems easier in case we do want to extend.
+ */
+const aliasToIDMap = new Map();
+
+export function storeRoomAliasInCache(alias, id) {
+ aliasToIDMap.set(alias, id);
+}
+
+export function getCachedRoomIDForAlias(alias) {
+ return aliasToIDMap.get(alias);
+}
diff --git a/src/components/structures/MatrixChat.js b/src/components/structures/MatrixChat.js
index da67416400..6cc86bf6d7 100644
--- a/src/components/structures/MatrixChat.js
+++ b/src/components/structures/MatrixChat.js
@@ -60,6 +60,7 @@ import AutoDiscoveryUtils from "../../utils/AutoDiscoveryUtils";
import DMRoomMap from '../../utils/DMRoomMap';
import { countRoomsWithNotif } from '../../RoomNotifs';
import { setTheme } from "../../theme";
+import { storeRoomAliasInCache } from '../../RoomAliasCache';
// Disable warnings for now: we use deprecated bluebird functions
// and need to migrate, but they spam the console with warnings.
@@ -866,7 +867,12 @@ export default createReactClass({
const room = MatrixClientPeg.get().getRoom(roomInfo.room_id);
if (room) {
const theAlias = Rooms.getDisplayAliasForRoom(room);
- if (theAlias) presentedId = theAlias;
+ if (theAlias) {
+ presentedId = theAlias;
+ // Store display alias of the presented room in cache to speed future
+ // navigation.
+ storeRoomAliasInCache(theAlias, room.roomId);
+ }
// Store this as the ID of the last room accessed. This is so that we can
// persist which room is being stored across refreshes and browser quits.
diff --git a/src/stores/RoomViewStore.js b/src/stores/RoomViewStore.js
index 7e1b06c0bf..e860ed8b24 100644
--- a/src/stores/RoomViewStore.js
+++ b/src/stores/RoomViewStore.js
@@ -20,6 +20,7 @@ import MatrixClientPeg from '../MatrixClientPeg';
import sdk from '../index';
import Modal from '../Modal';
import { _t } from '../languageHandler';
+import { getCachedRoomIDForAlias, storeRoomAliasInCache } from '../RoomAliasCache';
const INITIAL_STATE = {
// Whether we're joining the currently viewed room (see isJoining())
@@ -137,7 +138,7 @@ class RoomViewStore extends Store {
}
}
- _viewRoom(payload) {
+ async _viewRoom(payload) {
if (payload.room_id) {
const newState = {
roomId: payload.room_id,
@@ -176,6 +177,22 @@ class RoomViewStore extends Store {
this._joinRoom(payload);
}
} else if (payload.room_alias) {
+ // Try the room alias to room ID navigation cache first to avoid
+ // blocking room navigation on the homeserver.
+ const roomId = getCachedRoomIDForAlias(payload.room_alias);
+ if (roomId) {
+ dis.dispatch({
+ action: 'view_room',
+ room_id: roomId,
+ event_id: payload.event_id,
+ highlighted: payload.highlighted,
+ room_alias: payload.room_alias,
+ auto_join: payload.auto_join,
+ oob_data: payload.oob_data,
+ });
+ return;
+ }
+ // Room alias cache miss, so let's ask the homeserver.
// Resolve the alias and then do a second dispatch with the room ID acquired
this._setState({
roomId: null,
@@ -186,8 +203,9 @@ class RoomViewStore extends Store {
roomLoading: true,
roomLoadError: null,
});
- MatrixClientPeg.get().getRoomIdForAlias(payload.room_alias).done(
- (result) => {
+ try {
+ const result = await MatrixClientPeg.get().getRoomIdForAlias(payload.room_alias);
+ storeRoomAliasInCache(payload.room_alias, result.room_id);
dis.dispatch({
action: 'view_room',
room_id: result.room_id,
@@ -197,14 +215,14 @@ class RoomViewStore extends Store {
auto_join: payload.auto_join,
oob_data: payload.oob_data,
});
- }, (err) => {
+ } catch (err) {
dis.dispatch({
action: 'view_room_error',
room_id: null,
room_alias: payload.room_alias,
- err: err,
+ err,
});
- });
+ }
}
}
From 168b1b68bb5b9700da9ad22b692b3db866f12128 Mon Sep 17 00:00:00 2001
From: Michael Telatynski <7t3chguy@gmail.com>
Date: Tue, 12 Nov 2019 11:56:21 +0000
Subject: [PATCH 051/334] Revert "s/.done(/.then(/ since modern es6 track
unhandled promise exceptions"
This reverts commit 09a8fec2
---
src/Lifecycle.js | 4 ++--
src/Notifier.js | 2 +-
src/Resend.js | 2 +-
src/ScalarMessaging.js | 8 ++++----
src/components/structures/GroupView.js | 4 ++--
src/components/structures/InteractiveAuth.js | 2 +-
src/components/structures/MatrixChat.js | 8 ++++----
src/components/structures/MyGroups.js | 2 +-
src/components/structures/RoomDirectory.js | 8 ++++----
src/components/structures/RoomView.js | 8 ++++----
src/components/structures/TimelinePanel.js | 4 ++--
src/components/structures/auth/ForgotPassword.js | 2 +-
src/components/structures/auth/Login.js | 4 ++--
src/components/structures/auth/PostRegistration.js | 2 +-
src/components/structures/auth/Registration.js | 2 +-
.../views/auth/InteractiveAuthEntryComponents.js | 2 +-
.../views/context_menus/RoomTileContextMenu.js | 4 ++--
.../views/dialogs/AddressPickerDialog.js | 4 ++--
src/components/views/dialogs/CreateGroupDialog.js | 2 +-
src/components/views/dialogs/KeyShareDialog.js | 2 +-
src/components/views/dialogs/SetEmailDialog.js | 4 ++--
src/components/views/elements/AppTile.js | 2 +-
.../views/elements/EditableTextContainer.js | 4 ++--
src/components/views/elements/ErrorBoundary.js | 2 +-
src/components/views/elements/ImageView.js | 2 +-
src/components/views/elements/LanguageDropdown.js | 2 +-
src/components/views/groups/GroupUserSettings.js | 2 +-
src/components/views/messages/MAudioBody.js | 2 +-
src/components/views/messages/MImageBody.js | 2 +-
src/components/views/messages/MVideoBody.js | 2 +-
src/components/views/right_panel/UserInfo.js | 2 +-
src/components/views/rooms/LinkPreviewWidget.js | 2 +-
src/components/views/rooms/MemberInfo.js | 6 +++---
src/components/views/settings/ChangeAvatar.js | 2 +-
src/components/views/settings/ChangePassword.js | 2 +-
src/components/views/settings/DevicesPanel.js | 2 +-
src/components/views/settings/Notifications.js | 14 +++++++-------
.../settings/tabs/user/HelpUserSettingsTab.js | 2 +-
src/stores/RoomViewStore.js | 4 ++--
.../views/dialogs/InteractiveAuthDialog-test.js | 3 +--
.../views/elements/MemberEventListSummary-test.js | 2 +-
.../views/rooms/MessageComposerInput-test.js | 3 +--
test/i18n-test/languageHandler-test.js | 2 +-
43 files changed, 72 insertions(+), 74 deletions(-)
diff --git a/src/Lifecycle.js b/src/Lifecycle.js
index 9bada98168..53a9b7a998 100644
--- a/src/Lifecycle.js
+++ b/src/Lifecycle.js
@@ -524,7 +524,7 @@ export function logout() {
console.log("Failed to call logout API: token will not be invalidated");
onLoggedOut();
},
- ).then();
+ ).done();
}
export function softLogout() {
@@ -608,7 +608,7 @@ export function onLoggedOut() {
// that can occur when components try to use a null client.
dis.dispatch({action: 'on_logged_out'}, true);
stopMatrixClient();
- _clearStorage().then();
+ _clearStorage().done();
}
/**
diff --git a/src/Notifier.js b/src/Notifier.js
index edb9850dfe..cca0ea2b89 100644
--- a/src/Notifier.js
+++ b/src/Notifier.js
@@ -198,7 +198,7 @@ const Notifier = {
if (enable) {
// Attempt to get permission from user
- plaf.requestNotificationPermission().then((result) => {
+ plaf.requestNotificationPermission().done((result) => {
if (result !== 'granted') {
// The permission request was dismissed or denied
// TODO: Support alternative branding in messaging
diff --git a/src/Resend.js b/src/Resend.js
index 51ec804c01..4eaee16d1b 100644
--- a/src/Resend.js
+++ b/src/Resend.js
@@ -35,7 +35,7 @@ module.exports = {
},
resend: function(event) {
const room = MatrixClientPeg.get().getRoom(event.getRoomId());
- MatrixClientPeg.get().resendEvent(event, room).then(function(res) {
+ MatrixClientPeg.get().resendEvent(event, room).done(function(res) {
dis.dispatch({
action: 'message_sent',
event: event,
diff --git a/src/ScalarMessaging.js b/src/ScalarMessaging.js
index c0ffc3022d..910a6c4f13 100644
--- a/src/ScalarMessaging.js
+++ b/src/ScalarMessaging.js
@@ -279,7 +279,7 @@ function inviteUser(event, roomId, userId) {
}
}
- client.invite(roomId, userId).then(function() {
+ client.invite(roomId, userId).done(function() {
sendResponse(event, {
success: true,
});
@@ -398,7 +398,7 @@ function setPlumbingState(event, roomId, status) {
sendError(event, _t('You need to be logged in.'));
return;
}
- client.sendStateEvent(roomId, "m.room.plumbing", { status: status }).then(() => {
+ client.sendStateEvent(roomId, "m.room.plumbing", { status: status }).done(() => {
sendResponse(event, {
success: true,
});
@@ -414,7 +414,7 @@ function setBotOptions(event, roomId, userId) {
sendError(event, _t('You need to be logged in.'));
return;
}
- client.sendStateEvent(roomId, "m.room.bot.options", event.data.content, "_" + userId).then(() => {
+ client.sendStateEvent(roomId, "m.room.bot.options", event.data.content, "_" + userId).done(() => {
sendResponse(event, {
success: true,
});
@@ -444,7 +444,7 @@ function setBotPower(event, roomId, userId, level) {
},
);
- client.setPowerLevel(roomId, userId, level, powerEvent).then(() => {
+ client.setPowerLevel(roomId, userId, level, powerEvent).done(() => {
sendResponse(event, {
success: true,
});
diff --git a/src/components/structures/GroupView.js b/src/components/structures/GroupView.js
index b97d76d72a..4056557a7c 100644
--- a/src/components/structures/GroupView.js
+++ b/src/components/structures/GroupView.js
@@ -639,7 +639,7 @@ export default createReactClass({
title: _t('Error'),
description: _t('Failed to upload image'),
});
- }).then();
+ }).done();
},
_onJoinableChange: function(ev) {
@@ -678,7 +678,7 @@ export default createReactClass({
this.setState({
avatarChanged: false,
});
- }).then();
+ }).done();
},
_saveGroup: async function() {
diff --git a/src/components/structures/InteractiveAuth.js b/src/components/structures/InteractiveAuth.js
index 86fc351515..5e06d124c4 100644
--- a/src/components/structures/InteractiveAuth.js
+++ b/src/components/structures/InteractiveAuth.js
@@ -121,7 +121,7 @@ export default createReactClass({
this.setState({
errorText: msg,
});
- }).then();
+ }).done();
this._intervalId = null;
if (this.props.poll) {
diff --git a/src/components/structures/MatrixChat.js b/src/components/structures/MatrixChat.js
index c7bf2f181f..d12eba88f7 100644
--- a/src/components/structures/MatrixChat.js
+++ b/src/components/structures/MatrixChat.js
@@ -541,7 +541,7 @@ export default createReactClass({
const Loader = sdk.getComponent("elements.Spinner");
const modal = Modal.createDialog(Loader, null, 'mx_Dialog_spinner');
- MatrixClientPeg.get().leave(payload.room_id).then(() => {
+ MatrixClientPeg.get().leave(payload.room_id).done(() => {
modal.close();
if (this.state.currentRoomId === payload.room_id) {
dis.dispatch({action: 'view_next_room'});
@@ -862,7 +862,7 @@ export default createReactClass({
waitFor = this.firstSyncPromise.promise;
}
- waitFor.then(() => {
+ waitFor.done(() => {
let presentedId = roomInfo.room_alias || roomInfo.room_id;
const room = MatrixClientPeg.get().getRoom(roomInfo.room_id);
if (room) {
@@ -974,7 +974,7 @@ export default createReactClass({
const [shouldCreate, createOpts] = await modal.finished;
if (shouldCreate) {
- createRoom({createOpts}).then();
+ createRoom({createOpts}).done();
}
},
@@ -1750,7 +1750,7 @@ export default createReactClass({
return;
}
- cli.sendEvent(roomId, event.getType(), event.getContent()).then(() => {
+ cli.sendEvent(roomId, event.getType(), event.getContent()).done(() => {
dis.dispatch({action: 'message_sent'});
}, (err) => {
dis.dispatch({action: 'message_send_failed'});
diff --git a/src/components/structures/MyGroups.js b/src/components/structures/MyGroups.js
index 63ae14ba09..2de15a5444 100644
--- a/src/components/structures/MyGroups.js
+++ b/src/components/structures/MyGroups.js
@@ -47,7 +47,7 @@ export default createReactClass({
},
_fetch: function() {
- this.context.matrixClient.getJoinedGroups().then((result) => {
+ this.context.matrixClient.getJoinedGroups().done((result) => {
this.setState({groups: result.groups, error: null});
}, (err) => {
if (err.errcode === 'M_GUEST_ACCESS_FORBIDDEN') {
diff --git a/src/components/structures/RoomDirectory.js b/src/components/structures/RoomDirectory.js
index 941381726d..84f402e484 100644
--- a/src/components/structures/RoomDirectory.js
+++ b/src/components/structures/RoomDirectory.js
@@ -89,7 +89,7 @@ module.exports = createReactClass({
this.setState({protocolsLoading: false});
return;
}
- MatrixClientPeg.get().getThirdpartyProtocols().then((response) => {
+ MatrixClientPeg.get().getThirdpartyProtocols().done((response) => {
this.protocols = response;
this.setState({protocolsLoading: false});
}, (err) => {
@@ -135,7 +135,7 @@ module.exports = createReactClass({
publicRooms: [],
loading: true,
});
- this.getMoreRooms().then();
+ this.getMoreRooms().done();
},
getMoreRooms: function() {
@@ -246,7 +246,7 @@ module.exports = createReactClass({
if (!alias) return;
step = _t('delete the alias.');
return MatrixClientPeg.get().deleteAlias(alias);
- }).then(() => {
+ }).done(() => {
modal.close();
this.refreshRoomList();
}, (err) => {
@@ -348,7 +348,7 @@ module.exports = createReactClass({
});
return;
}
- MatrixClientPeg.get().getThirdpartyLocation(protocolName, fields).then((resp) => {
+ MatrixClientPeg.get().getThirdpartyLocation(protocolName, fields).done((resp) => {
if (resp.length > 0 && resp[0].alias) {
this.showRoomAlias(resp[0].alias, true);
} else {
diff --git a/src/components/structures/RoomView.js b/src/components/structures/RoomView.js
index d3ba517264..4de573479d 100644
--- a/src/components/structures/RoomView.js
+++ b/src/components/structures/RoomView.js
@@ -1101,7 +1101,7 @@ module.exports = createReactClass({
}
ContentMessages.sharedInstance().sendStickerContentToRoom(url, this.state.room.roomId, info, text, MatrixClientPeg.get())
- .then(undefined, (error) => {
+ .done(undefined, (error) => {
if (error.name === "UnknownDeviceError") {
// Let the staus bar handle this
return;
@@ -1145,7 +1145,7 @@ module.exports = createReactClass({
filter: filter,
term: term,
});
- this._handleSearchResult(searchPromise).then();
+ this._handleSearchResult(searchPromise).done();
},
_handleSearchResult: function(searchPromise) {
@@ -1316,7 +1316,7 @@ module.exports = createReactClass({
},
onForgetClick: function() {
- MatrixClientPeg.get().forget(this.state.room.roomId).then(function() {
+ MatrixClientPeg.get().forget(this.state.room.roomId).done(function() {
dis.dispatch({ action: 'view_next_room' });
}, function(err) {
const errCode = err.errcode || _t("unknown error code");
@@ -1333,7 +1333,7 @@ module.exports = createReactClass({
this.setState({
rejecting: true,
});
- MatrixClientPeg.get().leave(this.state.roomId).then(function() {
+ MatrixClientPeg.get().leave(this.state.roomId).done(function() {
dis.dispatch({ action: 'view_next_room' });
self.setState({
rejecting: false,
diff --git a/src/components/structures/TimelinePanel.js b/src/components/structures/TimelinePanel.js
index 573d82bb9d..faa6f2564a 100644
--- a/src/components/structures/TimelinePanel.js
+++ b/src/components/structures/TimelinePanel.js
@@ -462,7 +462,7 @@ const TimelinePanel = createReactClass({
// timeline window.
//
// see https://github.com/vector-im/vector-web/issues/1035
- this._timelineWindow.paginate(EventTimeline.FORWARDS, 1, false).then(() => {
+ this._timelineWindow.paginate(EventTimeline.FORWARDS, 1, false).done(() => {
if (this.unmounted) { return; }
const { events, liveEvents } = this._getEvents();
@@ -1088,7 +1088,7 @@ const TimelinePanel = createReactClass({
prom = prom.then(onLoaded, onError);
}
- prom.then();
+ prom.done();
},
// handle the completion of a timeline load or localEchoUpdate, by
diff --git a/src/components/structures/auth/ForgotPassword.js b/src/components/structures/auth/ForgotPassword.js
index 6f68293caa..46a5fa7bd7 100644
--- a/src/components/structures/auth/ForgotPassword.js
+++ b/src/components/structures/auth/ForgotPassword.js
@@ -105,7 +105,7 @@ module.exports = createReactClass({
phase: PHASE_SENDING_EMAIL,
});
this.reset = new PasswordReset(this.props.serverConfig.hsUrl, this.props.serverConfig.isUrl);
- this.reset.resetPassword(email, password).then(() => {
+ this.reset.resetPassword(email, password).done(() => {
this.setState({
phase: PHASE_EMAIL_SENT,
});
diff --git a/src/components/structures/auth/Login.js b/src/components/structures/auth/Login.js
index 84209e514f..ad77ed49a5 100644
--- a/src/components/structures/auth/Login.js
+++ b/src/components/structures/auth/Login.js
@@ -253,7 +253,7 @@ module.exports = createReactClass({
this.setState({
busy: false,
});
- }).then();
+ }).done();
},
onUsernameChanged: function(username) {
@@ -424,7 +424,7 @@ module.exports = createReactClass({
this.setState({
busy: false,
});
- }).then();
+ }).done();
},
_isSupportedFlow: function(flow) {
diff --git a/src/components/structures/auth/PostRegistration.js b/src/components/structures/auth/PostRegistration.js
index 760163585d..66075c80f7 100644
--- a/src/components/structures/auth/PostRegistration.js
+++ b/src/components/structures/auth/PostRegistration.js
@@ -43,7 +43,7 @@ module.exports = createReactClass({
const cli = MatrixClientPeg.get();
this.setState({busy: true});
const self = this;
- cli.getProfileInfo(cli.credentials.userId).then(function(result) {
+ cli.getProfileInfo(cli.credentials.userId).done(function(result) {
self.setState({
avatarUrl: MatrixClientPeg.get().mxcUrlToHttp(result.avatar_url),
busy: false,
diff --git a/src/components/structures/auth/Registration.js b/src/components/structures/auth/Registration.js
index 6e0fc246d2..6321028457 100644
--- a/src/components/structures/auth/Registration.js
+++ b/src/components/structures/auth/Registration.js
@@ -371,7 +371,7 @@ module.exports = createReactClass({
if (pushers[i].kind === 'email') {
const emailPusher = pushers[i];
emailPusher.data = { brand: this.props.brand };
- matrixClient.setPusher(emailPusher).then(() => {
+ matrixClient.setPusher(emailPusher).done(() => {
console.log("Set email branding to " + this.props.brand);
}, (error) => {
console.error("Couldn't set email branding: " + error);
diff --git a/src/components/views/auth/InteractiveAuthEntryComponents.js b/src/components/views/auth/InteractiveAuthEntryComponents.js
index d61035210b..d19ce95b33 100644
--- a/src/components/views/auth/InteractiveAuthEntryComponents.js
+++ b/src/components/views/auth/InteractiveAuthEntryComponents.js
@@ -441,7 +441,7 @@ export const MsisdnAuthEntry = createReactClass({
this.props.fail(e);
}).finally(() => {
this.setState({requestingToken: false});
- }).then();
+ }).done();
},
/*
diff --git a/src/components/views/context_menus/RoomTileContextMenu.js b/src/components/views/context_menus/RoomTileContextMenu.js
index 98628979e5..541daef27f 100644
--- a/src/components/views/context_menus/RoomTileContextMenu.js
+++ b/src/components/views/context_menus/RoomTileContextMenu.js
@@ -161,7 +161,7 @@ module.exports = createReactClass({
_onClickForget: function() {
// FIXME: duplicated with RoomSettings (and dead code in RoomView)
- MatrixClientPeg.get().forget(this.props.room.roomId).then(() => {
+ MatrixClientPeg.get().forget(this.props.room.roomId).done(() => {
// Switch to another room view if we're currently viewing the
// historical room
if (RoomViewStore.getRoomId() === this.props.room.roomId) {
@@ -191,7 +191,7 @@ module.exports = createReactClass({
this.setState({
roomNotifState: newState,
});
- RoomNotifs.setRoomNotifsState(roomId, newState).then(() => {
+ RoomNotifs.setRoomNotifsState(roomId, newState).done(() => {
// delay slightly so that the user can see their state change
// before closing the menu
return sleep(500).then(() => {
diff --git a/src/components/views/dialogs/AddressPickerDialog.js b/src/components/views/dialogs/AddressPickerDialog.js
index caf6bc18c5..dc61f23956 100644
--- a/src/components/views/dialogs/AddressPickerDialog.js
+++ b/src/components/views/dialogs/AddressPickerDialog.js
@@ -267,7 +267,7 @@ module.exports = createReactClass({
this.setState({
searchError: err.errcode ? err.message : _t('Something went wrong!'),
});
- }).then(() => {
+ }).done(() => {
this.setState({
busy: false,
});
@@ -380,7 +380,7 @@ module.exports = createReactClass({
// Do a local search immediately
this._doLocalSearch(query);
}
- }).then(() => {
+ }).done(() => {
this.setState({
busy: false,
});
diff --git a/src/components/views/dialogs/CreateGroupDialog.js b/src/components/views/dialogs/CreateGroupDialog.js
index 191d797a1e..11f4c21366 100644
--- a/src/components/views/dialogs/CreateGroupDialog.js
+++ b/src/components/views/dialogs/CreateGroupDialog.js
@@ -93,7 +93,7 @@ export default createReactClass({
this.setState({createError: e});
}).finally(() => {
this.setState({creating: false});
- }).then();
+ }).done();
},
_onCancel: function() {
diff --git a/src/components/views/dialogs/KeyShareDialog.js b/src/components/views/dialogs/KeyShareDialog.js
index 51b02f1adf..a10c25a0fb 100644
--- a/src/components/views/dialogs/KeyShareDialog.js
+++ b/src/components/views/dialogs/KeyShareDialog.js
@@ -78,7 +78,7 @@ export default createReactClass({
true,
);
}
- }).then();
+ }).done();
},
componentWillUnmount: function() {
diff --git a/src/components/views/dialogs/SetEmailDialog.js b/src/components/views/dialogs/SetEmailDialog.js
index b527abffc9..bedf713c4e 100644
--- a/src/components/views/dialogs/SetEmailDialog.js
+++ b/src/components/views/dialogs/SetEmailDialog.js
@@ -62,7 +62,7 @@ export default createReactClass({
return;
}
this._addThreepid = new AddThreepid();
- this._addThreepid.addEmailAddress(emailAddress).then(() => {
+ this._addThreepid.addEmailAddress(emailAddress).done(() => {
Modal.createTrackedDialog('Verification Pending', '', QuestionDialog, {
title: _t("Verification Pending"),
description: _t(
@@ -96,7 +96,7 @@ export default createReactClass({
},
verifyEmailAddress: function() {
- this._addThreepid.checkEmailLinkClicked().then(() => {
+ this._addThreepid.checkEmailLinkClicked().done(() => {
this.props.onFinished(true);
}, (err) => {
this.setState({emailBusy: false});
diff --git a/src/components/views/elements/AppTile.js b/src/components/views/elements/AppTile.js
index 453630413c..260b63dfd4 100644
--- a/src/components/views/elements/AppTile.js
+++ b/src/components/views/elements/AppTile.js
@@ -205,7 +205,7 @@ export default class AppTile extends React.Component {
if (!this._scalarClient) {
this._scalarClient = defaultManager.getScalarClient();
}
- this._scalarClient.getScalarToken().then((token) => {
+ this._scalarClient.getScalarToken().done((token) => {
// Append scalar_token as a query param if not already present
this._scalarClient.scalarToken = token;
const u = url.parse(this._addWurlParams(this.props.url));
diff --git a/src/components/views/elements/EditableTextContainer.js b/src/components/views/elements/EditableTextContainer.js
index cc49c3c67f..3bf37df951 100644
--- a/src/components/views/elements/EditableTextContainer.js
+++ b/src/components/views/elements/EditableTextContainer.js
@@ -51,7 +51,7 @@ export default class EditableTextContainer extends React.Component {
this.setState({busy: true});
- this.props.getInitialValue().then(
+ this.props.getInitialValue().done(
(result) => {
if (this._unmounted) { return; }
this.setState({
@@ -83,7 +83,7 @@ export default class EditableTextContainer extends React.Component {
errorString: null,
});
- this.props.onSubmit(value).then(
+ this.props.onSubmit(value).done(
() => {
if (this._unmounted) { return; }
this.setState({
diff --git a/src/components/views/elements/ErrorBoundary.js b/src/components/views/elements/ErrorBoundary.js
index e36464c4ef..e53e1ec0fa 100644
--- a/src/components/views/elements/ErrorBoundary.js
+++ b/src/components/views/elements/ErrorBoundary.js
@@ -54,7 +54,7 @@ export default class ErrorBoundary extends React.PureComponent {
if (!PlatformPeg.get()) return;
MatrixClientPeg.get().stopClient();
- MatrixClientPeg.get().store.deleteAllData().then(() => {
+ MatrixClientPeg.get().store.deleteAllData().done(() => {
PlatformPeg.get().reload();
});
};
diff --git a/src/components/views/elements/ImageView.js b/src/components/views/elements/ImageView.js
index 09e0ff0e54..2772363bd0 100644
--- a/src/components/views/elements/ImageView.js
+++ b/src/components/views/elements/ImageView.js
@@ -84,7 +84,7 @@ export default class ImageView extends React.Component {
title: _t('Error'),
description: _t('You cannot delete this image. (%(code)s)', {code: code}),
});
- }).then();
+ }).done();
},
});
};
diff --git a/src/components/views/elements/LanguageDropdown.js b/src/components/views/elements/LanguageDropdown.js
index 0c4b2b9d6a..365f9ded61 100644
--- a/src/components/views/elements/LanguageDropdown.js
+++ b/src/components/views/elements/LanguageDropdown.js
@@ -49,7 +49,7 @@ export default class LanguageDropdown extends React.Component {
this.setState({langs});
}).catch(() => {
this.setState({langs: ['en']});
- }).then();
+ }).done();
if (!this.props.value) {
// If no value is given, we start with the first
diff --git a/src/components/views/groups/GroupUserSettings.js b/src/components/views/groups/GroupUserSettings.js
index 3cd5731b99..7d80bdd209 100644
--- a/src/components/views/groups/GroupUserSettings.js
+++ b/src/components/views/groups/GroupUserSettings.js
@@ -36,7 +36,7 @@ export default createReactClass({
},
componentWillMount: function() {
- this.context.matrixClient.getJoinedGroups().then((result) => {
+ this.context.matrixClient.getJoinedGroups().done((result) => {
this.setState({groups: result.groups || [], error: null});
}, (err) => {
console.error(err);
diff --git a/src/components/views/messages/MAudioBody.js b/src/components/views/messages/MAudioBody.js
index 0246d28542..b4f26d0cbd 100644
--- a/src/components/views/messages/MAudioBody.js
+++ b/src/components/views/messages/MAudioBody.js
@@ -55,7 +55,7 @@ export default class MAudioBody extends React.Component {
decryptFile(content.file).then(function(blob) {
decryptedBlob = blob;
return URL.createObjectURL(decryptedBlob);
- }).then((url) => {
+ }).done((url) => {
this.setState({
decryptedUrl: url,
decryptedBlob: decryptedBlob,
diff --git a/src/components/views/messages/MImageBody.js b/src/components/views/messages/MImageBody.js
index abfd8b64cd..640baa1966 100644
--- a/src/components/views/messages/MImageBody.js
+++ b/src/components/views/messages/MImageBody.js
@@ -289,7 +289,7 @@ export default class MImageBody extends React.Component {
this.setState({
error: err,
});
- }).then();
+ }).done();
}
// Remember that the user wanted to show this particular image
diff --git a/src/components/views/messages/MVideoBody.js b/src/components/views/messages/MVideoBody.js
index bc4dbf3374..d277b6eae9 100644
--- a/src/components/views/messages/MVideoBody.js
+++ b/src/components/views/messages/MVideoBody.js
@@ -115,7 +115,7 @@ module.exports = createReactClass({
this.setState({
error: err,
});
- }).then();
+ }).done();
}
},
diff --git a/src/components/views/right_panel/UserInfo.js b/src/components/views/right_panel/UserInfo.js
index 192efcdd8a..207bf29998 100644
--- a/src/components/views/right_panel/UserInfo.js
+++ b/src/components/views/right_panel/UserInfo.js
@@ -870,7 +870,7 @@ const UserInfo = withLegacyMatrixClient(({matrixClient: cli, user, groupId, room
},
).finally(() => {
stopUpdating();
- }).then();
+ }).done();
};
const roomId = user.roomId;
diff --git a/src/components/views/rooms/LinkPreviewWidget.js b/src/components/views/rooms/LinkPreviewWidget.js
index b06a9b9a30..d93fe76b46 100644
--- a/src/components/views/rooms/LinkPreviewWidget.js
+++ b/src/components/views/rooms/LinkPreviewWidget.js
@@ -53,7 +53,7 @@ module.exports = createReactClass({
);
}, (error)=>{
console.error("Failed to get URL preview: " + error);
- }).then();
+ }).done();
},
componentDidMount: function() {
diff --git a/src/components/views/rooms/MemberInfo.js b/src/components/views/rooms/MemberInfo.js
index 68e494d5eb..2ea6392e96 100644
--- a/src/components/views/rooms/MemberInfo.js
+++ b/src/components/views/rooms/MemberInfo.js
@@ -248,7 +248,7 @@ module.exports = createReactClass({
return client.getStoredDevicesForUser(member.userId);
}).finally(function() {
self._cancelDeviceList = null;
- }).then(function(devices) {
+ }).done(function(devices) {
if (cancelled) {
// we got cancelled - presumably a different user now
return;
@@ -572,7 +572,7 @@ module.exports = createReactClass({
},
).finally(()=>{
this.setState({ updating: this.state.updating - 1 });
- }).then();
+ }).done();
},
onPowerChange: async function(powerLevel) {
@@ -629,7 +629,7 @@ module.exports = createReactClass({
this.setState({ updating: this.state.updating + 1 });
createRoom({dmUserId: this.props.member.userId}).finally(() => {
this.setState({ updating: this.state.updating - 1 });
- }).then();
+ }).done();
},
onLeaveClick: function() {
diff --git a/src/components/views/settings/ChangeAvatar.js b/src/components/views/settings/ChangeAvatar.js
index 904b17b15f..32521006c7 100644
--- a/src/components/views/settings/ChangeAvatar.js
+++ b/src/components/views/settings/ChangeAvatar.js
@@ -112,7 +112,7 @@ module.exports = createReactClass({
}
});
- httpPromise.then(function() {
+ httpPromise.done(function() {
self.setState({
phase: self.Phases.Display,
avatarUrl: MatrixClientPeg.get().mxcUrlToHttp(newUrl),
diff --git a/src/components/views/settings/ChangePassword.js b/src/components/views/settings/ChangePassword.js
index 15aa6203d7..91292b19f9 100644
--- a/src/components/views/settings/ChangePassword.js
+++ b/src/components/views/settings/ChangePassword.js
@@ -174,7 +174,7 @@ module.exports = createReactClass({
newPassword: "",
newPasswordConfirm: "",
});
- }).then();
+ }).done();
},
_optionallySetEmail: function() {
diff --git a/src/components/views/settings/DevicesPanel.js b/src/components/views/settings/DevicesPanel.js
index cb5db10be4..30f507ea18 100644
--- a/src/components/views/settings/DevicesPanel.js
+++ b/src/components/views/settings/DevicesPanel.js
@@ -52,7 +52,7 @@ export default class DevicesPanel extends React.Component {
}
_loadDevices() {
- MatrixClientPeg.get().getDevices().then(
+ MatrixClientPeg.get().getDevices().done(
(resp) => {
if (this._unmounted) { return; }
this.setState({devices: resp.devices || []});
diff --git a/src/components/views/settings/Notifications.js b/src/components/views/settings/Notifications.js
index e67c61dff5..e3b4cfe122 100644
--- a/src/components/views/settings/Notifications.js
+++ b/src/components/views/settings/Notifications.js
@@ -97,7 +97,7 @@ module.exports = createReactClass({
phase: this.phases.LOADING,
});
- MatrixClientPeg.get().setPushRuleEnabled('global', self.state.masterPushRule.kind, self.state.masterPushRule.rule_id, !checked).then(function() {
+ MatrixClientPeg.get().setPushRuleEnabled('global', self.state.masterPushRule.kind, self.state.masterPushRule.rule_id, !checked).done(function() {
self._refreshFromServer();
});
},
@@ -170,7 +170,7 @@ module.exports = createReactClass({
emailPusher.kind = null;
emailPusherPromise = MatrixClientPeg.get().setPusher(emailPusher);
}
- emailPusherPromise.then(() => {
+ emailPusherPromise.done(() => {
this._refreshFromServer();
}, (error) => {
const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
@@ -274,7 +274,7 @@ module.exports = createReactClass({
}
}
- Promise.all(deferreds).then(function() {
+ Promise.all(deferreds).done(function() {
self._refreshFromServer();
}, function(error) {
const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
@@ -343,7 +343,7 @@ module.exports = createReactClass({
}
}
- Promise.all(deferreds).then(function(resps) {
+ Promise.all(deferreds).done(function(resps) {
self._refreshFromServer();
}, function(error) {
const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
@@ -398,7 +398,7 @@ module.exports = createReactClass({
};
// Then, add the new ones
- Promise.all(removeDeferreds).then(function(resps) {
+ Promise.all(removeDeferreds).done(function(resps) {
const deferreds = [];
let pushRuleVectorStateKind = self.state.vectorContentRules.vectorState;
@@ -434,7 +434,7 @@ module.exports = createReactClass({
}
}
- Promise.all(deferreds).then(function(resps) {
+ Promise.all(deferreds).done(function(resps) {
self._refreshFromServer();
}, onError);
}, onError);
@@ -650,7 +650,7 @@ module.exports = createReactClass({
externalContentRules: self.state.externalContentRules,
externalPushRules: self.state.externalPushRules,
});
- }).then();
+ }).done();
MatrixClientPeg.get().getThreePids().then((r) => this.setState({threepids: r.threepids}));
},
diff --git a/src/components/views/settings/tabs/user/HelpUserSettingsTab.js b/src/components/views/settings/tabs/user/HelpUserSettingsTab.js
index 875f0bfc10..fbad327078 100644
--- a/src/components/views/settings/tabs/user/HelpUserSettingsTab.js
+++ b/src/components/views/settings/tabs/user/HelpUserSettingsTab.js
@@ -75,7 +75,7 @@ export default class HelpUserSettingsTab extends React.Component {
// stopping in the middle of the logs.
console.log("Clear cache & reload clicked");
MatrixClientPeg.get().stopClient();
- MatrixClientPeg.get().store.deleteAllData().then(() => {
+ MatrixClientPeg.get().store.deleteAllData().done(() => {
PlatformPeg.get().reload();
});
};
diff --git a/src/stores/RoomViewStore.js b/src/stores/RoomViewStore.js
index 177b88c3f2..7e1b06c0bf 100644
--- a/src/stores/RoomViewStore.js
+++ b/src/stores/RoomViewStore.js
@@ -186,7 +186,7 @@ class RoomViewStore extends Store {
roomLoading: true,
roomLoadError: null,
});
- MatrixClientPeg.get().getRoomIdForAlias(payload.room_alias).then(
+ MatrixClientPeg.get().getRoomIdForAlias(payload.room_alias).done(
(result) => {
dis.dispatch({
action: 'view_room',
@@ -223,7 +223,7 @@ class RoomViewStore extends Store {
});
MatrixClientPeg.get().joinRoom(
this._state.roomAlias || this._state.roomId, payload.opts,
- ).then(() => {
+ ).done(() => {
// We don't actually need to do anything here: we do *not*
// clear the 'joining' flag because the Room object and/or
// our 'joined' member event may not have come down the sync
diff --git a/test/components/views/dialogs/InteractiveAuthDialog-test.js b/test/components/views/dialogs/InteractiveAuthDialog-test.js
index 7612b43b48..b14ea7c242 100644
--- a/test/components/views/dialogs/InteractiveAuthDialog-test.js
+++ b/test/components/views/dialogs/InteractiveAuthDialog-test.js
@@ -26,7 +26,6 @@ import sdk from 'matrix-react-sdk';
import MatrixClientPeg from '../../../../src/MatrixClientPeg';
import * as test_utils from '../../../test-utils';
-import {sleep} from "../../../../src/utils/promise";
const InteractiveAuthDialog = sdk.getComponent(
'views.dialogs.InteractiveAuthDialog',
@@ -108,7 +107,7 @@ describe('InteractiveAuthDialog', function() {
},
})).toBe(true);
// let the request complete
- return sleep(1);
+ return Promise.delay(1);
}).then(() => {
expect(onFinished.callCount).toEqual(1);
expect(onFinished.calledWithExactly(true, {a: 1})).toBe(true);
diff --git a/test/components/views/elements/MemberEventListSummary-test.js b/test/components/views/elements/MemberEventListSummary-test.js
index a31cbdebb5..95f7e7999a 100644
--- a/test/components/views/elements/MemberEventListSummary-test.js
+++ b/test/components/views/elements/MemberEventListSummary-test.js
@@ -91,7 +91,7 @@ describe('MemberEventListSummary', function() {
testUtils.beforeEach(this);
sandbox = testUtils.stubClient();
- languageHandler.setLanguage('en').then(done);
+ languageHandler.setLanguage('en').done(done);
languageHandler.setMissingEntryGenerator(function(key) {
return key.split('|', 2)[1];
});
diff --git a/test/components/views/rooms/MessageComposerInput-test.js b/test/components/views/rooms/MessageComposerInput-test.js
index ed25c4d607..1105a4af17 100644
--- a/test/components/views/rooms/MessageComposerInput-test.js
+++ b/test/components/views/rooms/MessageComposerInput-test.js
@@ -8,7 +8,6 @@ import * as testUtils from '../../../test-utils';
import sdk from 'matrix-react-sdk';
const MessageComposerInput = sdk.getComponent('views.rooms.MessageComposerInput');
import MatrixClientPeg from '../../../../src/MatrixClientPeg';
-import {sleep} from "../../../../src/utils/promise";
function addTextToDraft(text) {
const components = document.getElementsByClassName('public-DraftEditor-content');
@@ -50,7 +49,7 @@ xdescribe('MessageComposerInput', () => {
// warnings
// (please can we make the components not setState() after
// they are unmounted?)
- sleep(10).then(() => {
+ Promise.delay(10).done(() => {
if (parentDiv) {
ReactDOM.unmountComponentAtNode(parentDiv);
parentDiv.remove();
diff --git a/test/i18n-test/languageHandler-test.js b/test/i18n-test/languageHandler-test.js
index 8f21638703..0d96bc15ab 100644
--- a/test/i18n-test/languageHandler-test.js
+++ b/test/i18n-test/languageHandler-test.js
@@ -11,7 +11,7 @@ describe('languageHandler', function() {
testUtils.beforeEach(this);
sandbox = testUtils.stubClient();
- languageHandler.setLanguage('en').then(done);
+ languageHandler.setLanguage('en').done(done);
});
afterEach(function() {
From f9d6ed63f0e82056905fa8fcee1914d3de4deaf1 Mon Sep 17 00:00:00 2001
From: Michael Telatynski <7t3chguy@gmail.com>
Date: Tue, 12 Nov 2019 11:56:32 +0000
Subject: [PATCH 052/334] Revert "Replace Promise.delay with promise utils
sleep"
This reverts commit 0a21957b
---
src/components/structures/GroupView.js | 9 ++++-----
.../views/context_menus/RoomTileContextMenu.js | 7 +++----
src/components/views/dialogs/AddressPickerDialog.js | 3 +--
.../views/settings/tabs/user/SecurityUserSettingsTab.js | 3 +--
4 files changed, 9 insertions(+), 13 deletions(-)
diff --git a/src/components/structures/GroupView.js b/src/components/structures/GroupView.js
index 4056557a7c..4d8f47003c 100644
--- a/src/components/structures/GroupView.js
+++ b/src/components/structures/GroupView.js
@@ -38,7 +38,6 @@ import FlairStore from '../../stores/FlairStore';
import { showGroupAddRoomDialog } from '../../GroupAddressPicker';
import {makeGroupPermalink, makeUserPermalink} from "../../utils/permalinks/Permalinks";
import {Group} from "matrix-js-sdk";
-import {sleep} from "../../utils/promise";
const LONG_DESC_PLACEHOLDER = _td(
`
HTML for your community's page
@@ -693,7 +692,7 @@ export default createReactClass({
// Wait 500ms to prevent flashing. Do this before sending a request otherwise we risk the
// spinner disappearing after we have fetched new group data.
- await sleep(500);
+ await Promise.delay(500);
GroupStore.acceptGroupInvite(this.props.groupId).then(() => {
// don't reset membershipBusy here: wait for the membership change to come down the sync
@@ -712,7 +711,7 @@ export default createReactClass({
// Wait 500ms to prevent flashing. Do this before sending a request otherwise we risk the
// spinner disappearing after we have fetched new group data.
- await sleep(500);
+ await Promise.delay(500);
GroupStore.leaveGroup(this.props.groupId).then(() => {
// don't reset membershipBusy here: wait for the membership change to come down the sync
@@ -736,7 +735,7 @@ export default createReactClass({
// Wait 500ms to prevent flashing. Do this before sending a request otherwise we risk the
// spinner disappearing after we have fetched new group data.
- await sleep(500);
+ await Promise.delay(500);
GroupStore.joinGroup(this.props.groupId).then(() => {
// don't reset membershipBusy here: wait for the membership change to come down the sync
@@ -788,7 +787,7 @@ export default createReactClass({
// Wait 500ms to prevent flashing. Do this before sending a request otherwise we risk the
// spinner disappearing after we have fetched new group data.
- await sleep(500);
+ await Promise.delay(500);
GroupStore.leaveGroup(this.props.groupId).then(() => {
// don't reset membershipBusy here: wait for the membership change to come down the sync
diff --git a/src/components/views/context_menus/RoomTileContextMenu.js b/src/components/views/context_menus/RoomTileContextMenu.js
index 541daef27f..9bb573026f 100644
--- a/src/components/views/context_menus/RoomTileContextMenu.js
+++ b/src/components/views/context_menus/RoomTileContextMenu.js
@@ -32,7 +32,6 @@ import * as RoomNotifs from '../../../RoomNotifs';
import Modal from '../../../Modal';
import RoomListActions from '../../../actions/RoomListActions';
import RoomViewStore from '../../../stores/RoomViewStore';
-import {sleep} from "../../../utils/promise";
module.exports = createReactClass({
displayName: 'RoomTileContextMenu',
@@ -63,7 +62,7 @@ module.exports = createReactClass({
_toggleTag: function(tagNameOn, tagNameOff) {
if (!MatrixClientPeg.get().isGuest()) {
- sleep(500).then(() => {
+ Promise.delay(500).then(() => {
dis.dispatch(RoomListActions.tagRoom(
MatrixClientPeg.get(),
this.props.room,
@@ -120,7 +119,7 @@ module.exports = createReactClass({
Rooms.guessAndSetDMRoom(
this.props.room, newIsDirectMessage,
- ).then(sleep(500)).finally(() => {
+ ).delay(500).finally(() => {
// Close the context menu
if (this.props.onFinished) {
this.props.onFinished();
@@ -194,7 +193,7 @@ module.exports = createReactClass({
RoomNotifs.setRoomNotifsState(roomId, newState).done(() => {
// delay slightly so that the user can see their state change
// before closing the menu
- return sleep(500).then(() => {
+ return Promise.delay(500).then(() => {
if (this._unmounted) return;
// Close the context menu
if (this.props.onFinished) {
diff --git a/src/components/views/dialogs/AddressPickerDialog.js b/src/components/views/dialogs/AddressPickerDialog.js
index dc61f23956..fb779fa96f 100644
--- a/src/components/views/dialogs/AddressPickerDialog.js
+++ b/src/components/views/dialogs/AddressPickerDialog.js
@@ -32,7 +32,6 @@ import * as Email from '../../../email';
import IdentityAuthClient from '../../../IdentityAuthClient';
import { getDefaultIdentityServerUrl, useDefaultIdentityServer } from '../../../utils/IdentityServerUtils';
import { abbreviateUrl } from '../../../utils/UrlUtils';
-import {sleep} from "../../../utils/promise";
const TRUNCATE_QUERY_LIST = 40;
const QUERY_USER_DIRECTORY_DEBOUNCE_MS = 200;
@@ -534,7 +533,7 @@ module.exports = createReactClass({
};
// wait a bit to let the user finish typing
- await sleep(500);
+ await Promise.delay(500);
if (cancelled) return null;
try {
diff --git a/src/components/views/settings/tabs/user/SecurityUserSettingsTab.js b/src/components/views/settings/tabs/user/SecurityUserSettingsTab.js
index 222af48fa1..e619791b01 100644
--- a/src/components/views/settings/tabs/user/SecurityUserSettingsTab.js
+++ b/src/components/views/settings/tabs/user/SecurityUserSettingsTab.js
@@ -25,7 +25,6 @@ import Analytics from "../../../../../Analytics";
import Promise from "bluebird";
import Modal from "../../../../../Modal";
import sdk from "../../../../..";
-import {sleep} from "../../../../../utils/promise";
export class IgnoredUser extends React.Component {
static propTypes = {
@@ -130,7 +129,7 @@ export default class SecurityUserSettingsTab extends React.Component {
if (e.errcode === "M_LIMIT_EXCEEDED") {
// Add a delay between each invite change in order to avoid rate
// limiting by the server.
- await sleep(e.retry_after_ms || 2500);
+ await Promise.delay(e.retry_after_ms || 2500);
// Redo last action
i--;
From 7a512f7299f5316aba434cc639a01c4b65a5a3aa Mon Sep 17 00:00:00 2001
From: Michael Telatynski <7t3chguy@gmail.com>
Date: Tue, 12 Nov 2019 11:56:43 +0000
Subject: [PATCH 053/334] Revert "Replace rest of defer usages using small
shim. Add homebrew promise utils"
This reverts commit 6850c147
---
src/Modal.js | 3 +-
src/components/structures/MatrixChat.js | 5 ++-
src/utils/MultiInviter.js | 3 +-
src/utils/promise.js | 46 -------------------------
4 files changed, 4 insertions(+), 53 deletions(-)
delete mode 100644 src/utils/promise.js
diff --git a/src/Modal.js b/src/Modal.js
index cb19731f01..26c9da8bbb 100644
--- a/src/Modal.js
+++ b/src/Modal.js
@@ -24,7 +24,6 @@ import sdk from './index';
import dis from './dispatcher';
import { _t } from './languageHandler';
import Promise from "bluebird";
-import {defer} from "./utils/promise";
const DIALOG_CONTAINER_ID = "mx_Dialog_Container";
const STATIC_DIALOG_CONTAINER_ID = "mx_Dialog_StaticContainer";
@@ -203,7 +202,7 @@ class ModalManager {
}
_getCloseFn(modal, props) {
- const deferred = defer();
+ const deferred = Promise.defer();
return [(...args) => {
deferred.resolve(args);
if (props && props.onFinished) props.onFinished.apply(null, args);
diff --git a/src/components/structures/MatrixChat.js b/src/components/structures/MatrixChat.js
index d12eba88f7..da67416400 100644
--- a/src/components/structures/MatrixChat.js
+++ b/src/components/structures/MatrixChat.js
@@ -60,7 +60,6 @@ import AutoDiscoveryUtils from "../../utils/AutoDiscoveryUtils";
import DMRoomMap from '../../utils/DMRoomMap';
import { countRoomsWithNotif } from '../../RoomNotifs';
import { setTheme } from "../../theme";
-import {defer} from "../../utils/promise";
// Disable warnings for now: we use deprecated bluebird functions
// and need to migrate, but they spam the console with warnings.
@@ -237,7 +236,7 @@ export default createReactClass({
// Used by _viewRoom before getting state from sync
this.firstSyncComplete = false;
- this.firstSyncPromise = defer();
+ this.firstSyncPromise = Promise.defer();
if (this.props.config.sync_timeline_limit) {
MatrixClientPeg.opts.initialSyncLimit = this.props.config.sync_timeline_limit;
@@ -1262,7 +1261,7 @@ export default createReactClass({
// since we're about to start the client and therefore about
// to do the first sync
this.firstSyncComplete = false;
- this.firstSyncPromise = defer();
+ this.firstSyncPromise = Promise.defer();
const cli = MatrixClientPeg.get();
const IncomingSasDialog = sdk.getComponent('views.dialogs.IncomingSasDialog');
diff --git a/src/utils/MultiInviter.js b/src/utils/MultiInviter.js
index de5c2e7610..e8995b46d7 100644
--- a/src/utils/MultiInviter.js
+++ b/src/utils/MultiInviter.js
@@ -24,7 +24,6 @@ import {_t} from "../languageHandler";
import sdk from "../index";
import Modal from "../Modal";
import SettingsStore from "../settings/SettingsStore";
-import {defer} from "./promise";
/**
* Invites multiple addresses to a room or group, handling rate limiting from the server
@@ -72,7 +71,7 @@ export default class MultiInviter {
};
}
}
- this.deferred = defer();
+ this.deferred = Promise.defer();
this._inviteMore(0);
return this.deferred.promise;
diff --git a/src/utils/promise.js b/src/utils/promise.js
deleted file mode 100644
index dd10f7fdd7..0000000000
--- a/src/utils/promise.js
+++ /dev/null
@@ -1,46 +0,0 @@
-/*
-Copyright 2019 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.
-*/
-
-// @flow
-
-// Returns a promise which resolves with a given value after the given number of ms
-export const sleep = (ms: number, value: any): Promise => new Promise((resolve => { setTimeout(resolve, ms, value); }));
-
-// Returns a promise which resolves when the input promise resolves with its value
-// or when the timeout of ms is reached with the value of given timeoutValue
-export async function timeout(promise: Promise, timeoutValue: any, ms: number): Promise {
- const timeoutPromise = new Promise((resolve) => {
- const timeoutId = setTimeout(resolve, ms, timeoutValue);
- promise.then(() => {
- clearTimeout(timeoutId);
- });
- });
-
- return Promise.race([promise, timeoutPromise]);
-}
-
-// Returns a Deferred
-export function defer(): {resolve: () => {}, reject: () => {}, promise: Promise} {
- let resolve;
- let reject;
-
- const promise = new Promise((_resolve, _reject) => {
- resolve = _resolve;
- reject = _reject;
- });
-
- return {resolve, reject, promise};
-}
From 548e38cba9fc24748b4fde73895ac9b30e53d2bf Mon Sep 17 00:00:00 2001
From: Michael Telatynski <7t3chguy@gmail.com>
Date: Tue, 12 Nov 2019 11:56:53 +0000
Subject: [PATCH 054/334] Revert "Replace all trivial Promise.defer usages with
regular Promises"
This reverts commit 44401d73
---
src/ContentMessages.js | 174 +++++++++---------
src/Lifecycle.js | 8 +-
src/components/views/rooms/Autocomplete.js | 29 +--
.../views/settings/ChangePassword.js | 9 +-
src/rageshake/submit-rageshake.js | 40 ++--
5 files changed, 138 insertions(+), 122 deletions(-)
diff --git a/src/ContentMessages.js b/src/ContentMessages.js
index dab8de2465..2d58622db8 100644
--- a/src/ContentMessages.js
+++ b/src/ContentMessages.js
@@ -59,38 +59,40 @@ export class UploadCanceledError extends Error {}
* and a thumbnail key.
*/
function createThumbnail(element, inputWidth, inputHeight, mimeType) {
- return new Promise((resolve) => {
- let targetWidth = inputWidth;
- let targetHeight = inputHeight;
- if (targetHeight > MAX_HEIGHT) {
- targetWidth = Math.floor(targetWidth * (MAX_HEIGHT / targetHeight));
- targetHeight = MAX_HEIGHT;
- }
- if (targetWidth > MAX_WIDTH) {
- targetHeight = Math.floor(targetHeight * (MAX_WIDTH / targetWidth));
- targetWidth = MAX_WIDTH;
- }
+ const deferred = Promise.defer();
- const canvas = document.createElement("canvas");
- canvas.width = targetWidth;
- canvas.height = targetHeight;
- canvas.getContext("2d").drawImage(element, 0, 0, targetWidth, targetHeight);
- canvas.toBlob(function(thumbnail) {
- resolve({
- info: {
- thumbnail_info: {
- w: targetWidth,
- h: targetHeight,
- mimetype: thumbnail.type,
- size: thumbnail.size,
- },
- w: inputWidth,
- h: inputHeight,
+ let targetWidth = inputWidth;
+ let targetHeight = inputHeight;
+ if (targetHeight > MAX_HEIGHT) {
+ targetWidth = Math.floor(targetWidth * (MAX_HEIGHT / targetHeight));
+ targetHeight = MAX_HEIGHT;
+ }
+ if (targetWidth > MAX_WIDTH) {
+ targetHeight = Math.floor(targetHeight * (MAX_WIDTH / targetWidth));
+ targetWidth = MAX_WIDTH;
+ }
+
+ const canvas = document.createElement("canvas");
+ canvas.width = targetWidth;
+ canvas.height = targetHeight;
+ canvas.getContext("2d").drawImage(element, 0, 0, targetWidth, targetHeight);
+ canvas.toBlob(function(thumbnail) {
+ deferred.resolve({
+ info: {
+ thumbnail_info: {
+ w: targetWidth,
+ h: targetHeight,
+ mimetype: thumbnail.type,
+ size: thumbnail.size,
},
- thumbnail: thumbnail,
- });
- }, mimeType);
- });
+ w: inputWidth,
+ h: inputHeight,
+ },
+ thumbnail: thumbnail,
+ });
+ }, mimeType);
+
+ return deferred.promise;
}
/**
@@ -177,29 +179,30 @@ function infoForImageFile(matrixClient, roomId, imageFile) {
* @return {Promise} A promise that resolves with the video image element.
*/
function loadVideoElement(videoFile) {
- return new Promise((resolve, reject) => {
- // Load the file into an html element
- const video = document.createElement("video");
+ const deferred = Promise.defer();
- const reader = new FileReader();
+ // Load the file into an html element
+ const video = document.createElement("video");
- reader.onload = function(e) {
- video.src = e.target.result;
+ const reader = new FileReader();
+ reader.onload = function(e) {
+ video.src = e.target.result;
- // Once ready, returns its size
- // Wait until we have enough data to thumbnail the first frame.
- video.onloadeddata = function() {
- resolve(video);
- };
- video.onerror = function(e) {
- reject(e);
- };
+ // Once ready, returns its size
+ // Wait until we have enough data to thumbnail the first frame.
+ video.onloadeddata = function() {
+ deferred.resolve(video);
};
- reader.onerror = function(e) {
- reject(e);
+ video.onerror = function(e) {
+ deferred.reject(e);
};
- reader.readAsDataURL(videoFile);
- });
+ };
+ reader.onerror = function(e) {
+ deferred.reject(e);
+ };
+ reader.readAsDataURL(videoFile);
+
+ return deferred.promise;
}
/**
@@ -233,16 +236,16 @@ function infoForVideoFile(matrixClient, roomId, videoFile) {
* is read.
*/
function readFileAsArrayBuffer(file) {
- return new Promise((resolve, reject) => {
- const reader = new FileReader();
- reader.onload = function(e) {
- resolve(e.target.result);
- };
- reader.onerror = function(e) {
- reject(e);
- };
- reader.readAsArrayBuffer(file);
- });
+ const deferred = Promise.defer();
+ const reader = new FileReader();
+ reader.onload = function(e) {
+ deferred.resolve(e.target.result);
+ };
+ reader.onerror = function(e) {
+ deferred.reject(e);
+ };
+ reader.readAsArrayBuffer(file);
+ return deferred.promise;
}
/**
@@ -458,34 +461,33 @@ export default class ContentMessages {
content.info.mimetype = file.type;
}
- const prom = new Promise((resolve) => {
- if (file.type.indexOf('image/') == 0) {
- content.msgtype = 'm.image';
- infoForImageFile(matrixClient, roomId, file).then((imageInfo)=>{
- extend(content.info, imageInfo);
- resolve();
- }, (error)=>{
- console.error(error);
- content.msgtype = 'm.file';
- resolve();
- });
- } else if (file.type.indexOf('audio/') == 0) {
- content.msgtype = 'm.audio';
- resolve();
- } else if (file.type.indexOf('video/') == 0) {
- content.msgtype = 'm.video';
- infoForVideoFile(matrixClient, roomId, file).then((videoInfo)=>{
- extend(content.info, videoInfo);
- resolve();
- }, (error)=>{
- content.msgtype = 'm.file';
- resolve();
- });
- } else {
+ const def = Promise.defer();
+ if (file.type.indexOf('image/') == 0) {
+ content.msgtype = 'm.image';
+ infoForImageFile(matrixClient, roomId, file).then((imageInfo)=>{
+ extend(content.info, imageInfo);
+ def.resolve();
+ }, (error)=>{
+ console.error(error);
content.msgtype = 'm.file';
- resolve();
- }
- });
+ def.resolve();
+ });
+ } else if (file.type.indexOf('audio/') == 0) {
+ content.msgtype = 'm.audio';
+ def.resolve();
+ } else if (file.type.indexOf('video/') == 0) {
+ content.msgtype = 'm.video';
+ infoForVideoFile(matrixClient, roomId, file).then((videoInfo)=>{
+ extend(content.info, videoInfo);
+ def.resolve();
+ }, (error)=>{
+ content.msgtype = 'm.file';
+ def.resolve();
+ });
+ } else {
+ content.msgtype = 'm.file';
+ def.resolve();
+ }
const upload = {
fileName: file.name || 'Attachment',
@@ -507,7 +509,7 @@ export default class ContentMessages {
dis.dispatch({action: 'upload_progress', upload: upload});
}
- return prom.then(function() {
+ return def.promise.then(function() {
// XXX: upload.promise must be the promise that
// is returned by uploadFile as it has an abort()
// method hacked onto it.
diff --git a/src/Lifecycle.js b/src/Lifecycle.js
index 53a9b7a998..13f3abccb1 100644
--- a/src/Lifecycle.js
+++ b/src/Lifecycle.js
@@ -312,14 +312,18 @@ async function _restoreFromLocalStorage(opts) {
function _handleLoadSessionFailure(e) {
console.error("Unable to load session", e);
+ const def = Promise.defer();
const SessionRestoreErrorDialog =
sdk.getComponent('views.dialogs.SessionRestoreErrorDialog');
- const modal = Modal.createTrackedDialog('Session Restore Error', '', SessionRestoreErrorDialog, {
+ Modal.createTrackedDialog('Session Restore Error', '', SessionRestoreErrorDialog, {
error: e.message,
+ onFinished: (success) => {
+ def.resolve(success);
+ },
});
- return modal.finished.then(([success]) => {
+ return def.promise.then((success) => {
if (success) {
// user clicked continue.
_clearStorage();
diff --git a/src/components/views/rooms/Autocomplete.js b/src/components/views/rooms/Autocomplete.js
index d4b51081f4..ad5fa198a3 100644
--- a/src/components/views/rooms/Autocomplete.js
+++ b/src/components/views/rooms/Autocomplete.js
@@ -26,7 +26,6 @@ import { Room } from 'matrix-js-sdk';
import SettingsStore from "../../../settings/SettingsStore";
import Autocompleter from '../../../autocomplete/Autocompleter';
-import {sleep} from "../../../utils/promise";
const COMPOSER_SELECTED = 0;
@@ -106,11 +105,13 @@ export default class Autocomplete extends React.Component {
autocompleteDelay = 0;
}
- return new Promise((resolve) => {
- this.debounceCompletionsRequest = setTimeout(() => {
- resolve(this.processQuery(query, selection));
- }, autocompleteDelay);
- });
+ const deferred = Promise.defer();
+ this.debounceCompletionsRequest = setTimeout(() => {
+ this.processQuery(query, selection).then(() => {
+ deferred.resolve();
+ });
+ }, autocompleteDelay);
+ return deferred.promise;
}
processQuery(query, selection) {
@@ -196,16 +197,16 @@ export default class Autocomplete extends React.Component {
}
forceComplete() {
- return new Promise((resolve) => {
- this.setState({
- forceComplete: true,
- hide: false,
- }, () => {
- this.complete(this.props.query, this.props.selection).then(() => {
- resolve(this.countCompletions());
- });
+ const done = Promise.defer();
+ this.setState({
+ forceComplete: true,
+ hide: false,
+ }, () => {
+ this.complete(this.props.query, this.props.selection).then(() => {
+ done.resolve(this.countCompletions());
});
});
+ return done.promise;
}
onCompletionClicked(selectionOffset: number): boolean {
diff --git a/src/components/views/settings/ChangePassword.js b/src/components/views/settings/ChangePassword.js
index 91292b19f9..a086efaa6d 100644
--- a/src/components/views/settings/ChangePassword.js
+++ b/src/components/views/settings/ChangePassword.js
@@ -178,12 +178,17 @@ module.exports = createReactClass({
},
_optionallySetEmail: function() {
+ const deferred = Promise.defer();
// Ask for an email otherwise the user has no way to reset their password
const SetEmailDialog = sdk.getComponent("dialogs.SetEmailDialog");
- const modal = Modal.createTrackedDialog('Do you want to set an email address?', '', SetEmailDialog, {
+ Modal.createTrackedDialog('Do you want to set an email address?', '', SetEmailDialog, {
title: _t('Do you want to set an email address?'),
+ onFinished: (confirmed) => {
+ // ignore confirmed, setting an email is optional
+ deferred.resolve(confirmed);
+ },
});
- return modal.finished.then(([confirmed]) => confirmed);
+ return deferred.promise;
},
_onExportE2eKeysClicked: function() {
diff --git a/src/rageshake/submit-rageshake.js b/src/rageshake/submit-rageshake.js
index e772912e48..99c412a6ab 100644
--- a/src/rageshake/submit-rageshake.js
+++ b/src/rageshake/submit-rageshake.js
@@ -105,22 +105,26 @@ export default async function sendBugReport(bugReportEndpoint, opts) {
}
function _submitReport(endpoint, body, progressCallback) {
- return new Promise((resolve, reject) => {
- const req = new XMLHttpRequest();
- req.open("POST", endpoint);
- req.timeout = 5 * 60 * 1000;
- req.onreadystatechange = function() {
- if (req.readyState === XMLHttpRequest.LOADING) {
- progressCallback(_t("Waiting for response from server"));
- } else if (req.readyState === XMLHttpRequest.DONE) {
- // on done
- if (req.status < 200 || req.status >= 400) {
- reject(new Error(`HTTP ${req.status}`));
- return;
- }
- resolve();
- }
- };
- req.send(body);
- });
+ const deferred = Promise.defer();
+
+ const req = new XMLHttpRequest();
+ req.open("POST", endpoint);
+ req.timeout = 5 * 60 * 1000;
+ req.onreadystatechange = function() {
+ if (req.readyState === XMLHttpRequest.LOADING) {
+ progressCallback(_t("Waiting for response from server"));
+ } else if (req.readyState === XMLHttpRequest.DONE) {
+ on_done();
+ }
+ };
+ req.send(body);
+ return deferred.promise;
+
+ function on_done() {
+ if (req.status < 200 || req.status >= 400) {
+ deferred.reject(new Error(`HTTP ${req.status}`));
+ return;
+ }
+ deferred.resolve();
+ }
}
From 217dfc3eed0c5f018a986ddc4f1c05a1a31b5960 Mon Sep 17 00:00:00 2001
From: Michael Telatynski <7t3chguy@gmail.com>
Date: Tue, 12 Nov 2019 11:40:38 +0000
Subject: [PATCH 055/334] Replace all trivial Promise.defer usages with regular
Promises
(cherry picked from commit 44401d73b44b6de4daeb91613456d2760d50456d)
---
src/ContentMessages.js | 174 +++++++++---------
src/Lifecycle.js | 8 +-
src/components/views/rooms/Autocomplete.js | 29 ++-
.../views/settings/ChangePassword.js | 9 +-
src/rageshake/submit-rageshake.js | 40 ++--
5 files changed, 122 insertions(+), 138 deletions(-)
diff --git a/src/ContentMessages.js b/src/ContentMessages.js
index 2d58622db8..dab8de2465 100644
--- a/src/ContentMessages.js
+++ b/src/ContentMessages.js
@@ -59,40 +59,38 @@ export class UploadCanceledError extends Error {}
* and a thumbnail key.
*/
function createThumbnail(element, inputWidth, inputHeight, mimeType) {
- const deferred = Promise.defer();
+ return new Promise((resolve) => {
+ let targetWidth = inputWidth;
+ let targetHeight = inputHeight;
+ if (targetHeight > MAX_HEIGHT) {
+ targetWidth = Math.floor(targetWidth * (MAX_HEIGHT / targetHeight));
+ targetHeight = MAX_HEIGHT;
+ }
+ if (targetWidth > MAX_WIDTH) {
+ targetHeight = Math.floor(targetHeight * (MAX_WIDTH / targetWidth));
+ targetWidth = MAX_WIDTH;
+ }
- let targetWidth = inputWidth;
- let targetHeight = inputHeight;
- if (targetHeight > MAX_HEIGHT) {
- targetWidth = Math.floor(targetWidth * (MAX_HEIGHT / targetHeight));
- targetHeight = MAX_HEIGHT;
- }
- if (targetWidth > MAX_WIDTH) {
- targetHeight = Math.floor(targetHeight * (MAX_WIDTH / targetWidth));
- targetWidth = MAX_WIDTH;
- }
-
- const canvas = document.createElement("canvas");
- canvas.width = targetWidth;
- canvas.height = targetHeight;
- canvas.getContext("2d").drawImage(element, 0, 0, targetWidth, targetHeight);
- canvas.toBlob(function(thumbnail) {
- deferred.resolve({
- info: {
- thumbnail_info: {
- w: targetWidth,
- h: targetHeight,
- mimetype: thumbnail.type,
- size: thumbnail.size,
+ const canvas = document.createElement("canvas");
+ canvas.width = targetWidth;
+ canvas.height = targetHeight;
+ canvas.getContext("2d").drawImage(element, 0, 0, targetWidth, targetHeight);
+ canvas.toBlob(function(thumbnail) {
+ resolve({
+ info: {
+ thumbnail_info: {
+ w: targetWidth,
+ h: targetHeight,
+ mimetype: thumbnail.type,
+ size: thumbnail.size,
+ },
+ w: inputWidth,
+ h: inputHeight,
},
- w: inputWidth,
- h: inputHeight,
- },
- thumbnail: thumbnail,
- });
- }, mimeType);
-
- return deferred.promise;
+ thumbnail: thumbnail,
+ });
+ }, mimeType);
+ });
}
/**
@@ -179,30 +177,29 @@ function infoForImageFile(matrixClient, roomId, imageFile) {
* @return {Promise} A promise that resolves with the video image element.
*/
function loadVideoElement(videoFile) {
- const deferred = Promise.defer();
+ return new Promise((resolve, reject) => {
+ // Load the file into an html element
+ const video = document.createElement("video");
- // Load the file into an html element
- const video = document.createElement("video");
+ const reader = new FileReader();
- const reader = new FileReader();
- reader.onload = function(e) {
- video.src = e.target.result;
+ reader.onload = function(e) {
+ video.src = e.target.result;
- // Once ready, returns its size
- // Wait until we have enough data to thumbnail the first frame.
- video.onloadeddata = function() {
- deferred.resolve(video);
+ // Once ready, returns its size
+ // Wait until we have enough data to thumbnail the first frame.
+ video.onloadeddata = function() {
+ resolve(video);
+ };
+ video.onerror = function(e) {
+ reject(e);
+ };
};
- video.onerror = function(e) {
- deferred.reject(e);
+ reader.onerror = function(e) {
+ reject(e);
};
- };
- reader.onerror = function(e) {
- deferred.reject(e);
- };
- reader.readAsDataURL(videoFile);
-
- return deferred.promise;
+ reader.readAsDataURL(videoFile);
+ });
}
/**
@@ -236,16 +233,16 @@ function infoForVideoFile(matrixClient, roomId, videoFile) {
* is read.
*/
function readFileAsArrayBuffer(file) {
- const deferred = Promise.defer();
- const reader = new FileReader();
- reader.onload = function(e) {
- deferred.resolve(e.target.result);
- };
- reader.onerror = function(e) {
- deferred.reject(e);
- };
- reader.readAsArrayBuffer(file);
- return deferred.promise;
+ return new Promise((resolve, reject) => {
+ const reader = new FileReader();
+ reader.onload = function(e) {
+ resolve(e.target.result);
+ };
+ reader.onerror = function(e) {
+ reject(e);
+ };
+ reader.readAsArrayBuffer(file);
+ });
}
/**
@@ -461,33 +458,34 @@ export default class ContentMessages {
content.info.mimetype = file.type;
}
- const def = Promise.defer();
- if (file.type.indexOf('image/') == 0) {
- content.msgtype = 'm.image';
- infoForImageFile(matrixClient, roomId, file).then((imageInfo)=>{
- extend(content.info, imageInfo);
- def.resolve();
- }, (error)=>{
- console.error(error);
+ const prom = new Promise((resolve) => {
+ if (file.type.indexOf('image/') == 0) {
+ content.msgtype = 'm.image';
+ infoForImageFile(matrixClient, roomId, file).then((imageInfo)=>{
+ extend(content.info, imageInfo);
+ resolve();
+ }, (error)=>{
+ console.error(error);
+ content.msgtype = 'm.file';
+ resolve();
+ });
+ } else if (file.type.indexOf('audio/') == 0) {
+ content.msgtype = 'm.audio';
+ resolve();
+ } else if (file.type.indexOf('video/') == 0) {
+ content.msgtype = 'm.video';
+ infoForVideoFile(matrixClient, roomId, file).then((videoInfo)=>{
+ extend(content.info, videoInfo);
+ resolve();
+ }, (error)=>{
+ content.msgtype = 'm.file';
+ resolve();
+ });
+ } else {
content.msgtype = 'm.file';
- def.resolve();
- });
- } else if (file.type.indexOf('audio/') == 0) {
- content.msgtype = 'm.audio';
- def.resolve();
- } else if (file.type.indexOf('video/') == 0) {
- content.msgtype = 'm.video';
- infoForVideoFile(matrixClient, roomId, file).then((videoInfo)=>{
- extend(content.info, videoInfo);
- def.resolve();
- }, (error)=>{
- content.msgtype = 'm.file';
- def.resolve();
- });
- } else {
- content.msgtype = 'm.file';
- def.resolve();
- }
+ resolve();
+ }
+ });
const upload = {
fileName: file.name || 'Attachment',
@@ -509,7 +507,7 @@ export default class ContentMessages {
dis.dispatch({action: 'upload_progress', upload: upload});
}
- return def.promise.then(function() {
+ return prom.then(function() {
// XXX: upload.promise must be the promise that
// is returned by uploadFile as it has an abort()
// method hacked onto it.
diff --git a/src/Lifecycle.js b/src/Lifecycle.js
index 13f3abccb1..53a9b7a998 100644
--- a/src/Lifecycle.js
+++ b/src/Lifecycle.js
@@ -312,18 +312,14 @@ async function _restoreFromLocalStorage(opts) {
function _handleLoadSessionFailure(e) {
console.error("Unable to load session", e);
- const def = Promise.defer();
const SessionRestoreErrorDialog =
sdk.getComponent('views.dialogs.SessionRestoreErrorDialog');
- Modal.createTrackedDialog('Session Restore Error', '', SessionRestoreErrorDialog, {
+ const modal = Modal.createTrackedDialog('Session Restore Error', '', SessionRestoreErrorDialog, {
error: e.message,
- onFinished: (success) => {
- def.resolve(success);
- },
});
- return def.promise.then((success) => {
+ return modal.finished.then(([success]) => {
if (success) {
// user clicked continue.
_clearStorage();
diff --git a/src/components/views/rooms/Autocomplete.js b/src/components/views/rooms/Autocomplete.js
index ad5fa198a3..d4b51081f4 100644
--- a/src/components/views/rooms/Autocomplete.js
+++ b/src/components/views/rooms/Autocomplete.js
@@ -26,6 +26,7 @@ import { Room } from 'matrix-js-sdk';
import SettingsStore from "../../../settings/SettingsStore";
import Autocompleter from '../../../autocomplete/Autocompleter';
+import {sleep} from "../../../utils/promise";
const COMPOSER_SELECTED = 0;
@@ -105,13 +106,11 @@ export default class Autocomplete extends React.Component {
autocompleteDelay = 0;
}
- const deferred = Promise.defer();
- this.debounceCompletionsRequest = setTimeout(() => {
- this.processQuery(query, selection).then(() => {
- deferred.resolve();
- });
- }, autocompleteDelay);
- return deferred.promise;
+ return new Promise((resolve) => {
+ this.debounceCompletionsRequest = setTimeout(() => {
+ resolve(this.processQuery(query, selection));
+ }, autocompleteDelay);
+ });
}
processQuery(query, selection) {
@@ -197,16 +196,16 @@ export default class Autocomplete extends React.Component {
}
forceComplete() {
- const done = Promise.defer();
- this.setState({
- forceComplete: true,
- hide: false,
- }, () => {
- this.complete(this.props.query, this.props.selection).then(() => {
- done.resolve(this.countCompletions());
+ return new Promise((resolve) => {
+ this.setState({
+ forceComplete: true,
+ hide: false,
+ }, () => {
+ this.complete(this.props.query, this.props.selection).then(() => {
+ resolve(this.countCompletions());
+ });
});
});
- return done.promise;
}
onCompletionClicked(selectionOffset: number): boolean {
diff --git a/src/components/views/settings/ChangePassword.js b/src/components/views/settings/ChangePassword.js
index a086efaa6d..91292b19f9 100644
--- a/src/components/views/settings/ChangePassword.js
+++ b/src/components/views/settings/ChangePassword.js
@@ -178,17 +178,12 @@ module.exports = createReactClass({
},
_optionallySetEmail: function() {
- const deferred = Promise.defer();
// Ask for an email otherwise the user has no way to reset their password
const SetEmailDialog = sdk.getComponent("dialogs.SetEmailDialog");
- Modal.createTrackedDialog('Do you want to set an email address?', '', SetEmailDialog, {
+ const modal = Modal.createTrackedDialog('Do you want to set an email address?', '', SetEmailDialog, {
title: _t('Do you want to set an email address?'),
- onFinished: (confirmed) => {
- // ignore confirmed, setting an email is optional
- deferred.resolve(confirmed);
- },
});
- return deferred.promise;
+ return modal.finished.then(([confirmed]) => confirmed);
},
_onExportE2eKeysClicked: function() {
diff --git a/src/rageshake/submit-rageshake.js b/src/rageshake/submit-rageshake.js
index 99c412a6ab..e772912e48 100644
--- a/src/rageshake/submit-rageshake.js
+++ b/src/rageshake/submit-rageshake.js
@@ -105,26 +105,22 @@ export default async function sendBugReport(bugReportEndpoint, opts) {
}
function _submitReport(endpoint, body, progressCallback) {
- const deferred = Promise.defer();
-
- const req = new XMLHttpRequest();
- req.open("POST", endpoint);
- req.timeout = 5 * 60 * 1000;
- req.onreadystatechange = function() {
- if (req.readyState === XMLHttpRequest.LOADING) {
- progressCallback(_t("Waiting for response from server"));
- } else if (req.readyState === XMLHttpRequest.DONE) {
- on_done();
- }
- };
- req.send(body);
- return deferred.promise;
-
- function on_done() {
- if (req.status < 200 || req.status >= 400) {
- deferred.reject(new Error(`HTTP ${req.status}`));
- return;
- }
- deferred.resolve();
- }
+ return new Promise((resolve, reject) => {
+ const req = new XMLHttpRequest();
+ req.open("POST", endpoint);
+ req.timeout = 5 * 60 * 1000;
+ req.onreadystatechange = function() {
+ if (req.readyState === XMLHttpRequest.LOADING) {
+ progressCallback(_t("Waiting for response from server"));
+ } else if (req.readyState === XMLHttpRequest.DONE) {
+ // on done
+ if (req.status < 200 || req.status >= 400) {
+ reject(new Error(`HTTP ${req.status}`));
+ return;
+ }
+ resolve();
+ }
+ };
+ req.send(body);
+ });
}
From 2ea239d1923170ef9d226eeae99dcfc12432dc30 Mon Sep 17 00:00:00 2001
From: Michael Telatynski <7t3chguy@gmail.com>
Date: Tue, 12 Nov 2019 11:45:28 +0000
Subject: [PATCH 056/334] Replace rest of defer usages using small shim. Add
homebrew promise utils
(cherry picked from commit 6850c147393ba7be8b98d97dfc8d7244fa503461)
---
src/Modal.js | 3 +-
src/components/structures/MatrixChat.js | 5 +--
src/utils/MultiInviter.js | 3 +-
src/utils/promise.js | 46 +++++++++++++++++++++++++
4 files changed, 53 insertions(+), 4 deletions(-)
create mode 100644 src/utils/promise.js
diff --git a/src/Modal.js b/src/Modal.js
index 26c9da8bbb..cb19731f01 100644
--- a/src/Modal.js
+++ b/src/Modal.js
@@ -24,6 +24,7 @@ import sdk from './index';
import dis from './dispatcher';
import { _t } from './languageHandler';
import Promise from "bluebird";
+import {defer} from "./utils/promise";
const DIALOG_CONTAINER_ID = "mx_Dialog_Container";
const STATIC_DIALOG_CONTAINER_ID = "mx_Dialog_StaticContainer";
@@ -202,7 +203,7 @@ class ModalManager {
}
_getCloseFn(modal, props) {
- const deferred = Promise.defer();
+ const deferred = defer();
return [(...args) => {
deferred.resolve(args);
if (props && props.onFinished) props.onFinished.apply(null, args);
diff --git a/src/components/structures/MatrixChat.js b/src/components/structures/MatrixChat.js
index da67416400..d12eba88f7 100644
--- a/src/components/structures/MatrixChat.js
+++ b/src/components/structures/MatrixChat.js
@@ -60,6 +60,7 @@ import AutoDiscoveryUtils from "../../utils/AutoDiscoveryUtils";
import DMRoomMap from '../../utils/DMRoomMap';
import { countRoomsWithNotif } from '../../RoomNotifs';
import { setTheme } from "../../theme";
+import {defer} from "../../utils/promise";
// Disable warnings for now: we use deprecated bluebird functions
// and need to migrate, but they spam the console with warnings.
@@ -236,7 +237,7 @@ export default createReactClass({
// Used by _viewRoom before getting state from sync
this.firstSyncComplete = false;
- this.firstSyncPromise = Promise.defer();
+ this.firstSyncPromise = defer();
if (this.props.config.sync_timeline_limit) {
MatrixClientPeg.opts.initialSyncLimit = this.props.config.sync_timeline_limit;
@@ -1261,7 +1262,7 @@ export default createReactClass({
// since we're about to start the client and therefore about
// to do the first sync
this.firstSyncComplete = false;
- this.firstSyncPromise = Promise.defer();
+ this.firstSyncPromise = defer();
const cli = MatrixClientPeg.get();
const IncomingSasDialog = sdk.getComponent('views.dialogs.IncomingSasDialog');
diff --git a/src/utils/MultiInviter.js b/src/utils/MultiInviter.js
index e8995b46d7..de5c2e7610 100644
--- a/src/utils/MultiInviter.js
+++ b/src/utils/MultiInviter.js
@@ -24,6 +24,7 @@ import {_t} from "../languageHandler";
import sdk from "../index";
import Modal from "../Modal";
import SettingsStore from "../settings/SettingsStore";
+import {defer} from "./promise";
/**
* Invites multiple addresses to a room or group, handling rate limiting from the server
@@ -71,7 +72,7 @@ export default class MultiInviter {
};
}
}
- this.deferred = Promise.defer();
+ this.deferred = defer();
this._inviteMore(0);
return this.deferred.promise;
diff --git a/src/utils/promise.js b/src/utils/promise.js
new file mode 100644
index 0000000000..dd10f7fdd7
--- /dev/null
+++ b/src/utils/promise.js
@@ -0,0 +1,46 @@
+/*
+Copyright 2019 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.
+*/
+
+// @flow
+
+// Returns a promise which resolves with a given value after the given number of ms
+export const sleep = (ms: number, value: any): Promise => new Promise((resolve => { setTimeout(resolve, ms, value); }));
+
+// Returns a promise which resolves when the input promise resolves with its value
+// or when the timeout of ms is reached with the value of given timeoutValue
+export async function timeout(promise: Promise, timeoutValue: any, ms: number): Promise {
+ const timeoutPromise = new Promise((resolve) => {
+ const timeoutId = setTimeout(resolve, ms, timeoutValue);
+ promise.then(() => {
+ clearTimeout(timeoutId);
+ });
+ });
+
+ return Promise.race([promise, timeoutPromise]);
+}
+
+// Returns a Deferred
+export function defer(): {resolve: () => {}, reject: () => {}, promise: Promise} {
+ let resolve;
+ let reject;
+
+ const promise = new Promise((_resolve, _reject) => {
+ resolve = _resolve;
+ reject = _reject;
+ });
+
+ return {resolve, reject, promise};
+}
From 2b34cf4362ab03b0409da6086199ef2eab54dee7 Mon Sep 17 00:00:00 2001
From: Michael Telatynski <7t3chguy@gmail.com>
Date: Tue, 12 Nov 2019 11:46:58 +0000
Subject: [PATCH 057/334] Replace Promise.delay with promise utils sleep
(cherry picked from commit 0a21957b2cac0cc2d0169f428187bc2e468251a9)
---
src/components/structures/GroupView.js | 9 +++++----
.../views/context_menus/RoomTileContextMenu.js | 7 ++++---
src/components/views/dialogs/AddressPickerDialog.js | 3 ++-
.../views/settings/tabs/user/SecurityUserSettingsTab.js | 3 ++-
4 files changed, 13 insertions(+), 9 deletions(-)
diff --git a/src/components/structures/GroupView.js b/src/components/structures/GroupView.js
index 4d8f47003c..4056557a7c 100644
--- a/src/components/structures/GroupView.js
+++ b/src/components/structures/GroupView.js
@@ -38,6 +38,7 @@ import FlairStore from '../../stores/FlairStore';
import { showGroupAddRoomDialog } from '../../GroupAddressPicker';
import {makeGroupPermalink, makeUserPermalink} from "../../utils/permalinks/Permalinks";
import {Group} from "matrix-js-sdk";
+import {sleep} from "../../utils/promise";
const LONG_DESC_PLACEHOLDER = _td(
`
HTML for your community's page
@@ -692,7 +693,7 @@ export default createReactClass({
// Wait 500ms to prevent flashing. Do this before sending a request otherwise we risk the
// spinner disappearing after we have fetched new group data.
- await Promise.delay(500);
+ await sleep(500);
GroupStore.acceptGroupInvite(this.props.groupId).then(() => {
// don't reset membershipBusy here: wait for the membership change to come down the sync
@@ -711,7 +712,7 @@ export default createReactClass({
// Wait 500ms to prevent flashing. Do this before sending a request otherwise we risk the
// spinner disappearing after we have fetched new group data.
- await Promise.delay(500);
+ await sleep(500);
GroupStore.leaveGroup(this.props.groupId).then(() => {
// don't reset membershipBusy here: wait for the membership change to come down the sync
@@ -735,7 +736,7 @@ export default createReactClass({
// Wait 500ms to prevent flashing. Do this before sending a request otherwise we risk the
// spinner disappearing after we have fetched new group data.
- await Promise.delay(500);
+ await sleep(500);
GroupStore.joinGroup(this.props.groupId).then(() => {
// don't reset membershipBusy here: wait for the membership change to come down the sync
@@ -787,7 +788,7 @@ export default createReactClass({
// Wait 500ms to prevent flashing. Do this before sending a request otherwise we risk the
// spinner disappearing after we have fetched new group data.
- await Promise.delay(500);
+ await sleep(500);
GroupStore.leaveGroup(this.props.groupId).then(() => {
// don't reset membershipBusy here: wait for the membership change to come down the sync
diff --git a/src/components/views/context_menus/RoomTileContextMenu.js b/src/components/views/context_menus/RoomTileContextMenu.js
index 9bb573026f..541daef27f 100644
--- a/src/components/views/context_menus/RoomTileContextMenu.js
+++ b/src/components/views/context_menus/RoomTileContextMenu.js
@@ -32,6 +32,7 @@ import * as RoomNotifs from '../../../RoomNotifs';
import Modal from '../../../Modal';
import RoomListActions from '../../../actions/RoomListActions';
import RoomViewStore from '../../../stores/RoomViewStore';
+import {sleep} from "../../../utils/promise";
module.exports = createReactClass({
displayName: 'RoomTileContextMenu',
@@ -62,7 +63,7 @@ module.exports = createReactClass({
_toggleTag: function(tagNameOn, tagNameOff) {
if (!MatrixClientPeg.get().isGuest()) {
- Promise.delay(500).then(() => {
+ sleep(500).then(() => {
dis.dispatch(RoomListActions.tagRoom(
MatrixClientPeg.get(),
this.props.room,
@@ -119,7 +120,7 @@ module.exports = createReactClass({
Rooms.guessAndSetDMRoom(
this.props.room, newIsDirectMessage,
- ).delay(500).finally(() => {
+ ).then(sleep(500)).finally(() => {
// Close the context menu
if (this.props.onFinished) {
this.props.onFinished();
@@ -193,7 +194,7 @@ module.exports = createReactClass({
RoomNotifs.setRoomNotifsState(roomId, newState).done(() => {
// delay slightly so that the user can see their state change
// before closing the menu
- return Promise.delay(500).then(() => {
+ return sleep(500).then(() => {
if (this._unmounted) return;
// Close the context menu
if (this.props.onFinished) {
diff --git a/src/components/views/dialogs/AddressPickerDialog.js b/src/components/views/dialogs/AddressPickerDialog.js
index fb779fa96f..dc61f23956 100644
--- a/src/components/views/dialogs/AddressPickerDialog.js
+++ b/src/components/views/dialogs/AddressPickerDialog.js
@@ -32,6 +32,7 @@ import * as Email from '../../../email';
import IdentityAuthClient from '../../../IdentityAuthClient';
import { getDefaultIdentityServerUrl, useDefaultIdentityServer } from '../../../utils/IdentityServerUtils';
import { abbreviateUrl } from '../../../utils/UrlUtils';
+import {sleep} from "../../../utils/promise";
const TRUNCATE_QUERY_LIST = 40;
const QUERY_USER_DIRECTORY_DEBOUNCE_MS = 200;
@@ -533,7 +534,7 @@ module.exports = createReactClass({
};
// wait a bit to let the user finish typing
- await Promise.delay(500);
+ await sleep(500);
if (cancelled) return null;
try {
diff --git a/src/components/views/settings/tabs/user/SecurityUserSettingsTab.js b/src/components/views/settings/tabs/user/SecurityUserSettingsTab.js
index e619791b01..222af48fa1 100644
--- a/src/components/views/settings/tabs/user/SecurityUserSettingsTab.js
+++ b/src/components/views/settings/tabs/user/SecurityUserSettingsTab.js
@@ -25,6 +25,7 @@ import Analytics from "../../../../../Analytics";
import Promise from "bluebird";
import Modal from "../../../../../Modal";
import sdk from "../../../../..";
+import {sleep} from "../../../../../utils/promise";
export class IgnoredUser extends React.Component {
static propTypes = {
@@ -129,7 +130,7 @@ export default class SecurityUserSettingsTab extends React.Component {
if (e.errcode === "M_LIMIT_EXCEEDED") {
// Add a delay between each invite change in order to avoid rate
// limiting by the server.
- await Promise.delay(e.retry_after_ms || 2500);
+ await sleep(e.retry_after_ms || 2500);
// Redo last action
i--;
From f5d467b3917849cb1f17445b75fe9ea1ba38098e Mon Sep 17 00:00:00 2001
From: Michael Telatynski <7t3chguy@gmail.com>
Date: Tue, 12 Nov 2019 12:26:07 +0000
Subject: [PATCH 058/334] delint
---
src/components/views/context_menus/RoomTileContextMenu.js | 1 -
src/components/views/dialogs/AddressPickerDialog.js | 1 -
.../views/settings/tabs/user/SecurityUserSettingsTab.js | 1 -
3 files changed, 3 deletions(-)
diff --git a/src/components/views/context_menus/RoomTileContextMenu.js b/src/components/views/context_menus/RoomTileContextMenu.js
index 541daef27f..fb056ee47f 100644
--- a/src/components/views/context_menus/RoomTileContextMenu.js
+++ b/src/components/views/context_menus/RoomTileContextMenu.js
@@ -17,7 +17,6 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
-import Promise from 'bluebird';
import React from 'react';
import PropTypes from 'prop-types';
import createReactClass from 'create-react-class';
diff --git a/src/components/views/dialogs/AddressPickerDialog.js b/src/components/views/dialogs/AddressPickerDialog.js
index dc61f23956..24d8b96e0c 100644
--- a/src/components/views/dialogs/AddressPickerDialog.js
+++ b/src/components/views/dialogs/AddressPickerDialog.js
@@ -25,7 +25,6 @@ import { _t, _td } from '../../../languageHandler';
import sdk from '../../../index';
import MatrixClientPeg from '../../../MatrixClientPeg';
import dis from '../../../dispatcher';
-import Promise from 'bluebird';
import { addressTypes, getAddressType } from '../../../UserAddress.js';
import GroupStore from '../../../stores/GroupStore';
import * as Email from '../../../email';
diff --git a/src/components/views/settings/tabs/user/SecurityUserSettingsTab.js b/src/components/views/settings/tabs/user/SecurityUserSettingsTab.js
index 222af48fa1..0732bcf926 100644
--- a/src/components/views/settings/tabs/user/SecurityUserSettingsTab.js
+++ b/src/components/views/settings/tabs/user/SecurityUserSettingsTab.js
@@ -22,7 +22,6 @@ import MatrixClientPeg from "../../../../../MatrixClientPeg";
import * as FormattingUtils from "../../../../../utils/FormattingUtils";
import AccessibleButton from "../../../elements/AccessibleButton";
import Analytics from "../../../../../Analytics";
-import Promise from "bluebird";
import Modal from "../../../../../Modal";
import sdk from "../../../../..";
import {sleep} from "../../../../../utils/promise";
From cfdcf45ac6eb019242b5d969ce8018fae195caec Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Damir=20Jeli=C4=87?=
Date: Tue, 12 Nov 2019 13:29:07 +0100
Subject: [PATCH 059/334] MatrixChat: Move the event indexing logic into
separate modules.
---
src/EventIndexPeg.js | 74 +++++
src/EventIndexing.js | 404 ++++++++++++++++++++++++
src/Lifecycle.js | 2 +
src/MatrixClientPeg.js | 4 +-
src/components/structures/MatrixChat.js | 371 ++--------------------
5 files changed, 499 insertions(+), 356 deletions(-)
create mode 100644 src/EventIndexPeg.js
create mode 100644 src/EventIndexing.js
diff --git a/src/EventIndexPeg.js b/src/EventIndexPeg.js
new file mode 100644
index 0000000000..794450e4b7
--- /dev/null
+++ b/src/EventIndexPeg.js
@@ -0,0 +1,74 @@
+/*
+Copyright 2019 New Vector Ltd
+
+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.
+*/
+
+/*
+ * Holds the current Platform object used by the code to do anything
+ * specific to the platform we're running on (eg. web, electron)
+ * Platforms are provided by the app layer.
+ * This allows the app layer to set a Platform without necessarily
+ * having to have a MatrixChat object
+ */
+
+import PlatformPeg from "./PlatformPeg";
+import EventIndex from "./EventIndexing";
+import MatrixClientPeg from "./MatrixClientPeg";
+
+class EventIndexPeg {
+ constructor() {
+ this.index = null;
+ }
+
+ /**
+ * Returns the current Event index object for the application. Can be null
+ * if the platform doesn't support event indexing.
+ */
+ get() {
+ return this.index;
+ }
+
+ /** Create a new EventIndex and initialize it if the platform supports it.
+ * Returns true if an EventIndex was successfully initialized, false
+ * otherwise.
+ */
+ async init() {
+ const platform = PlatformPeg.get();
+ if (!platform.supportsEventIndexing()) return false;
+
+ let index = new EventIndex();
+
+ const userId = MatrixClientPeg.get().getUserId();
+ // TODO log errors here and return false if it errors out.
+ await index.init(userId);
+ this.index = index;
+
+ return true
+ }
+
+ async stop() {
+ if (this.index == null) return;
+ index.stopCrawler();
+ }
+
+ async deleteEventIndex() {
+ if (this.index == null) return;
+ index.deleteEventIndex();
+ }
+}
+
+if (!global.mxEventIndexPeg) {
+ global.mxEventIndexPeg = new EventIndexPeg();
+}
+module.exports = global.mxEventIndexPeg;
diff --git a/src/EventIndexing.js b/src/EventIndexing.js
new file mode 100644
index 0000000000..21ee8f3da6
--- /dev/null
+++ b/src/EventIndexing.js
@@ -0,0 +1,404 @@
+/*
+Copyright 2019 New Vector Ltd
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+import PlatformPeg from "./PlatformPeg";
+import MatrixClientPeg from "./MatrixClientPeg";
+
+/**
+ * Event indexing class that wraps the platform specific event indexing.
+ */
+export default class EventIndexer {
+ constructor() {
+ this.crawlerChekpoints = [];
+ // The time that the crawler will wait between /rooms/{room_id}/messages
+ // requests
+ this._crawler_timeout = 3000;
+ this._crawlerRef = null;
+ this.liveEventsForIndex = new Set();
+ }
+
+ async init(userId) {
+ const platform = PlatformPeg.get();
+ if (!platform.supportsEventIndexing()) return false;
+ platform.initEventIndex(userId);
+ }
+
+ async onSync(state, prevState, data) {
+ const platform = PlatformPeg.get();
+ if (!platform.supportsEventIndexing()) return;
+
+ if (prevState === null && state === "PREPARED") {
+ // Load our stored checkpoints, if any.
+ this.crawlerChekpoints = await platform.loadCheckpoints();
+ console.log("Seshat: Loaded checkpoints",
+ this.crawlerChekpoints);
+ return;
+ }
+
+ if (prevState === "PREPARED" && state === "SYNCING") {
+ const addInitialCheckpoints = async () => {
+ const client = MatrixClientPeg.get();
+ const rooms = client.getRooms();
+
+ const isRoomEncrypted = (room) => {
+ return client.isRoomEncrypted(room.roomId);
+ };
+
+ // We only care to crawl the encrypted rooms, non-encrytped
+ // rooms can use the search provided by the Homeserver.
+ const encryptedRooms = rooms.filter(isRoomEncrypted);
+
+ console.log("Seshat: Adding initial crawler checkpoints");
+
+ // Gather the prev_batch tokens and create checkpoints for
+ // our message crawler.
+ await Promise.all(encryptedRooms.map(async (room) => {
+ const timeline = room.getLiveTimeline();
+ const token = timeline.getPaginationToken("b");
+
+ console.log("Seshat: Got token for indexer",
+ room.roomId, token);
+
+ const backCheckpoint = {
+ roomId: room.roomId,
+ token: token,
+ direction: "b",
+ };
+
+ const forwardCheckpoint = {
+ roomId: room.roomId,
+ token: token,
+ direction: "f",
+ };
+
+ await platform.addCrawlerCheckpoint(backCheckpoint);
+ await platform.addCrawlerCheckpoint(forwardCheckpoint);
+ this.crawlerChekpoints.push(backCheckpoint);
+ this.crawlerChekpoints.push(forwardCheckpoint);
+ }));
+ };
+
+ // If our indexer is empty we're most likely running Riot the
+ // first time with indexing support or running it with an
+ // initial sync. Add checkpoints to crawl our encrypted rooms.
+ const eventIndexWasEmpty = await platform.isEventIndexEmpty();
+ if (eventIndexWasEmpty) await addInitialCheckpoints();
+
+ // Start our crawler.
+ this.startCrawler();
+ return;
+ }
+
+ if (prevState === "SYNCING" && state === "SYNCING") {
+ // A sync was done, presumably we queued up some live events,
+ // commit them now.
+ console.log("Seshat: Committing events");
+ await platform.commitLiveEvents();
+ return;
+ }
+ }
+
+ async onRoomTimeline(ev, room, toStartOfTimeline, removed, data) {
+ const platform = PlatformPeg.get();
+ if (!platform.supportsEventIndexing()) return;
+
+ // We only index encrypted rooms locally.
+ if (!MatrixClientPeg.get().isRoomEncrypted(room.roomId)) return;
+
+ // If it isn't a live event or if it's redacted there's nothing to
+ // do.
+ if (toStartOfTimeline || !data || !data.liveEvent
+ || ev.isRedacted()) {
+ return;
+ }
+
+ // If the event is not yet decrypted mark it for the
+ // Event.decrypted callback.
+ if (ev.isBeingDecrypted()) {
+ const eventId = ev.getId();
+ this.liveEventsForIndex.add(eventId);
+ } else {
+ // If the event is decrypted or is unencrypted add it to the
+ // index now.
+ await this.addLiveEventToIndex(ev);
+ }
+ }
+
+ async onEventDecrypted(ev, err) {
+ const platform = PlatformPeg.get();
+ if (!platform.supportsEventIndexing()) return;
+
+ const eventId = ev.getId();
+
+ // If the event isn't in our live event set, ignore it.
+ if (!this.liveEventsForIndex.delete(eventId)) return;
+ if (err) return;
+ await this.addLiveEventToIndex(ev);
+ }
+
+ async addLiveEventToIndex(ev) {
+ const platform = PlatformPeg.get();
+ if (!platform.supportsEventIndexing()) return;
+
+ if (["m.room.message", "m.room.name", "m.room.topic"]
+ .indexOf(ev.getType()) == -1) {
+ return;
+ }
+
+ const e = ev.toJSON().decrypted;
+ const profile = {
+ displayname: ev.sender.rawDisplayName,
+ avatar_url: ev.sender.getMxcAvatarUrl(),
+ };
+
+ platform.addEventToIndex(e, profile);
+ }
+
+ async crawlerFunc(handle) {
+ // TODO either put this in a better place or find a library provided
+ // method that does this.
+ const sleep = async (ms) => {
+ return new Promise(resolve => setTimeout(resolve, ms));
+ };
+
+ let cancelled = false;
+
+ console.log("Seshat: Started crawler function");
+
+ const client = MatrixClientPeg.get();
+ const platform = PlatformPeg.get();
+
+ handle.cancel = () => {
+ cancelled = true;
+ };
+
+ while (!cancelled) {
+ // This is a low priority task and we don't want to spam our
+ // Homeserver with /messages requests so we set a hefty timeout
+ // here.
+ await sleep(this._crawler_timeout);
+
+ console.log("Seshat: Running the crawler loop.");
+
+ if (cancelled) {
+ console.log("Seshat: Cancelling the crawler.");
+ break;
+ }
+
+ const checkpoint = this.crawlerChekpoints.shift();
+
+ /// There is no checkpoint available currently, one may appear if
+ // a sync with limited room timelines happens, so go back to sleep.
+ if (checkpoint === undefined) {
+ continue;
+ }
+
+ console.log("Seshat: crawling using checkpoint", checkpoint);
+
+ // We have a checkpoint, let us fetch some messages, again, very
+ // conservatively to not bother our Homeserver too much.
+ const eventMapper = client.getEventMapper();
+ // TODO we need to ensure to use member lazy loading with this
+ // request so we get the correct profiles.
+ let res;
+
+ try {
+ res = await client._createMessagesRequest(
+ checkpoint.roomId, checkpoint.token, 100,
+ checkpoint.direction);
+ } catch (e) {
+ console.log("Seshat: Error crawling events:", e);
+ this.crawlerChekpoints.push(checkpoint);
+ continue
+ }
+
+ if (res.chunk.length === 0) {
+ console.log("Seshat: Done with the checkpoint", checkpoint);
+ // We got to the start/end of our timeline, lets just
+ // delete our checkpoint and go back to sleep.
+ await platform.removeCrawlerCheckpoint(checkpoint);
+ continue;
+ }
+
+ // Convert the plain JSON events into Matrix events so they get
+ // decrypted if necessary.
+ const matrixEvents = res.chunk.map(eventMapper);
+ let stateEvents = [];
+ if (res.state !== undefined) {
+ stateEvents = res.state.map(eventMapper);
+ }
+
+ const profiles = {};
+
+ stateEvents.forEach(ev => {
+ if (ev.event.content &&
+ ev.event.content.membership === "join") {
+ profiles[ev.event.sender] = {
+ displayname: ev.event.content.displayname,
+ avatar_url: ev.event.content.avatar_url,
+ };
+ }
+ });
+
+ const decryptionPromises = [];
+
+ matrixEvents.forEach(ev => {
+ if (ev.isBeingDecrypted() || ev.isDecryptionFailure()) {
+ // TODO the decryption promise is a private property, this
+ // should either be made public or we should convert the
+ // event that gets fired when decryption is done into a
+ // promise using the once event emitter method:
+ // https://nodejs.org/api/events.html#events_events_once_emitter_name
+ decryptionPromises.push(ev._decryptionPromise);
+ }
+ });
+
+ // Let us wait for all the events to get decrypted.
+ await Promise.all(decryptionPromises);
+
+ // We filter out events for which decryption failed, are redacted
+ // or aren't of a type that we know how to index.
+ const isValidEvent = (value) => {
+ return ([
+ "m.room.message",
+ "m.room.name",
+ "m.room.topic",
+ ].indexOf(value.getType()) >= 0
+ && !value.isRedacted() && !value.isDecryptionFailure()
+ );
+ // TODO do we need to check if the event has all the valid
+ // attributes?
+ };
+
+ // TODO if there ar no events at this point we're missing a lot
+ // decryption keys, do we wan't to retry this checkpoint at a later
+ // stage?
+ const filteredEvents = matrixEvents.filter(isValidEvent);
+
+ // Let us convert the events back into a format that Seshat can
+ // consume.
+ const events = filteredEvents.map((ev) => {
+ const jsonEvent = ev.toJSON();
+
+ let e;
+ if (ev.isEncrypted()) e = jsonEvent.decrypted;
+ else e = jsonEvent;
+
+ let profile = {};
+ if (e.sender in profiles) profile = profiles[e.sender];
+ const object = {
+ event: e,
+ profile: profile,
+ };
+ return object;
+ });
+
+ // Create a new checkpoint so we can continue crawling the room for
+ // messages.
+ const newCheckpoint = {
+ roomId: checkpoint.roomId,
+ token: res.end,
+ fullCrawl: checkpoint.fullCrawl,
+ direction: checkpoint.direction,
+ };
+
+ console.log(
+ "Seshat: Crawled room",
+ client.getRoom(checkpoint.roomId).name,
+ "and fetched", events.length, "events.",
+ );
+
+ try {
+ const eventsAlreadyAdded = await platform.addHistoricEvents(
+ events, newCheckpoint, checkpoint);
+ // If all events were already indexed we assume that we catched
+ // up with our index and don't need to crawl the room further.
+ // Let us delete the checkpoint in that case, otherwise push
+ // the new checkpoint to be used by the crawler.
+ if (eventsAlreadyAdded === true && newCheckpoint.fullCrawl !== true) {
+ console.log("Seshat: Checkpoint had already all events",
+ "added, stopping the crawl", checkpoint);
+ await platform.removeCrawlerCheckpoint(newCheckpoint);
+ } else {
+ this.crawlerChekpoints.push(newCheckpoint);
+ }
+ } catch (e) {
+ console.log("Seshat: Error durring a crawl", e);
+ // An error occured, put the checkpoint back so we
+ // can retry.
+ this.crawlerChekpoints.push(checkpoint);
+ }
+ }
+
+ console.log("Seshat: Stopping crawler function");
+ }
+
+ async addCheckpointForLimitedRoom(roomId) {
+ const platform = PlatformPeg.get();
+ if (!platform.supportsEventIndexing()) return;
+ if (!MatrixClientPeg.get().isRoomEncrypted(roomId)) return;
+
+ const client = MatrixClientPeg.get();
+ const room = client.getRoom(roomId);
+
+ if (room === null) return;
+
+ const timeline = room.getLiveTimeline();
+ const token = timeline.getPaginationToken("b");
+
+ const backwardsCheckpoint = {
+ roomId: room.roomId,
+ token: token,
+ fullCrawl: false,
+ direction: "b",
+ };
+
+ const forwardsCheckpoint = {
+ roomId: room.roomId,
+ token: token,
+ fullCrawl: false,
+ direction: "f",
+ };
+
+ console.log("Seshat: Added checkpoint because of a limited timeline",
+ backwardsCheckpoint, forwardsCheckpoint);
+
+ await platform.addCrawlerCheckpoint(backwardsCheckpoint);
+ await platform.addCrawlerCheckpoint(forwardsCheckpoint);
+
+ this.crawlerChekpoints.push(backwardsCheckpoint);
+ this.crawlerChekpoints.push(forwardsCheckpoint);
+ }
+
+ async deleteEventIndex() {
+ if (platform.supportsEventIndexing()) {
+ console.log("Seshat: Deleting event index.");
+ this.crawlerRef.cancel();
+ await platform.deleteEventIndex();
+ }
+ }
+
+ startCrawler() {
+ const crawlerHandle = {};
+ this.crawlerFunc(crawlerHandle);
+ this.crawlerRef = crawlerHandle;
+ }
+
+ stopCrawler() {
+ this._crawlerRef.cancel();
+ this._crawlerRef = null;
+ }
+}
diff --git a/src/Lifecycle.js b/src/Lifecycle.js
index 7490c5d464..0b44f2ed84 100644
--- a/src/Lifecycle.js
+++ b/src/Lifecycle.js
@@ -20,6 +20,7 @@ import Promise from 'bluebird';
import Matrix from 'matrix-js-sdk';
import MatrixClientPeg from './MatrixClientPeg';
+import EventIndexPeg from './EventIndexPeg';
import createMatrixClient from './utils/createMatrixClient';
import Analytics from './Analytics';
import Notifier from './Notifier';
@@ -587,6 +588,7 @@ async function startMatrixClient(startSyncing=true) {
if (startSyncing) {
await MatrixClientPeg.start();
+ await EventIndexPeg.init();
} else {
console.warn("Caller requested only auxiliary services be started");
await MatrixClientPeg.assign();
diff --git a/src/MatrixClientPeg.js b/src/MatrixClientPeg.js
index 5c5ee6e4ec..6c5b465bb0 100644
--- a/src/MatrixClientPeg.js
+++ b/src/MatrixClientPeg.js
@@ -31,6 +31,7 @@ import MatrixClientBackedSettingsHandler from "./settings/handlers/MatrixClientB
import * as StorageManager from './utils/StorageManager';
import IdentityAuthClient from './IdentityAuthClient';
import PlatformPeg from "./PlatformPeg";
+import EventIndexPeg from "./EventIndexPeg";
interface MatrixClientCreds {
homeserverUrl: string,
@@ -223,9 +224,6 @@ class MatrixClientPeg {
this.matrixClient = createMatrixClient(opts);
- const platform = PlatformPeg.get();
- if (platform.supportsEventIndexing()) platform.initEventIndex(creds.userId);
-
// we're going to add eventlisteners for each matrix event tile, so the
// potential number of event listeners is quite high.
this.matrixClient.setMaxListeners(500);
diff --git a/src/components/structures/MatrixChat.js b/src/components/structures/MatrixChat.js
index 402790df98..d006247151 100644
--- a/src/components/structures/MatrixChat.js
+++ b/src/components/structures/MatrixChat.js
@@ -31,6 +31,7 @@ import Analytics from "../../Analytics";
import { DecryptionFailureTracker } from "../../DecryptionFailureTracker";
import MatrixClientPeg from "../../MatrixClientPeg";
import PlatformPeg from "../../PlatformPeg";
+import EventIndexPeg from "../../EventIndexPeg";
import SdkConfig from "../../SdkConfig";
import * as RoomListSorter from "../../RoomListSorter";
import dis from "../../dispatcher";
@@ -1224,12 +1225,6 @@ export default createReactClass({
_onLoggedOut: async function() {
const platform = PlatformPeg.get();
- if (platform.supportsEventIndexing()) {
- console.log("Seshat: Deleting event index.");
- this.crawlerRef.cancel();
- await platform.deleteEventIndex();
- }
-
this.notifyNewScreen('login');
this.setStateForNewView({
view: VIEWS.LOGIN,
@@ -1270,8 +1265,6 @@ export default createReactClass({
// to do the first sync
this.firstSyncComplete = false;
this.firstSyncPromise = Promise.defer();
- this.crawlerChekpoints = [];
- this.liveEventsForIndex = new Set();
const cli = MatrixClientPeg.get();
const IncomingSasDialog = sdk.getComponent('views.dialogs.IncomingSasDialog');
@@ -1284,7 +1277,10 @@ export default createReactClass({
cli.setCanResetTimelineCallback(async function(roomId) {
console.log("Request to reset timeline in room ", roomId, " viewing:", self.state.currentRoomId);
// TODO is there a better place to plug this in
- await self.addCheckpointForLimitedRoom(roomId);
+ const eventIndex = EventIndexPeg.get();
+ if (eventIndex !== null) {
+ await eventIndex.addCheckpointForLimitedRoom(roomId);
+ }
if (roomId !== self.state.currentRoomId) {
// It is safe to remove events from rooms we are not viewing.
@@ -1301,80 +1297,21 @@ export default createReactClass({
});
cli.on('sync', async (state, prevState, data) => {
- const platform = PlatformPeg.get();
- if (!platform.supportsEventIndexing()) return;
+ const eventIndex = EventIndexPeg.get();
+ if (eventIndex === null) return;
+ await eventIndex.onSync(state, prevState, data);
+ });
- if (prevState === null && state === "PREPARED") {
- /// Load our stored checkpoints, if any.
- self.crawlerChekpoints = await platform.loadCheckpoints();
- console.log("Seshat: Loaded checkpoints",
- self.crawlerChekpoints);
- return;
- }
+ cli.on("Room.timeline", async (ev, room, toStartOfTimeline, removed, data) => {
+ const eventIndex = EventIndexPeg.get();
+ if (eventIndex === null) return;
+ await eventIndex.onRoomTimeline(ev, room, toStartOfTimeline, removed, data);
+ });
- if (prevState === "PREPARED" && state === "SYNCING") {
- const addInitialCheckpoints = async () => {
- const client = MatrixClientPeg.get();
- const rooms = client.getRooms();
-
- const isRoomEncrypted = (room) => {
- return client.isRoomEncrypted(room.roomId);
- };
-
- // We only care to crawl the encrypted rooms, non-encrytped
- // rooms can use the search provided by the Homeserver.
- const encryptedRooms = rooms.filter(isRoomEncrypted);
-
- console.log("Seshat: Adding initial crawler checkpoints");
-
- // Gather the prev_batch tokens and create checkpoints for
- // our message crawler.
- await Promise.all(encryptedRooms.map(async (room) => {
- const timeline = room.getLiveTimeline();
- const token = timeline.getPaginationToken("b");
-
- console.log("Seshat: Got token for indexer",
- room.roomId, token);
-
- const backCheckpoint = {
- roomId: room.roomId,
- token: token,
- direction: "b",
- };
-
- const forwardCheckpoint = {
- roomId: room.roomId,
- token: token,
- direction: "f",
- };
-
- await platform.addCrawlerCheckpoint(backCheckpoint);
- await platform.addCrawlerCheckpoint(forwardCheckpoint);
- self.crawlerChekpoints.push(backCheckpoint);
- self.crawlerChekpoints.push(forwardCheckpoint);
- }));
- };
-
- // If our indexer is empty we're most likely running Riot the
- // first time with indexing support or running it with an
- // initial sync. Add checkpoints to crawl our encrypted rooms.
- const eventIndexWasEmpty = await platform.isEventIndexEmpty();
- if (eventIndexWasEmpty) await addInitialCheckpoints();
-
- // Start our crawler.
- const crawlerHandle = {};
- self.crawlerFunc(crawlerHandle);
- self.crawlerRef = crawlerHandle;
- return;
- }
-
- if (prevState === "SYNCING" && state === "SYNCING") {
- // A sync was done, presumably we queued up some live events,
- // commit them now.
- console.log("Seshat: Committing events");
- await platform.commitLiveEvents();
- return;
- }
+ cli.on("Event.decrypted", async (ev, err) => {
+ const eventIndex = EventIndexPeg.get();
+ if (eventIndex === null) return;
+ await eventIndex.onEventDecrypted(ev, err);
});
cli.on('sync', function(state, prevState, data) {
@@ -1459,44 +1396,6 @@ export default createReactClass({
}, null, true);
});
- cli.on("Room.timeline", async (ev, room, toStartOfTimeline, removed, data) => {
- const platform = PlatformPeg.get();
- if (!platform.supportsEventIndexing()) return;
-
- // We only index encrypted rooms locally.
- if (!MatrixClientPeg.get().isRoomEncrypted(room.roomId)) return;
-
- // If it isn't a live event or if it's redacted there's nothing to
- // do.
- if (toStartOfTimeline || !data || !data.liveEvent
- || ev.isRedacted()) {
- return;
- }
-
- // If the event is not yet decrypted mark it for the
- // Event.decrypted callback.
- if (ev.isBeingDecrypted()) {
- const eventId = ev.getId();
- self.liveEventsForIndex.add(eventId);
- } else {
- // If the event is decrypted or is unencrypted add it to the
- // index now.
- await self.addLiveEventToIndex(ev);
- }
- });
-
- cli.on("Event.decrypted", async (ev, err) => {
- const platform = PlatformPeg.get();
- if (!platform.supportsEventIndexing()) return;
-
- const eventId = ev.getId();
-
- // If the event isn't in our live event set, ignore it.
- if (!self.liveEventsForIndex.delete(eventId)) return;
- if (err) return;
- await self.addLiveEventToIndex(ev);
- });
-
cli.on("accountData", function(ev) {
if (ev.getType() === 'im.vector.web.settings') {
if (ev.getContent() && ev.getContent().theme) {
@@ -2058,238 +1957,4 @@ export default createReactClass({
{view}
;
},
-
- async addLiveEventToIndex(ev) {
- const platform = PlatformPeg.get();
- if (!platform.supportsEventIndexing()) return;
-
- if (["m.room.message", "m.room.name", "m.room.topic"]
- .indexOf(ev.getType()) == -1) {
- return;
- }
-
- const e = ev.toJSON().decrypted;
- const profile = {
- displayname: ev.sender.rawDisplayName,
- avatar_url: ev.sender.getMxcAvatarUrl(),
- };
-
- platform.addEventToIndex(e, profile);
- },
-
- async crawlerFunc(handle) {
- // TODO either put this in a better place or find a library provided
- // method that does this.
- const sleep = async (ms) => {
- return new Promise(resolve => setTimeout(resolve, ms));
- };
-
- let cancelled = false;
-
- console.log("Seshat: Started crawler function");
-
- const client = MatrixClientPeg.get();
- const platform = PlatformPeg.get();
-
- handle.cancel = () => {
- cancelled = true;
- };
-
- while (!cancelled) {
- // This is a low priority task and we don't want to spam our
- // Homeserver with /messages requests so we set a hefty 3s timeout
- // here.
- await sleep(3000);
-
- console.log("Seshat: Running the crawler loop.");
-
- if (cancelled) {
- console.log("Seshat: Cancelling the crawler.");
- break;
- }
-
- const checkpoint = this.crawlerChekpoints.shift();
-
- /// There is no checkpoint available currently, one may appear if
- // a sync with limited room timelines happens, so go back to sleep.
- if (checkpoint === undefined) {
- continue;
- }
-
- console.log("Seshat: crawling using checkpoint", checkpoint);
-
- // We have a checkpoint, let us fetch some messages, again, very
- // conservatively to not bother our Homeserver too much.
- const eventMapper = client.getEventMapper();
- // TODO we need to ensure to use member lazy loading with this
- // request so we get the correct profiles.
- let res;
-
- try {
- res = await client._createMessagesRequest(
- checkpoint.roomId, checkpoint.token, 100,
- checkpoint.direction);
- } catch (e) {
- console.log("Seshat: Error crawling events:", e);
- this.crawlerChekpoints.push(checkpoint);
- continue
- }
-
- if (res.chunk.length === 0) {
- console.log("Seshat: Done with the checkpoint", checkpoint);
- // We got to the start/end of our timeline, lets just
- // delete our checkpoint and go back to sleep.
- await platform.removeCrawlerCheckpoint(checkpoint);
- continue;
- }
-
- // Convert the plain JSON events into Matrix events so they get
- // decrypted if necessary.
- const matrixEvents = res.chunk.map(eventMapper);
- let stateEvents = [];
- if (res.state !== undefined) {
- stateEvents = res.state.map(eventMapper);
- }
-
- const profiles = {};
-
- stateEvents.forEach(ev => {
- if (ev.event.content &&
- ev.event.content.membership === "join") {
- profiles[ev.event.sender] = {
- displayname: ev.event.content.displayname,
- avatar_url: ev.event.content.avatar_url,
- };
- }
- });
-
- const decryptionPromises = [];
-
- matrixEvents.forEach(ev => {
- if (ev.isBeingDecrypted() || ev.isDecryptionFailure()) {
- // TODO the decryption promise is a private property, this
- // should either be made public or we should convert the
- // event that gets fired when decryption is done into a
- // promise using the once event emitter method:
- // https://nodejs.org/api/events.html#events_events_once_emitter_name
- decryptionPromises.push(ev._decryptionPromise);
- }
- });
-
- // Let us wait for all the events to get decrypted.
- await Promise.all(decryptionPromises);
-
- // We filter out events for which decryption failed, are redacted
- // or aren't of a type that we know how to index.
- const isValidEvent = (value) => {
- return ([
- "m.room.message",
- "m.room.name",
- "m.room.topic",
- ].indexOf(value.getType()) >= 0
- && !value.isRedacted() && !value.isDecryptionFailure()
- );
- // TODO do we need to check if the event has all the valid
- // attributes?
- };
-
- // TODO if there ar no events at this point we're missing a lot
- // decryption keys, do we wan't to retry this checkpoint at a later
- // stage?
- const filteredEvents = matrixEvents.filter(isValidEvent);
-
- // Let us convert the events back into a format that Seshat can
- // consume.
- const events = filteredEvents.map((ev) => {
- const jsonEvent = ev.toJSON();
-
- let e;
- if (ev.isEncrypted()) e = jsonEvent.decrypted;
- else e = jsonEvent;
-
- let profile = {};
- if (e.sender in profiles) profile = profiles[e.sender];
- const object = {
- event: e,
- profile: profile,
- };
- return object;
- });
-
- // Create a new checkpoint so we can continue crawling the room for
- // messages.
- const newCheckpoint = {
- roomId: checkpoint.roomId,
- token: res.end,
- fullCrawl: checkpoint.fullCrawl,
- direction: checkpoint.direction,
- };
-
- console.log(
- "Seshat: Crawled room",
- client.getRoom(checkpoint.roomId).name,
- "and fetched", events.length, "events.",
- );
-
- try {
- const eventsAlreadyAdded = await platform.addHistoricEvents(
- events, newCheckpoint, checkpoint);
- // If all events were already indexed we assume that we catched
- // up with our index and don't need to crawl the room further.
- // Let us delete the checkpoint in that case, otherwise push
- // the new checkpoint to be used by the crawler.
- if (eventsAlreadyAdded === true && newCheckpoint.fullCrawl !== true) {
- console.log("Seshat: Checkpoint had already all events",
- "added, stopping the crawl", checkpoint);
- await platform.removeCrawlerCheckpoint(newCheckpoint);
- } else {
- this.crawlerChekpoints.push(newCheckpoint);
- }
- } catch (e) {
- console.log("Seshat: Error durring a crawl", e);
- // An error occured, put the checkpoint back so we
- // can retry.
- this.crawlerChekpoints.push(checkpoint);
- }
- }
-
- console.log("Seshat: Stopping crawler function");
- },
-
- async addCheckpointForLimitedRoom(roomId) {
- const platform = PlatformPeg.get();
- if (!platform.supportsEventIndexing()) return;
- if (!MatrixClientPeg.get().isRoomEncrypted(roomId)) return;
-
- const client = MatrixClientPeg.get();
- const room = client.getRoom(roomId);
-
- if (room === null) return;
-
- const timeline = room.getLiveTimeline();
- const token = timeline.getPaginationToken("b");
-
- const backwardsCheckpoint = {
- roomId: room.roomId,
- token: token,
- fullCrawl: false,
- direction: "b",
- };
-
- const forwardsCheckpoint = {
- roomId: room.roomId,
- token: token,
- fullCrawl: false,
- direction: "f",
- };
-
- console.log("Seshat: Added checkpoint because of a limited timeline",
- backwardsCheckpoint, forwardsCheckpoint);
-
- await platform.addCrawlerCheckpoint(backwardsCheckpoint);
- await platform.addCrawlerCheckpoint(forwardsCheckpoint);
-
- this.crawlerChekpoints.push(backwardsCheckpoint);
- this.crawlerChekpoints.push(forwardsCheckpoint);
- },
});
From c3df2f941dccf5e135ac48408fccc25cf3c5da30 Mon Sep 17 00:00:00 2001
From: Michael Telatynski <7t3chguy@gmail.com>
Date: Tue, 12 Nov 2019 12:30:05 +0000
Subject: [PATCH 060/334] attach promise utils atop bluebird
---
src/utils/promise.js | 3 +++
1 file changed, 3 insertions(+)
diff --git a/src/utils/promise.js b/src/utils/promise.js
index dd10f7fdd7..f7a2e7c3e7 100644
--- a/src/utils/promise.js
+++ b/src/utils/promise.js
@@ -14,6 +14,9 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
+// This is only here to allow access to methods like done for the time being
+import Promise from "bluebird";
+
// @flow
// Returns a promise which resolves with a given value after the given number of ms
From 3f2b77189e31c0cb3617d78105987190f10502a9 Mon Sep 17 00:00:00 2001
From: "J. Ryan Stinnett"
Date: Tue, 12 Nov 2019 13:29:01 +0000
Subject: [PATCH 061/334] Simplify dispatch blocks
---
src/stores/RoomViewStore.js | 75 +++++++++++++++++--------------------
1 file changed, 34 insertions(+), 41 deletions(-)
diff --git a/src/stores/RoomViewStore.js b/src/stores/RoomViewStore.js
index e860ed8b24..6a405124f4 100644
--- a/src/stores/RoomViewStore.js
+++ b/src/stores/RoomViewStore.js
@@ -179,50 +179,43 @@ class RoomViewStore extends Store {
} else if (payload.room_alias) {
// Try the room alias to room ID navigation cache first to avoid
// blocking room navigation on the homeserver.
- const roomId = getCachedRoomIDForAlias(payload.room_alias);
- if (roomId) {
- dis.dispatch({
- action: 'view_room',
- room_id: roomId,
- event_id: payload.event_id,
- highlighted: payload.highlighted,
- room_alias: payload.room_alias,
- auto_join: payload.auto_join,
- oob_data: payload.oob_data,
+ let roomId = getCachedRoomIDForAlias(payload.room_alias);
+ if (!roomId) {
+ // Room alias cache miss, so let's ask the homeserver. Resolve the alias
+ // and then do a second dispatch with the room ID acquired.
+ this._setState({
+ roomId: null,
+ initialEventId: null,
+ initialEventPixelOffset: null,
+ isInitialEventHighlighted: null,
+ roomAlias: payload.room_alias,
+ roomLoading: true,
+ roomLoadError: null,
});
- return;
+ try {
+ const result = await MatrixClientPeg.get().getRoomIdForAlias(payload.room_alias);
+ storeRoomAliasInCache(payload.room_alias, result.room_id);
+ roomId = result.room_id;
+ } catch (err) {
+ dis.dispatch({
+ action: 'view_room_error',
+ room_id: null,
+ room_alias: payload.room_alias,
+ err,
+ });
+ return;
+ }
}
- // Room alias cache miss, so let's ask the homeserver.
- // Resolve the alias and then do a second dispatch with the room ID acquired
- this._setState({
- roomId: null,
- initialEventId: null,
- initialEventPixelOffset: null,
- isInitialEventHighlighted: null,
- roomAlias: payload.room_alias,
- roomLoading: true,
- roomLoadError: null,
+
+ dis.dispatch({
+ action: 'view_room',
+ room_id: roomId,
+ event_id: payload.event_id,
+ highlighted: payload.highlighted,
+ room_alias: payload.room_alias,
+ auto_join: payload.auto_join,
+ oob_data: payload.oob_data,
});
- try {
- const result = await MatrixClientPeg.get().getRoomIdForAlias(payload.room_alias);
- storeRoomAliasInCache(payload.room_alias, result.room_id);
- dis.dispatch({
- action: 'view_room',
- room_id: result.room_id,
- event_id: payload.event_id,
- highlighted: payload.highlighted,
- room_alias: payload.room_alias,
- auto_join: payload.auto_join,
- oob_data: payload.oob_data,
- });
- } catch (err) {
- dis.dispatch({
- action: 'view_room_error',
- room_id: null,
- room_alias: payload.room_alias,
- err,
- });
- }
}
}
From e296fd05c0048e95a98c8777209ecb2990d787f0 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Damir=20Jeli=C4=87?=
Date: Tue, 12 Nov 2019 15:39:26 +0100
Subject: [PATCH 062/334] RoomView: Move the search logic into a separate
module.
---
src/EventIndexing.js | 5 +
src/Searching.js | 137 ++++++++++++++++++++++++++
src/components/structures/RoomView.js | 125 +----------------------
3 files changed, 147 insertions(+), 120 deletions(-)
create mode 100644 src/Searching.js
diff --git a/src/EventIndexing.js b/src/EventIndexing.js
index 21ee8f3da6..29f9c48842 100644
--- a/src/EventIndexing.js
+++ b/src/EventIndexing.js
@@ -401,4 +401,9 @@ export default class EventIndexer {
this._crawlerRef.cancel();
this._crawlerRef = null;
}
+
+ async search(searchArgs) {
+ const platform = PlatformPeg.get();
+ return platform.searchEventIndex(searchArgs)
+ }
}
diff --git a/src/Searching.js b/src/Searching.js
new file mode 100644
index 0000000000..cd06d9bc67
--- /dev/null
+++ b/src/Searching.js
@@ -0,0 +1,137 @@
+/*
+Copyright 2019 New Vector Ltd
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+import EventIndexPeg from "./EventIndexPeg";
+import MatrixClientPeg from "./MatrixClientPeg";
+
+function serverSideSearch(term, roomId = undefined) {
+ let filter;
+ if (roomId !== undefined) {
+ filter = {
+ // XXX: it's unintuitive that the filter for searching doesn't have the same shape as the v2 filter API :(
+ rooms: [roomId],
+ };
+ }
+
+ let searchPromise = MatrixClientPeg.get().searchRoomEvents({
+ filter: filter,
+ term: term,
+ });
+
+ return searchPromise;
+}
+
+function eventIndexSearch(term, roomId = undefined) {
+ const combinedSearchFunc = async (searchTerm) => {
+ // Create two promises, one for the local search, one for the
+ // server-side search.
+ const client = MatrixClientPeg.get();
+ const serverSidePromise = serverSideSearch(searchTerm);
+ const localPromise = localSearchFunc(searchTerm);
+
+ // Wait for both promises to resolve.
+ await Promise.all([serverSidePromise, localPromise]);
+
+ // Get both search results.
+ const localResult = await localPromise;
+ const serverSideResult = await serverSidePromise;
+
+ // Combine the search results into one result.
+ const result = {};
+
+ // Our localResult and serverSideResult are both ordered by
+ // recency separetly, when we combine them the order might not
+ // be the right one so we need to sort them.
+ const compare = (a, b) => {
+ const aEvent = a.context.getEvent().event;
+ const bEvent = b.context.getEvent().event;
+
+ if (aEvent.origin_server_ts >
+ bEvent.origin_server_ts) return -1;
+ if (aEvent.origin_server_ts <
+ bEvent.origin_server_ts) return 1;
+ return 0;
+ };
+
+ result.count = localResult.count + serverSideResult.count;
+ result.results = localResult.results.concat(
+ serverSideResult.results).sort(compare);
+ result.highlights = localResult.highlights.concat(
+ serverSideResult.highlights);
+
+ return result;
+ };
+
+ const localSearchFunc = async (searchTerm, roomId = undefined) => {
+ const searchArgs = {
+ search_term: searchTerm,
+ before_limit: 1,
+ after_limit: 1,
+ order_by_recency: true,
+ };
+
+ if (roomId !== undefined) {
+ searchArgs.room_id = roomId;
+ }
+
+ const eventIndex = EventIndexPeg.get();
+
+ const localResult = await eventIndex.search(searchArgs);
+
+ const response = {
+ search_categories: {
+ room_events: localResult,
+ },
+ };
+
+ const emptyResult = {
+ results: [],
+ highlights: [],
+ };
+
+ const result = MatrixClientPeg.get()._processRoomEventsSearch(
+ emptyResult, response);
+
+ return result;
+ };
+
+ let searchPromise;
+
+ if (roomId !== undefined) {
+ if (MatrixClientPeg.get().isRoomEncrypted(roomId)) {
+ // The search is for a single encrypted room, use our local
+ // search method.
+ searchPromise = localSearchFunc(term, roomId);
+ } else {
+ // The search is for a single non-encrypted room, use the
+ // server-side search.
+ searchPromise = serverSideSearch(term, roomId);
+ }
+ } else {
+ // Search across all rooms, combine a server side search and a
+ // local search.
+ searchPromise = combinedSearchFunc(term);
+ }
+
+ return searchPromise
+}
+
+export default function eventSearch(term, roomId = undefined) {
+ const eventIndex = EventIndexPeg.get();
+
+ if (eventIndex === null) return serverSideSearch(term, roomId);
+ else return eventIndexSearch(term, roomId);
+}
diff --git a/src/components/structures/RoomView.js b/src/components/structures/RoomView.js
index 1b44335f51..9fe54ad164 100644
--- a/src/components/structures/RoomView.js
+++ b/src/components/structures/RoomView.js
@@ -34,7 +34,6 @@ import { _t } from '../../languageHandler';
import {RoomPermalinkCreator} from '../../utils/permalinks/Permalinks';
import MatrixClientPeg from '../../MatrixClientPeg';
-import PlatformPeg from "../../PlatformPeg";
import ContentMessages from '../../ContentMessages';
import Modal from '../../Modal';
import sdk from '../../index';
@@ -44,6 +43,7 @@ import Tinter from '../../Tinter';
import rate_limited_func from '../../ratelimitedfunc';
import ObjectUtils from '../../ObjectUtils';
import * as Rooms from '../../Rooms';
+import eventSearch from '../../Searching';
import { KeyCode, isOnlyCtrlOrCmdKeyEvent } from '../../Keyboard';
@@ -1130,127 +1130,12 @@ module.exports = createReactClass({
// todo: should cancel any previous search requests.
this.searchId = new Date().getTime();
- let filter;
- if (scope === "Room") {
- filter = {
- // XXX: it's unintuitive that the filter for searching doesn't have the same shape as the v2 filter API :(
- rooms: [
- this.state.room.roomId,
- ],
- };
- }
+ let roomId;
+ if (scope === "Room") roomId = this.state.room.roomId,
debuglog("sending search request");
- const platform = PlatformPeg.get();
-
- if (platform.supportsEventIndexing()) {
- const combinedSearchFunc = async (searchTerm) => {
- // Create two promises, one for the local search, one for the
- // server-side search.
- const client = MatrixClientPeg.get();
- const serverSidePromise = client.searchRoomEvents({
- term: searchTerm,
- });
- const localPromise = localSearchFunc(searchTerm);
-
- // Wait for both promises to resolve.
- await Promise.all([serverSidePromise, localPromise]);
-
- // Get both search results.
- const localResult = await localPromise;
- const serverSideResult = await serverSidePromise;
-
- // Combine the search results into one result.
- const result = {};
-
- // Our localResult and serverSideResult are both ordered by
- // recency separetly, when we combine them the order might not
- // be the right one so we need to sort them.
- const compare = (a, b) => {
- const aEvent = a.context.getEvent().event;
- const bEvent = b.context.getEvent().event;
-
- if (aEvent.origin_server_ts >
- bEvent.origin_server_ts) return -1;
- if (aEvent.origin_server_ts <
- bEvent.origin_server_ts) return 1;
- return 0;
- };
-
- result.count = localResult.count + serverSideResult.count;
- result.results = localResult.results.concat(
- serverSideResult.results).sort(compare);
- result.highlights = localResult.highlights.concat(
- serverSideResult.highlights);
-
- return result;
- };
-
- const localSearchFunc = async (searchTerm, roomId = undefined) => {
- const searchArgs = {
- search_term: searchTerm,
- before_limit: 1,
- after_limit: 1,
- order_by_recency: true,
- };
-
- if (roomId !== undefined) {
- searchArgs.room_id = roomId;
- }
-
- const localResult = await platform.searchEventIndex(
- searchArgs);
-
- const response = {
- search_categories: {
- room_events: localResult,
- },
- };
-
- const emptyResult = {
- results: [],
- highlights: [],
- };
-
- // TODO is there a better way to convert our result into what
- // is expected by the handler method.
- const result = MatrixClientPeg.get()._processRoomEventsSearch(
- emptyResult, response);
-
- return result;
- };
-
- let searchPromise;
-
- if (scope === "Room") {
- const roomId = this.state.room.roomId;
-
- if (MatrixClientPeg.get().isRoomEncrypted(roomId)) {
- // The search is for a single encrypted room, use our local
- // search method.
- searchPromise = localSearchFunc(term, roomId);
- } else {
- // The search is for a single non-encrypted room, use the
- // server-side search.
- searchPromise = MatrixClientPeg.get().searchRoomEvents({
- filter: filter,
- term: term,
- });
- }
- } else {
- // Search across all rooms, combine a server side search and a
- // local search.
- searchPromise = combinedSearchFunc(term);
- }
-
- this._handleSearchResult(searchPromise).done();
- } else {
- const searchPromise = MatrixClientPeg.get().searchRoomEvents({
- filter: filter,
- term: term,
- });
- this._handleSearchResult(searchPromise).done();
- }
+ const searchPromise = eventSearch(term, roomId);
+ this._handleSearchResult(searchPromise).done();
},
_handleSearchResult: function(searchPromise) {
From d911055f5d8016ebd2f036d68a9c1ee3f7343af2 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Damir=20Jeli=C4=87?=
Date: Tue, 12 Nov 2019 15:39:54 +0100
Subject: [PATCH 063/334] MatrixChat: Move the indexing limited room logic to a
different event.
---
src/components/structures/MatrixChat.js | 12 +++++++-----
1 file changed, 7 insertions(+), 5 deletions(-)
diff --git a/src/components/structures/MatrixChat.js b/src/components/structures/MatrixChat.js
index d006247151..0d3d5abd55 100644
--- a/src/components/structures/MatrixChat.js
+++ b/src/components/structures/MatrixChat.js
@@ -1276,11 +1276,6 @@ export default createReactClass({
// https://github.com/vector-im/riot-web/issues/3307#issuecomment-282895568
cli.setCanResetTimelineCallback(async function(roomId) {
console.log("Request to reset timeline in room ", roomId, " viewing:", self.state.currentRoomId);
- // TODO is there a better place to plug this in
- const eventIndex = EventIndexPeg.get();
- if (eventIndex !== null) {
- await eventIndex.addCheckpointForLimitedRoom(roomId);
- }
if (roomId !== self.state.currentRoomId) {
// It is safe to remove events from rooms we are not viewing.
@@ -1314,6 +1309,13 @@ export default createReactClass({
await eventIndex.onEventDecrypted(ev, err);
});
+ cli.on("Room.timelineReset", async (room, timelineSet, resetAllTimelines) => {
+ const eventIndex = EventIndexPeg.get();
+ if (eventIndex === null) return;
+ if (resetAllTimelines === true) return;
+ await eventIndex.addCheckpointForLimitedRoom(roomId);
+ });
+
cli.on('sync', function(state, prevState, data) {
// LifecycleStore and others cannot directly subscribe to matrix client for
// events because flux only allows store state changes during flux dispatches.
From ecbc47c5488bf60b5cc068b09d4a51672b9a5c14 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Damir=20Jeli=C4=87?=
Date: Tue, 12 Nov 2019 15:40:49 +0100
Subject: [PATCH 064/334] EventIndexing: Rename the stop method.
---
src/EventIndexPeg.js | 9 +++++----
src/EventIndexing.js | 3 ++-
2 files changed, 7 insertions(+), 5 deletions(-)
diff --git a/src/EventIndexPeg.js b/src/EventIndexPeg.js
index 794450e4b7..86fb889c7a 100644
--- a/src/EventIndexPeg.js
+++ b/src/EventIndexPeg.js
@@ -57,13 +57,14 @@ class EventIndexPeg {
return true
}
- async stop() {
- if (this.index == null) return;
- index.stopCrawler();
+ stop() {
+ if (this.index === null) return;
+ index.stop();
+ this.index = null;
}
async deleteEventIndex() {
- if (this.index == null) return;
+ if (this.index === null) return;
index.deleteEventIndex();
}
}
diff --git a/src/EventIndexing.js b/src/EventIndexing.js
index 29f9c48842..92a3a5a1f8 100644
--- a/src/EventIndexing.js
+++ b/src/EventIndexing.js
@@ -34,6 +34,7 @@ export default class EventIndexer {
const platform = PlatformPeg.get();
if (!platform.supportsEventIndexing()) return false;
platform.initEventIndex(userId);
+ return true;
}
async onSync(state, prevState, data) {
@@ -397,7 +398,7 @@ export default class EventIndexer {
this.crawlerRef = crawlerHandle;
}
- stopCrawler() {
+ stop() {
this._crawlerRef.cancel();
this._crawlerRef = null;
}
From d69eb78b661764e9241d14a0c08dff23906245c1 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Damir=20Jeli=C4=87?=
Date: Tue, 12 Nov 2019 15:41:14 +0100
Subject: [PATCH 065/334] EventIndexing: Add a missing platform getting.
---
src/EventIndexing.js | 1 +
1 file changed, 1 insertion(+)
diff --git a/src/EventIndexing.js b/src/EventIndexing.js
index 92a3a5a1f8..ebd2ffe983 100644
--- a/src/EventIndexing.js
+++ b/src/EventIndexing.js
@@ -385,6 +385,7 @@ export default class EventIndexer {
}
async deleteEventIndex() {
+ const platform = PlatformPeg.get();
if (platform.supportsEventIndexing()) {
console.log("Seshat: Deleting event index.");
this.crawlerRef.cancel();
From 3502454c615f1a7bc74588f3512661278604d2d7 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Damir=20Jeli=C4=87?=
Date: Tue, 12 Nov 2019 15:58:38 +0100
Subject: [PATCH 066/334] LifeCycle: Stop the crawler and delete the index when
whe log out.
---
src/Lifecycle.js | 2 ++
1 file changed, 2 insertions(+)
diff --git a/src/Lifecycle.js b/src/Lifecycle.js
index 0b44f2ed84..7360cd3231 100644
--- a/src/Lifecycle.js
+++ b/src/Lifecycle.js
@@ -613,6 +613,7 @@ export function onLoggedOut() {
// so that React components unmount first. This avoids React soft crashes
// that can occur when components try to use a null client.
dis.dispatch({action: 'on_logged_out'});
+ EventIndexPeg.deleteEventIndex().done();
stopMatrixClient();
_clearStorage().done();
}
@@ -648,6 +649,7 @@ export function stopMatrixClient(unsetClient=true) {
ActiveWidgetStore.stop();
IntegrationManagers.sharedInstance().stopWatching();
if (DMRoomMap.shared()) DMRoomMap.shared().stop();
+ EventIndexPeg.stop();
const cli = MatrixClientPeg.get();
if (cli) {
cli.stopClient();
From 87f9e0d5650b42383f85804623c2b40aaba50854 Mon Sep 17 00:00:00 2001
From: take100yen
Date: Tue, 12 Nov 2019 11:28:20 +0000
Subject: [PATCH 067/334] Translated using Weblate (Japanese)
Currently translated at 62.7% (1168 of 1864 strings)
Translation: Riot Web/matrix-react-sdk
Translate-URL: https://translate.riot.im/projects/riot-web/matrix-react-sdk/ja/
---
src/i18n/strings/ja.json | 53 +++++++++++++++++++++++++++++++++++-----
1 file changed, 47 insertions(+), 6 deletions(-)
diff --git a/src/i18n/strings/ja.json b/src/i18n/strings/ja.json
index 5e3fecaed9..22b092c06b 100644
--- a/src/i18n/strings/ja.json
+++ b/src/i18n/strings/ja.json
@@ -17,12 +17,12 @@
"Hide read receipts": "既読を表示しない",
"Invited": "招待中",
"%(displayName)s is typing": "%(displayName)s 文字入力中",
- "%(targetName)s joined the room.": "%(targetName)s 部屋に参加しました。",
+ "%(targetName)s joined the room.": "%(targetName)s が部屋に参加しました。",
"Low priority": "低優先度",
"Mute": "通知しない",
"New password": "新しいパスワード",
"Notifications": "通知",
- "Cancel": "取消",
+ "Cancel": "キャンセル",
"Create new room": "新しい部屋を作成",
"Room directory": "公開部屋一覧",
"Search": "検索",
@@ -490,7 +490,7 @@
"Delete %(count)s devices|one": "端末を削除する",
"Device ID": "端末ID",
"Device Name": "端末名",
- "Last seen": "最後のシーン",
+ "Last seen": "最後に表示した時刻",
"Select devices": "端末の選択",
"Failed to set display name": "表示名の設定に失敗しました",
"Disable Notifications": "通知を無効にする",
@@ -1162,8 +1162,8 @@
"No media permissions": "メディア権限がありません",
"You may need to manually permit Riot to access your microphone/webcam": "自分のマイク/ウェブカメラにアクセスするために手動でRiotを許可する必要があるかもしれません",
"Missing Media Permissions, click here to request.": "メディアアクセス権がありません。ここをクリックしてリクエストしてください。",
- "No Audio Outputs detected": "オーディオ出力が検出されなかった",
- "Default Device": "標準端末",
+ "No Audio Outputs detected": "オーディオ出力が検出されませんでした",
+ "Default Device": "既定のデバイス",
"Audio Output": "音声出力",
"VoIP": "VoIP",
"Email": "Eメール",
@@ -1380,5 +1380,46 @@
"Enable Community Filter Panel": "コミュニティーフィルターパネルを有効にする",
"Show recently visited rooms above the room list": "最近訪問した部屋をリストの上位に表示する",
"Low bandwidth mode": "低帯域通信モード",
- "Trust & Devices": "信頼と端末"
+ "Trust & Devices": "信頼と端末",
+ "Public Name": "パブリック名",
+ "Upload profile picture": "プロフィール画像をアップロード",
+ "Upgrade to your own domain": "あなた自身のドメインにアップグレード",
+ "Phone numbers": "電話番号",
+ "Set a new account password...": "アカウントの新しいパスワードを設定...",
+ "Language and region": "言語と地域",
+ "Theme": "テーマ",
+ "General": "一般",
+ "Preferences": "環境設定",
+ "Security & Privacy": "セキュリティとプライバシー",
+ "A device's public name is visible to people you communicate with": "デバイスのパブリック名はあなたと会話するすべての人が閲覧できます",
+ "Room information": "部屋の情報",
+ "Internal room ID:": "内部 部屋ID:",
+ "Room version": "部屋のバージョン",
+ "Room version:": "部屋のバージョン:",
+ "Developer options": "開発者オプション",
+ "Room Addresses": "部屋のアドレス",
+ "Sounds": "音",
+ "Notification sound": "通知音",
+ "Reset": "リセット",
+ "Set a new custom sound": "カスタム音を設定",
+ "Browse": "参照",
+ "Roles & Permissions": "役割と権限",
+ "Changes to who can read history will only apply to future messages in this room. The visibility of existing history will be unchanged.": "誰が履歴を読み取れるかに関する変更は、今後送信されるメッセージにのみ適用されます。既に存在する履歴の表示は変更されません。",
+ "Encryption": "暗号化",
+ "Once enabled, encryption cannot be disabled.": "もし有効化された場合、二度と無効化できません。",
+ "Encrypted": "暗号化",
+ "Email Address": "メールアドレス",
+ "Main address": "メインアドレス",
+ "Join": "参加",
+ "This room is private, and can only be joined by invitation.": "この部屋はプライベートです。招待によってのみ参加できます。",
+ "Create a private room": "プライベートな部屋を作成",
+ "Topic (optional)": "トピック (オプション)",
+ "Hide advanced": "高度な設定を非表示",
+ "Show advanced": "高度な設定を表示",
+ "Block users on other matrix homeservers from joining this room (This setting cannot be changed later!)": "他の Matrix ホームサーバーからの参加を禁止する (この設定はあとから変更できません!)",
+ "Room Settings - %(roomName)s": "部屋の設定 - %(roomName)s",
+ "Explore": "探索",
+ "Filter": "フィルター",
+ "Find a room… (e.g. %(exampleRoom)s)": "部屋を探す… (例: %(exampleRoom)s)",
+ "If you can't find the room you're looking for, ask for an invite or Create a new room.": "もしお探しの部屋が見つからない場合、招待してもらうか部屋を作成しましょう。"
}
From 5fa4bc7192d22cdb0c811f676b9479b3ff99ad6a Mon Sep 17 00:00:00 2001
From: shuji narazaki
Date: Tue, 12 Nov 2019 11:52:45 +0000
Subject: [PATCH 068/334] Translated using Weblate (Japanese)
Currently translated at 62.7% (1168 of 1864 strings)
Translation: Riot Web/matrix-react-sdk
Translate-URL: https://translate.riot.im/projects/riot-web/matrix-react-sdk/ja/
---
src/i18n/strings/ja.json | 7 ++++---
1 file changed, 4 insertions(+), 3 deletions(-)
diff --git a/src/i18n/strings/ja.json b/src/i18n/strings/ja.json
index 22b092c06b..54e2c29a21 100644
--- a/src/i18n/strings/ja.json
+++ b/src/i18n/strings/ja.json
@@ -389,8 +389,8 @@
"%(senderName)s kicked %(targetName)s.": "%(senderName)s は %(targetName)s を追放しました。",
"%(senderName)s withdrew %(targetName)s's invitation.": "%(senderName)s は %(targetName)s の招待を撤回しました。",
"%(senderDisplayName)s changed the topic to \"%(topic)s\".": "%(senderDisplayName)s はトピックを \"%(topic)s\" に変更しました。",
- "%(senderDisplayName)s removed the room name.": "%(senderDisplayName)s はルーム名を削除しました。",
- "%(senderDisplayName)s changed the room name to %(roomName)s.": "%(senderDisplayName)s はルーム名を %(roomName)s に変更しました。",
+ "%(senderDisplayName)s removed the room name.": "%(senderDisplayName)s は部屋名を削除しました。",
+ "%(senderDisplayName)s changed the room name to %(roomName)s.": "%(senderDisplayName)s は部屋名を %(roomName)s に変更しました。",
"%(senderDisplayName)s sent an image.": "%(senderDisplayName)s はイメージを送信しました。",
"%(senderName)s added %(count)s %(addedAddresses)s as addresses for this room.|other": "%(senderName)s はこの部屋のアドレスとして %(addedAddresses)s を追加しました。",
"%(senderName)s added %(count)s %(addedAddresses)s as addresses for this room.|one": "%(senderName)s はこの部屋のアドレスとして %(addedAddresses)s を追加しました。",
@@ -1421,5 +1421,6 @@
"Explore": "探索",
"Filter": "フィルター",
"Find a room… (e.g. %(exampleRoom)s)": "部屋を探す… (例: %(exampleRoom)s)",
- "If you can't find the room you're looking for, ask for an invite or Create a new room.": "もしお探しの部屋が見つからない場合、招待してもらうか部屋を作成しましょう。"
+ "If you can't find the room you're looking for, ask for an invite or Create a new room.": "もしお探しの部屋が見つからない場合、招待してもらうか部屋を作成しましょう。",
+ "Enable room encryption": "部屋の暗号化を有効にする"
}
From 523e17838de98b9a0744b229db36741a21d22996 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?=EC=86=A1=ED=83=9C=EC=84=AD?=
Date: Tue, 12 Nov 2019 12:15:45 +0000
Subject: [PATCH 069/334] Translated using Weblate (Korean)
Currently translated at 100.0% (1864 of 1864 strings)
Translation: Riot Web/matrix-react-sdk
Translate-URL: https://translate.riot.im/projects/riot-web/matrix-react-sdk/ko/
---
src/i18n/strings/ko.json | 47 +++++++++++++++++++++++++---------------
1 file changed, 29 insertions(+), 18 deletions(-)
diff --git a/src/i18n/strings/ko.json b/src/i18n/strings/ko.json
index 54425657cf..3d01d5be67 100644
--- a/src/i18n/strings/ko.json
+++ b/src/i18n/strings/ko.json
@@ -128,8 +128,8 @@
"Deactivate Account": "계정 비활성화",
"Deactivate my account": "계정 정지하기",
"Decline": "거절",
- "Decrypt %(text)s": "%(text)s 해독",
- "Decryption error": "암호 해독 오류",
+ "Decrypt %(text)s": "%(text)s 복호화",
+ "Decryption error": "암호 복호화 오류",
"Delete": "지우기",
"Deops user with given id": "받은 ID로 사용자의 등급을 낮추기",
"Device ID:": "기기 ID:",
@@ -156,7 +156,7 @@
"End-to-end encryption is in beta and may not be reliable": "종단 간 암호화는 베타 테스트 중이며 신뢰하기 힘들 수 있습니다.",
"Enter Code": "코드를 입력하세요",
"Enter passphrase": "암호 입력",
- "Error decrypting attachment": "첨부 파일 해독 중 오류",
+ "Error decrypting attachment": "첨부 파일 복호화 중 오류",
"Error: Problem communicating with the given homeserver.": "오류: 지정한 홈서버와 통신에 문제가 있습니다.",
"Event information": "이벤트 정보",
"Existing Call": "기존 전화",
@@ -394,7 +394,7 @@
"Unable to capture screen": "화면을 찍을 수 없음",
"Unable to enable Notifications": "알림을 사용할 수 없음",
"Unable to load device list": "기기 목록을 불러올 수 없음",
- "Undecryptable": "해독할 수 없음",
+ "Undecryptable": "복호화할 수 없음",
"Unencrypted room": "암호화하지 않은 방",
"unencrypted": "암호화하지 않음",
"Unencrypted message": "암호화하지 않은 메시지",
@@ -549,10 +549,10 @@
"Unknown error": "알 수 없는 오류",
"Incorrect password": "맞지 않는 비밀번호",
"To continue, please enter your password.": "계속하려면, 비밀번호를 입력해주세요.",
- "This process allows you to export the keys for messages you have received in encrypted rooms to a local file. You will then be able to import the file into another Matrix client in the future, so that client will also be able to decrypt these messages.": "이 과정으로 암호화한 방에서 받은 메시지의 키를 로컬 파일로 내보낼 수 있습니다. 그런 다음 나중에 다른 Matrix 클라이언트에서 파일을 가져와서, 해당 클라이언트에서도 이 메시지를 해독할 수 있도록 할 수 있습니다.",
- "The exported file will allow anyone who can read it to decrypt any encrypted messages that you can see, so you should be careful to keep it secure. To help with this, you should enter a passphrase below, which will be used to encrypt the exported data. It will only be possible to import the data by using the same passphrase.": "내보낸 파일이 있으면 누구든 암호화한 메시지를 해독해서 읽을 수 있으므로, 보안에 신경을 써야 합니다. 이런 이유로 내보낸 파일을 암호화하도록 아래에 암호를 입력하는 것을 추천합니다. 같은 암호를 사용해야 데이터를 불러올 수 있을 것입니다.",
- "This process allows you to import encryption keys that you had previously exported from another Matrix client. You will then be able to decrypt any messages that the other client could decrypt.": "이 과정으로 다른 Matrix 클라이언트에서 내보낸 암호화 키를 가져올 수 있습니다. 그런 다음 이전 클라이언트에서 해독할 수 있는 모든 메시지를 해독할 수 있습니다.",
- "The export file will be protected with a passphrase. You should enter the passphrase here, to decrypt the file.": "내보낸 파일이 암호로 보호되어 있습니다. 파일을 해독하려면, 여기에 암호를 입력해야 합니다.",
+ "This process allows you to export the keys for messages you have received in encrypted rooms to a local file. You will then be able to import the file into another Matrix client in the future, so that client will also be able to decrypt these messages.": "이 과정으로 암호화한 방에서 받은 메시지의 키를 로컬 파일로 내보낼 수 있습니다. 그런 다음 나중에 다른 Matrix 클라이언트에서 파일을 가져와서, 해당 클라이언트에서도 이 메시지를 복호화할 수 있도록 할 수 있습니다.",
+ "The exported file will allow anyone who can read it to decrypt any encrypted messages that you can see, so you should be careful to keep it secure. To help with this, you should enter a passphrase below, which will be used to encrypt the exported data. It will only be possible to import the data by using the same passphrase.": "내보낸 파일이 있으면 누구든 암호화한 메시지를 복호화해서 읽을 수 있으므로, 보안에 신경을 써야 합니다. 이런 이유로 내보낸 파일을 암호화하도록 아래에 암호를 입력하는 것을 추천합니다. 같은 암호를 사용해야 데이터를 불러올 수 있을 것입니다.",
+ "This process allows you to import encryption keys that you had previously exported from another Matrix client. You will then be able to decrypt any messages that the other client could decrypt.": "이 과정으로 다른 Matrix 클라이언트에서 내보낸 암호화 키를 가져올 수 있습니다. 그런 다음 이전 클라이언트에서 복호화할 수 있는 모든 메시지를 복호화할 수 있습니다.",
+ "The export file will be protected with a passphrase. You should enter the passphrase here, to decrypt the file.": "내보낸 파일이 암호로 보호되어 있습니다. 파일을 복호화하려면, 여기에 암호를 입력해야 합니다.",
"Are you sure you wish to remove (delete) this event? Note that if you delete a room name or topic change, it could undo the change.": "이 이벤트를 감추길(삭제하길) 원하세요? 방 이름을 삭제하거나 주제를 바꾸면, 다시 생길 수도 있습니다.",
"To verify that this device can be trusted, please contact its owner using some other means (e.g. in person or a phone call) and ask them whether the key they see in their User Settings for this device matches the key below:": "이 기기를 신뢰할 수 있는 지 인증하려면, 다른 방법(예를 들자면 직접 만나거나 전화를 걸어서)으로 소유자 분에게 연락해, 사용자 설정에 있는 키가 아래 키와 같은지 물어보세요:",
"Device name": "기기 이름",
@@ -590,9 +590,9 @@
"Custom server": "사용자 지정 서버",
"Home server URL": "홈 서버 URL",
"Identity server URL": "ID 서버 URL",
- "Error decrypting audio": "음성 해독 오류",
- "Error decrypting image": "사진 해독 중 오류",
- "Error decrypting video": "영상 해독 중 오류",
+ "Error decrypting audio": "음성 복호화 오류",
+ "Error decrypting image": "사진 복호화 중 오류",
+ "Error decrypting video": "영상 복호화 중 오류",
"Add an Integration": "통합 추가",
"You are about to be taken to a third-party site so you can authenticate your account for use with %(integrationsUrl)s. Do you wish to continue?": "%(integrationsUrl)s에서 쓸 수 있도록 계정을 인증하려고 다른 사이트로 이동하고 있습니다. 계속하겠습니까?",
"Removed or unknown message type": "감췄거나 알 수 없는 메시지 유형",
@@ -670,7 +670,7 @@
"Messages containing my display name": "내 표시 이름이 포함된 메시지",
"Messages in one-to-one chats": "1:1 대화 메시지",
"Unavailable": "이용할 수 없음",
- "View Decrypted Source": "해독된 소스 보기",
+ "View Decrypted Source": "복호화된 소스 보기",
"Send": "보내기",
"remove %(name)s from the directory.": "목록에서 %(name)s 방을 제거했습니다.",
"Notifications on the following keywords follow rules which can’t be displayed here:": "여기에 표시할 수 없는 규칙에 따르는 다음 키워드에 대한 알림:",
@@ -779,7 +779,7 @@
"%(oneUser)schanged their avatar %(count)s times|other": "%(oneUser)s님이 아바타를 %(count)s번 바꿨습니다",
"%(oneUser)schanged their avatar %(count)s times|one": "%(oneUser)s님이 아바타를 바꿨습니다",
"This setting cannot be changed later!": "이 설정은 나중에 바꿀 수 없습니다!",
- "Data from an older version of Riot has been detected. This will have caused end-to-end cryptography to malfunction in the older version. End-to-end encrypted messages exchanged recently whilst using the older version may not be decryptable in this version. This may also cause messages exchanged with this version to fail. If you experience problems, log out and back in again. To retain message history, export and re-import your keys.": "이전 버전 Riot의 데이터가 감지됬습니다. 이 때문에 이전 버전에서 종단간 암호화가 작동하지 않을 수 있습니다. 이전 버전을 사용하면서 최근에 교환한 종단간 암호화 메시지를 이 버전에서는 해독할 수 없습니다. 이 버전에서 메시지를 교환할 수 없을 수도 있습니다. 문제가 발생하면 로그아웃한 후 다시 로그인하세요. 메시지 기록을 유지하려면 키를 내보낸 후 다시 가져오세요.",
+ "Data from an older version of Riot has been detected. This will have caused end-to-end cryptography to malfunction in the older version. End-to-end encrypted messages exchanged recently whilst using the older version may not be decryptable in this version. This may also cause messages exchanged with this version to fail. If you experience problems, log out and back in again. To retain message history, export and re-import your keys.": "이전 버전 Riot의 데이터가 감지됬습니다. 이 때문에 이전 버전에서 종단간 암호화가 작동하지 않을 수 있습니다. 이전 버전을 사용하면서 최근에 교환한 종단간 암호화 메시지를 이 버전에서는 복호화할 수 없습니다. 이 버전에서 메시지를 교환할 수 없을 수도 있습니다. 문제가 발생하면 로그아웃한 후 다시 로그인하세요. 메시지 기록을 유지하려면 키를 내보낸 후 다시 가져오세요.",
"Hide display name changes": "별명 변경 내역 숨기기",
"This event could not be displayed": "이 이벤트를 표시할 수 없음",
"Seen by %(displayName)s (%(userName)s) at %(dateTime)s": "%(displayName)s(%(userName)s)님이 %(dateTime)s에 확인함",
@@ -902,7 +902,7 @@
"%(senderName)s sent a video": "%(senderName)s님이 영상을 보냄",
"%(senderName)s uploaded a file": "%(senderName)s님이 파일을 업로드함",
"Key request sent.": "키 요청을 보냈습니다.",
- "If your other devices do not have the key for this message you will not be able to decrypt them.": "당신의 다른 기기에 이 메시지를 읽기 위한 키가 없다면 메시지를 해독할 수 없습니다.",
+ "If your other devices do not have the key for this message you will not be able to decrypt them.": "당신의 다른 기기에 이 메시지를 읽기 위한 키가 없다면 메시지를 복호화할 수 없습니다.",
"Encrypting": "암호화 중",
"Encrypted, not sent": "암호화 됨, 보내지지 않음",
"Disinvite this user?": "이 사용자에 대한 초대를 취소할까요?",
@@ -1811,13 +1811,13 @@
"Deny": "거부",
"Unable to load backup status": "백업 상태 불러올 수 없음",
"Recovery Key Mismatch": "복구 키가 맞지 않음",
- "Backup could not be decrypted with this key: please verify that you entered the correct recovery key.": "이 키로 백업을 해독할 수 없음: 맞는 복구 키를 입력해서 인증해주세요.",
+ "Backup could not be decrypted with this key: please verify that you entered the correct recovery key.": "이 키로 백업을 복호화할 수 없음: 맞는 복구 키를 입력해서 인증해주세요.",
"Incorrect Recovery Passphrase": "맞지 않은 복구 암호",
- "Backup could not be decrypted with this passphrase: please verify that you entered the correct recovery passphrase.": "이 암호로 백업을 해독할 수 없음: 맞는 암호를 입력해서 입증해주세요.",
+ "Backup could not be decrypted with this passphrase: please verify that you entered the correct recovery passphrase.": "이 암호로 백업을 복호화할 수 없음: 맞는 암호를 입력해서 입증해주세요.",
"Unable to restore backup": "백업을 복구할 수 없음",
"No backup found!": "백업을 찾을 수 없습니다!",
"Backup Restored": "백업 복구됨",
- "Failed to decrypt %(failedCount)s sessions!": "%(failedCount)s개의 세션 해독에 실패했습니다!",
+ "Failed to decrypt %(failedCount)s sessions!": "%(failedCount)s개의 세션 복호화에 실패했습니다!",
"Restored %(sessionCount)s session keys": "%(sessionCount)s개의 세션 키 복구됨",
"Enter Recovery Passphrase": "복구 암호 입력",
"Warning: you should only set up key backup from a trusted computer.": "경고: 신뢰할 수 있는 컴퓨터에서만 키 백업을 설정해야 합니다.",
@@ -2124,5 +2124,16 @@
"Show tray icon and minimize window to it on close": "닫을 때 창을 최소화하고 트레이 아이콘으로 표시하기",
"This action requires accessing the default identity server to validate an email address or phone number, but the server does not have any terms of service.": "이 작업에는 이메일 주소 또는 전화번호를 확인하기 위해 기본 ID 서버 에 접근해야 합니다. 하지만 서버가 서비스 약관을 갖고 있지 않습니다.",
"Trust": "신뢰함",
- "Message Actions": "메시지 동작"
+ "Message Actions": "메시지 동작",
+ "%(name)s (%(userId)s)": "%(name)s (%(userId)s)",
+ "Send verification requests in direct message": "다이렉트 메시지에서 확인 요청 보내기",
+ "You verified %(name)s": "%(name)s님을 확인했습니다",
+ "You cancelled verifying %(name)s": "%(name)s님의 확인을 취소했습니다",
+ "%(name)s cancelled verifying": "%(name)s님이 확인을 취소했습니다",
+ "You accepted": "당신이 수락했습니다",
+ "%(name)s accepted": "%(name)s님이 수락했습니다",
+ "You cancelled": "당신이 취소했습니다",
+ "%(name)s cancelled": "%(name)s님이 취소했습니다",
+ "%(name)s wants to verify": "%(name)s님이 확인을 요청합니다",
+ "You sent a verification request": "확인 요청을 보냈습니다"
}
From fd28cf7a4c9e284ffe3bca8921e7402aacdd2924 Mon Sep 17 00:00:00 2001
From: Travis Ralston
Date: Tue, 12 Nov 2019 15:12:54 -0700
Subject: [PATCH 070/334] Move notification count to in front of the room name
in the page title
Fixes https://github.com/vector-im/riot-web/issues/10943
---
src/components/structures/MatrixChat.js | 6 ++++--
1 file changed, 4 insertions(+), 2 deletions(-)
diff --git a/src/components/structures/MatrixChat.js b/src/components/structures/MatrixChat.js
index 6cc86bf6d7..cd5b27f2b9 100644
--- a/src/components/structures/MatrixChat.js
+++ b/src/components/structures/MatrixChat.js
@@ -1767,10 +1767,12 @@ export default createReactClass({
const client = MatrixClientPeg.get();
const room = client && client.getRoom(this.state.currentRoomId);
if (room) {
- subtitle = `| ${ room.name } ${subtitle}`;
+ subtitle = `${this.subTitleStatus} | ${ room.name } ${subtitle}`;
}
+ } else {
+ subtitle = `${this.subTitleStatus} ${subtitle}`;
}
- document.title = `${SdkConfig.get().brand || 'Riot'} ${subtitle} ${this.subTitleStatus}`;
+ document.title = `${SdkConfig.get().brand || 'Riot'} ${subtitle}`;
},
updateStatusIndicator: function(state, prevState) {
From 1aa0ab13e682fd73c2c9b7d46d77202339593a1e Mon Sep 17 00:00:00 2001
From: Travis Ralston
Date: Tue, 12 Nov 2019 15:38:24 -0700
Subject: [PATCH 071/334] Add some logging/recovery for lost rooms
Zero inserts is not normal, so we apply the same recovery technique from the categorization logic above this block: insert it to be the very first room and hope that someone complains that the room is ordered incorrectly.
There's some additional logging to try and identify what went wrong because it should definitely be inserted. The `!== 1` check is not supposed to be called, ever.
Logging for https://github.com/vector-im/riot-web/issues/11303
---
src/stores/RoomListStore.js | 16 +++++++++++++++-
1 file changed, 15 insertions(+), 1 deletion(-)
diff --git a/src/stores/RoomListStore.js b/src/stores/RoomListStore.js
index 980753551a..134870398f 100644
--- a/src/stores/RoomListStore.js
+++ b/src/stores/RoomListStore.js
@@ -515,7 +515,21 @@ class RoomListStore extends Store {
}
if (count !== 1) {
- console.warn(`!! Room ${room.roomId} inserted ${count} times`);
+ console.warn(`!! Room ${room.roomId} inserted ${count} times to ${targetTag}`);
+ }
+
+ // This is a workaround for https://github.com/vector-im/riot-web/issues/11303
+ // The logging is to try and identify what happened exactly.
+ if (count === 0) {
+ // Something went very badly wrong - try to recover the room.
+ // We don't bother checking how the target list is ordered - we're expecting
+ // to just insert it.
+ console.warn(`!! Recovering ${room.roomId} for tag ${targetTag} at position 0`);
+ if (!listsClone[targetTag]) {
+ console.warn(`!! List for tag ${targetTag} does not exist - creating`);
+ listsClone[targetTag] = [];
+ }
+ listsClone[targetTag].splice(0, 0, {room, category});
}
}
From fa6e02fafb30b44b0ac0d833335780b18fc80851 Mon Sep 17 00:00:00 2001
From: Travis Ralston
Date: Tue, 12 Nov 2019 15:38:24 -0700
Subject: [PATCH 072/334] Revert "Add some logging/recovery for lost rooms"
This reverts commit 1aa0ab13e682fd73c2c9b7d46d77202339593a1e.
---
src/stores/RoomListStore.js | 16 +---------------
1 file changed, 1 insertion(+), 15 deletions(-)
diff --git a/src/stores/RoomListStore.js b/src/stores/RoomListStore.js
index 134870398f..980753551a 100644
--- a/src/stores/RoomListStore.js
+++ b/src/stores/RoomListStore.js
@@ -515,21 +515,7 @@ class RoomListStore extends Store {
}
if (count !== 1) {
- console.warn(`!! Room ${room.roomId} inserted ${count} times to ${targetTag}`);
- }
-
- // This is a workaround for https://github.com/vector-im/riot-web/issues/11303
- // The logging is to try and identify what happened exactly.
- if (count === 0) {
- // Something went very badly wrong - try to recover the room.
- // We don't bother checking how the target list is ordered - we're expecting
- // to just insert it.
- console.warn(`!! Recovering ${room.roomId} for tag ${targetTag} at position 0`);
- if (!listsClone[targetTag]) {
- console.warn(`!! List for tag ${targetTag} does not exist - creating`);
- listsClone[targetTag] = [];
- }
- listsClone[targetTag].splice(0, 0, {room, category});
+ console.warn(`!! Room ${room.roomId} inserted ${count} times`);
}
}
From 651098b2cabbb6c9211a571a9beff480f8b8b1bf Mon Sep 17 00:00:00 2001
From: Besnik Bleta
Date: Tue, 12 Nov 2019 17:46:17 +0000
Subject: [PATCH 073/334] Translated using Weblate (Albanian)
Currently translated at 99.8% (1894 of 1898 strings)
Translation: Riot Web/matrix-react-sdk
Translate-URL: https://translate.riot.im/projects/riot-web/matrix-react-sdk/sq/
---
src/i18n/strings/sq.json | 38 ++++++++++++++++++++++++++++++++++++--
1 file changed, 36 insertions(+), 2 deletions(-)
diff --git a/src/i18n/strings/sq.json b/src/i18n/strings/sq.json
index 1d80a90a2b..2efe4ddd68 100644
--- a/src/i18n/strings/sq.json
+++ b/src/i18n/strings/sq.json
@@ -2063,7 +2063,7 @@
"Discovery options will appear once you have added a phone number above.": "Mundësitë e zbulimit do të shfaqen sapo të keni shtuar më sipër një numër telefoni.",
"Call failed due to misconfigured server": "Thirrja dështoi për shkak shërbyesi të keqformësuar",
"Please ask the administrator of your homeserver (%(homeserverDomain)s) to configure a TURN server in order for calls to work reliably.": "Që thirrjet të funksionojnë pa probleme, ju lutemi, kërkojini përgjegjësit të shërbyesit tuaj Home (%(homeserverDomain)s) të formësojë një shërbyes TURN.",
- "Alternatively, you can try to use the public server at turn.matrix.org, but this will not be as reliable, and it will share your IP address with that server. You can also manage this in Settings.": "Ndryshe, mund të provoni të përdorni shërbyesin publik te turn.matrix.org, por kjo s’do të jetë edhe aq e qëndrueshme, dhe adresa juaj IP do t’i bëhet e njohur atij shërbyesi.Këtë mund ta bëni edhe që nga Rregullimet.",
+ "Alternatively, you can try to use the public server at turn.matrix.org, but this will not be as reliable, and it will share your IP address with that server. You can also manage this in Settings.": "Ndryshe, mund të provoni të përdorni shërbyesin publik te turn.matrix.org, por kjo s’do të jetë edhe aq e qëndrueshme, dhe adresa juaj IP do t’i bëhet e njohur atij shërbyesi. Këtë mund ta bëni edhe që nga Rregullimet.",
"Try using turn.matrix.org": "Provo të përdorësh turn.matrix.org",
"Allow fallback call assist server turn.matrix.org when your homeserver does not offer one (your IP address would be shared during a call)": "Lejoni shërbyes rrugëzgjidhje asistimi thirrjesh turn.matrix.org kur shërbyesi juaj Home nuk ofron një të tillë (gjatë thirrjes, adresa juaj IP do t’i bëhet e ditur)",
"ID": "ID",
@@ -2246,5 +2246,39 @@
"Cancel search": "Anulo kërkimin",
"No identity server is configured so you cannot add an email address in order to reset your password in the future.": "S’ka shërbyes identitetesh të formësuar, ndaj s’mund të shtoni një adresë email që të mund të ricaktoni fjalëkalimin tuaj në të ardhmen.",
"Jump to first unread room.": "Hidhu te dhoma e parë e palexuar.",
- "Jump to first invite.": "Hidhu te ftesa e parë."
+ "Jump to first invite.": "Hidhu te ftesa e parë.",
+ "Try out new ways to ignore people (experimental)": "Provoni rrugë të reja për shpërfillje personash (eksperimentale)",
+ "My Ban List": "Lista Ime e Dëbimeve",
+ "This is your list of users/servers you have blocked - don't leave the room!": "Kjo është lista juaj e përdoruesve/shërbyesve që keni bllokuar - mos dilni nga dhoma!",
+ "Ignored/Blocked": "Të shpërfillur/Të bllokuar",
+ "Error adding ignored user/server": "Gabim shtimi përdoruesi/shërbyesi të shpërfillur",
+ "Something went wrong. Please try again or view your console for hints.": "Diç shkoi ters. Ju lutemi, riprovoni ose, për ndonjë ide, shihni konsolën tuaj.",
+ "Error subscribing to list": "Gabim pajtimi te lista",
+ "Please verify the room ID or alias and try again.": "Ju lutemi, verifikoni ID-në ose aliasin e dhomës dhe riprovoni.",
+ "Error removing ignored user/server": "Gabim në heqje përdoruesi/shërbyes të shpërfillur",
+ "Error unsubscribing from list": "Gabim shpajtimi nga lista",
+ "Please try again or view your console for hints.": "Ju lutemi, riprovoni, ose shihni konsolën tuaj, për ndonjë ide.",
+ "None": "Asnjë",
+ "Ban list rules - %(roomName)s": "Rregulla liste dëbimesh - %(roomName)s",
+ "Server rules": "Rregulla shërbyesi",
+ "User rules": "Rregulla përdoruesi",
+ "You have not ignored anyone.": "S’keni shpërfillur ndonjë.",
+ "You are currently ignoring:": "Aktualisht shpërfillni:",
+ "You are not subscribed to any lists": "S’jeni pajtuar te ndonjë listë",
+ "Unsubscribe": "Shpajtohuni",
+ "View rules": "Shihni rregulla",
+ "You are currently subscribed to:": "Jeni i pajtuar te:",
+ "⚠ These settings are meant for advanced users.": "⚠ Këto rregullime janë menduar për përdorues të përparuar.",
+ "Add users and servers you want to ignore here. Use asterisks to have Riot match any characters. For example, @bot:* would ignore all users that have the name 'bot' on any server.": "Shtoni këtu përdorues dhe shërbyes që doni të shpërfillen. Që Riot të kërkojë për përputhje me çfarëdo shkronjash, përdorni yllthin. Për shembull, @bot:* do të shpërfillë krej përdoruesit që kanë emrin 'bot' në çfarëdo shërbyesi.",
+ "Ignoring people is done through ban lists which contain rules for who to ban. Subscribing to a ban list means the users/servers blocked by that list will be hidden from you.": "Shpërfillja e personave kryhet përmes listash dëbimi, të cilat përmbajnë rregulla se cilët të dëbohen. Pajtimi te një listë dëbimesh do të thotë se përdoruesit/shërbyesit e bllokuar nga ajo listë do t’ju fshihen juve.",
+ "Personal ban list": "Listë personale dëbimesh",
+ "Your personal ban list holds all the users/servers you personally don't want to see messages from. After ignoring your first user/server, a new room will show up in your room list named 'My Ban List' - stay in this room to keep the ban list in effect.": "Lista juaj personale e dëbimeve mban krejt përdoruesit/shërbyesit prej të cilëve ju personalisht s’dëshironi të shihni mesazhe. Pas shpërfilljes së përdoruesit/shërbyesit tuaj të parë, te lista juaj e dhomave do të shfaqet një dhomë e re e quajtur 'Lista Ime e Dëbimeve' - qëndroni në këtë dhomë që ta mbani listën e dëbimeve në fuqi.",
+ "Server or user ID to ignore": "Shërbyes ose ID përdoruesi për t’u shpërfillur",
+ "eg: @bot:* or example.org": "p.sh.: @bot:* ose example.org",
+ "Subscribed lists": "Lista me pajtim",
+ "Subscribing to a ban list will cause you to join it!": "Pajtimi te një listë dëbimesh do të shkaktojë pjesëmarrjen tuaj në të!",
+ "If this isn't what you want, please use a different tool to ignore users.": "Nëse kjo s’është ajo çka doni, ju lutemi, përdorni një tjetër mjet për të shpërfillur përdorues.",
+ "Room ID or alias of ban list": "ID dhome ose alias e listës së dëbimeve",
+ "Subscribe": "Pajtohuni",
+ "You have ignored this user, so their message is hidden. Show anyways.": "E keni shpërfillur këtë përdorues, ndaj mesazhi i tij është fshehur. Shfaqe, sido qoftë."
}
From 6b8d8d13f241d996bab6b2345db7e084a287adee Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Jiri=20Gr=C3=B6nroos?=
Date: Tue, 12 Nov 2019 20:28:10 +0000
Subject: [PATCH 074/334] Translated using Weblate (Finnish)
Currently translated at 95.6% (1814 of 1898 strings)
Translation: Riot Web/matrix-react-sdk
Translate-URL: https://translate.riot.im/projects/riot-web/matrix-react-sdk/fi/
---
src/i18n/strings/fi.json | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/i18n/strings/fi.json b/src/i18n/strings/fi.json
index 5331bdb5c8..a1163f564b 100644
--- a/src/i18n/strings/fi.json
+++ b/src/i18n/strings/fi.json
@@ -1533,7 +1533,7 @@
"Prepends ¯\\_(ツ)_/¯ to a plain-text message": "Lisää ”¯\\_(ツ)_/¯” viestin alkuun",
"User %(userId)s is already in the room": "Käyttäjä %(userId)s on jo huoneessa",
"The user must be unbanned before they can be invited.": "Käyttäjän porttikielto täytyy poistaa ennen kutsumista.",
- "Upgrade to your own domain": "Päivitä omaan verkkotunnukseesi",
+ "Upgrade to your own domain": "Päivitä omaan verkkotunnukseen",
"Accept all %(invitedRooms)s invites": "Hyväksy kaikki %(invitedRooms)s kutsut",
"Change room avatar": "Vaihda huoneen kuva",
"Change room name": "Vaihda huoneen nimi",
From a0ff1e9e9b547de4386ac38a6900db5b612be840 Mon Sep 17 00:00:00 2001
From: Szimszon
Date: Tue, 12 Nov 2019 20:08:29 +0000
Subject: [PATCH 075/334] Translated using Weblate (Hungarian)
Currently translated at 100.0% (1898 of 1898 strings)
Translation: Riot Web/matrix-react-sdk
Translate-URL: https://translate.riot.im/projects/riot-web/matrix-react-sdk/hu/
---
src/i18n/strings/hu.json | 36 +++++++++++++++++++++++++++++++++++-
1 file changed, 35 insertions(+), 1 deletion(-)
diff --git a/src/i18n/strings/hu.json b/src/i18n/strings/hu.json
index 67af8a6a57..29747bb1b6 100644
--- a/src/i18n/strings/hu.json
+++ b/src/i18n/strings/hu.json
@@ -2278,5 +2278,39 @@
"You cancelled": "Megszakítottad",
"%(name)s cancelled": "%(name)s megszakította",
"%(name)s wants to verify": "%(name)s ellenőrizni szeretné",
- "You sent a verification request": "Ellenőrzési kérést küldtél"
+ "You sent a verification request": "Ellenőrzési kérést küldtél",
+ "Try out new ways to ignore people (experimental)": "Emberek figyelmen kívül hagyásához próbálj ki új utakat (kísérleti)",
+ "My Ban List": "Tiltólistám",
+ "This is your list of users/servers you have blocked - don't leave the room!": "Ez az általad tiltott felhasználók/szerverek listája - ne hagyd el ezt a szobát!",
+ "Ignored/Blocked": "Figyelmen kívül hagyott/Tiltott",
+ "Error adding ignored user/server": "Hiba a felhasználó/szerver hozzáadásánál a figyelmen kívül hagyandók listájához",
+ "Something went wrong. Please try again or view your console for hints.": "Valami nem sikerült. Kérjük próbáld újra vagy nézd meg a konzolt a hiba okának felderítéséhez.",
+ "Error subscribing to list": "A listára való feliratkozásnál hiba történt",
+ "Please verify the room ID or alias and try again.": "Kérünk ellenőrizd a szoba azonosítóját vagy alternatív nevét és próbáld újra.",
+ "Error removing ignored user/server": "Hiba a felhasználó/szerver törlésénél a figyelmen kívül hagyandók listájából",
+ "Error unsubscribing from list": "A listáról való leiratkozásnál hiba történt",
+ "Please try again or view your console for hints.": "Kérjük próbáld újra vagy nézd meg a konzolt a hiba okának felderítéséhez.",
+ "None": "Semmi",
+ "Ban list rules - %(roomName)s": "Tiltási lista szabályok - %(roomName)s",
+ "Server rules": "Szerver szabályok",
+ "User rules": "Felhasználói szabályok",
+ "You have not ignored anyone.": "Senkit nem hagysz figyelmen kívül.",
+ "You are currently ignoring:": "Jelenleg őket hagyod figyelmen kívül:",
+ "You are not subscribed to any lists": "Nem iratkoztál fel egyetlen listára sem",
+ "Unsubscribe": "Leiratkozás",
+ "View rules": "Szabályok megtekintése",
+ "You are currently subscribed to:": "Jelenleg ezekre iratkoztál fel:",
+ "⚠ These settings are meant for advanced users.": "⚠ Ezek a beállítások haladó felhasználók számára vannak.",
+ "Add users and servers you want to ignore here. Use asterisks to have Riot match any characters. For example, @bot:* would ignore all users that have the name 'bot' on any server.": "Adj hozzá olyan felhasználókat és szervereket akiket figyelmen kívül kívánsz hagyni. Használj csillagot ahol bármilyen karakter állhat. Például: @bot:* minden „bot” nevű felhasználót figyelmen kívül fog hagyni akármelyik szerverről.",
+ "Ignoring people is done through ban lists which contain rules for who to ban. Subscribing to a ban list means the users/servers blocked by that list will be hidden from you.": "Emberek figyelmen kívül hagyása tiltólistán keresztül történik ami arról tartalmaz szabályokat, hogy kiket kell kitiltani. A feliratkozás a tiltólistára azt jelenti, hogy azok a felhasználók/szerverek amik tiltva vannak a lista által, azoknak az üzenetei rejtve maradnak számodra.",
+ "Personal ban list": "Személyes tiltó lista",
+ "Your personal ban list holds all the users/servers you personally don't want to see messages from. After ignoring your first user/server, a new room will show up in your room list named 'My Ban List' - stay in this room to keep the ban list in effect.": "A személyes tiltólistád tartalmazza azokat a személyeket/szervereket akiktől nem szeretnél üzeneteket látni. Az első felhasználó/szerver figyelmen kívül hagyása után egy új szoba jelenik meg a szobák listájában „Tiltólistám” névvel - ahhoz, hogy a lista érvényben maradjon maradj a szobában.",
+ "Server or user ID to ignore": "Figyelmen kívül hagyandó szerver vagy felhasználói azonosító",
+ "eg: @bot:* or example.org": "pl.: @bot:* vagy example.org",
+ "Subscribed lists": "Feliratkozott listák",
+ "Subscribing to a ban list will cause you to join it!": "A feliratkozás egy tiltó listára azzal jár, hogy csatlakozol hozzá!",
+ "If this isn't what you want, please use a different tool to ignore users.": "Ha nem ez az amit szeretnél, kérlek használj más eszközt a felhasználók figyelmen kívül hagyásához.",
+ "Room ID or alias of ban list": "Tiltó lista szoba azonosítója vagy alternatív neve",
+ "Subscribe": "Feliratkozás",
+ "You have ignored this user, so their message is hidden. Show anyways.": "Ezt a felhasználót figyelmen kívül hagyod, így az üzenetei el lesznek rejtve. Mindenképpen megmutat."
}
From 3dcc92b79da7458e25b6511f6d0a478da746714b Mon Sep 17 00:00:00 2001
From: Travis Ralston
Date: Tue, 12 Nov 2019 15:38:24 -0700
Subject: [PATCH 076/334] Add some logging/recovery for lost rooms
Zero inserts is not normal, so we apply the same recovery technique from the categorization logic above this block: insert it to be the very first room and hope that someone complains that the room is ordered incorrectly.
There's some additional logging to try and identify what went wrong because it should definitely be inserted. The `!== 1` check is not supposed to be called, ever.
Logging for https://github.com/vector-im/riot-web/issues/11303
---
src/stores/RoomListStore.js | 16 +++++++++++++++-
1 file changed, 15 insertions(+), 1 deletion(-)
diff --git a/src/stores/RoomListStore.js b/src/stores/RoomListStore.js
index 980753551a..134870398f 100644
--- a/src/stores/RoomListStore.js
+++ b/src/stores/RoomListStore.js
@@ -515,7 +515,21 @@ class RoomListStore extends Store {
}
if (count !== 1) {
- console.warn(`!! Room ${room.roomId} inserted ${count} times`);
+ console.warn(`!! Room ${room.roomId} inserted ${count} times to ${targetTag}`);
+ }
+
+ // This is a workaround for https://github.com/vector-im/riot-web/issues/11303
+ // The logging is to try and identify what happened exactly.
+ if (count === 0) {
+ // Something went very badly wrong - try to recover the room.
+ // We don't bother checking how the target list is ordered - we're expecting
+ // to just insert it.
+ console.warn(`!! Recovering ${room.roomId} for tag ${targetTag} at position 0`);
+ if (!listsClone[targetTag]) {
+ console.warn(`!! List for tag ${targetTag} does not exist - creating`);
+ listsClone[targetTag] = [];
+ }
+ listsClone[targetTag].splice(0, 0, {room, category});
}
}
From 008554463d0478e9b06f0da35dce1af83d08eb55 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Damir=20Jeli=C4=87?=
Date: Wed, 13 Nov 2019 09:52:59 +0100
Subject: [PATCH 077/334] Lifecycle: Move the event index deletion into the
clear storage method.
---
src/Lifecycle.js | 9 +++++++--
1 file changed, 7 insertions(+), 2 deletions(-)
diff --git a/src/Lifecycle.js b/src/Lifecycle.js
index 7360cd3231..1e68bcc062 100644
--- a/src/Lifecycle.js
+++ b/src/Lifecycle.js
@@ -613,7 +613,6 @@ export function onLoggedOut() {
// so that React components unmount first. This avoids React soft crashes
// that can occur when components try to use a null client.
dis.dispatch({action: 'on_logged_out'});
- EventIndexPeg.deleteEventIndex().done();
stopMatrixClient();
_clearStorage().done();
}
@@ -633,7 +632,13 @@ function _clearStorage() {
// we'll never make any requests, so can pass a bogus HS URL
baseUrl: "",
});
- return cli.clearStores();
+
+ const clear = async() => {
+ await EventIndexPeg.deleteEventIndex();
+ await cli.clearStores();
+ }
+
+ return clear();
}
/**
From 1cc64f2426bc049257985b06855b9ba9dbcd0113 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Damir=20Jeli=C4=87?=
Date: Wed, 13 Nov 2019 10:10:35 +0100
Subject: [PATCH 078/334] Searching: Move the small helper functions out of the
eventIndexSearch function.
---
src/Searching.js | 146 +++++++++++++++++++++++------------------------
1 file changed, 73 insertions(+), 73 deletions(-)
diff --git a/src/Searching.js b/src/Searching.js
index cd06d9bc67..cff5742b04 100644
--- a/src/Searching.js
+++ b/src/Searching.js
@@ -34,80 +34,80 @@ function serverSideSearch(term, roomId = undefined) {
return searchPromise;
}
+async function combinedSearchFunc(searchTerm) {
+ // Create two promises, one for the local search, one for the
+ // server-side search.
+ const client = MatrixClientPeg.get();
+ const serverSidePromise = serverSideSearch(searchTerm);
+ const localPromise = localSearchFunc(searchTerm);
+
+ // Wait for both promises to resolve.
+ await Promise.all([serverSidePromise, localPromise]);
+
+ // Get both search results.
+ const localResult = await localPromise;
+ const serverSideResult = await serverSidePromise;
+
+ // Combine the search results into one result.
+ const result = {};
+
+ // Our localResult and serverSideResult are both ordered by
+ // recency separetly, when we combine them the order might not
+ // be the right one so we need to sort them.
+ const compare = (a, b) => {
+ const aEvent = a.context.getEvent().event;
+ const bEvent = b.context.getEvent().event;
+
+ if (aEvent.origin_server_ts >
+ bEvent.origin_server_ts) return -1;
+ if (aEvent.origin_server_ts <
+ bEvent.origin_server_ts) return 1;
+ return 0;
+ };
+
+ result.count = localResult.count + serverSideResult.count;
+ result.results = localResult.results.concat(
+ serverSideResult.results).sort(compare);
+ result.highlights = localResult.highlights.concat(
+ serverSideResult.highlights);
+
+ return result;
+}
+
+async function localSearchFunc(searchTerm, roomId = undefined) {
+ const searchArgs = {
+ search_term: searchTerm,
+ before_limit: 1,
+ after_limit: 1,
+ order_by_recency: true,
+ };
+
+ if (roomId !== undefined) {
+ searchArgs.room_id = roomId;
+ }
+
+ const eventIndex = EventIndexPeg.get();
+
+ const localResult = await eventIndex.search(searchArgs);
+
+ const response = {
+ search_categories: {
+ room_events: localResult,
+ },
+ };
+
+ const emptyResult = {
+ results: [],
+ highlights: [],
+ };
+
+ const result = MatrixClientPeg.get()._processRoomEventsSearch(
+ emptyResult, response);
+
+ return result;
+}
+
function eventIndexSearch(term, roomId = undefined) {
- const combinedSearchFunc = async (searchTerm) => {
- // Create two promises, one for the local search, one for the
- // server-side search.
- const client = MatrixClientPeg.get();
- const serverSidePromise = serverSideSearch(searchTerm);
- const localPromise = localSearchFunc(searchTerm);
-
- // Wait for both promises to resolve.
- await Promise.all([serverSidePromise, localPromise]);
-
- // Get both search results.
- const localResult = await localPromise;
- const serverSideResult = await serverSidePromise;
-
- // Combine the search results into one result.
- const result = {};
-
- // Our localResult and serverSideResult are both ordered by
- // recency separetly, when we combine them the order might not
- // be the right one so we need to sort them.
- const compare = (a, b) => {
- const aEvent = a.context.getEvent().event;
- const bEvent = b.context.getEvent().event;
-
- if (aEvent.origin_server_ts >
- bEvent.origin_server_ts) return -1;
- if (aEvent.origin_server_ts <
- bEvent.origin_server_ts) return 1;
- return 0;
- };
-
- result.count = localResult.count + serverSideResult.count;
- result.results = localResult.results.concat(
- serverSideResult.results).sort(compare);
- result.highlights = localResult.highlights.concat(
- serverSideResult.highlights);
-
- return result;
- };
-
- const localSearchFunc = async (searchTerm, roomId = undefined) => {
- const searchArgs = {
- search_term: searchTerm,
- before_limit: 1,
- after_limit: 1,
- order_by_recency: true,
- };
-
- if (roomId !== undefined) {
- searchArgs.room_id = roomId;
- }
-
- const eventIndex = EventIndexPeg.get();
-
- const localResult = await eventIndex.search(searchArgs);
-
- const response = {
- search_categories: {
- room_events: localResult,
- },
- };
-
- const emptyResult = {
- results: [],
- highlights: [],
- };
-
- const result = MatrixClientPeg.get()._processRoomEventsSearch(
- emptyResult, response);
-
- return result;
- };
-
let searchPromise;
if (roomId !== undefined) {
From 1df28c75262e113ea0111a6cc0dccb74a512e93d Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Damir=20Jeli=C4=87?=
Date: Wed, 13 Nov 2019 10:30:38 +0100
Subject: [PATCH 079/334] Fix some lint errors.
---
src/EventIndexPeg.js | 12 +++++++-----
src/Lifecycle.js | 4 ++--
src/MatrixClientPeg.js | 2 --
src/Searching.js | 5 ++---
4 files changed, 11 insertions(+), 12 deletions(-)
diff --git a/src/EventIndexPeg.js b/src/EventIndexPeg.js
index 86fb889c7a..15d34ea230 100644
--- a/src/EventIndexPeg.js
+++ b/src/EventIndexPeg.js
@@ -32,7 +32,9 @@ class EventIndexPeg {
}
/**
- * Returns the current Event index object for the application. Can be null
+ * Get the current event index.
+ *
+ * @Returns The EventIndex object for the application. Can be null
* if the platform doesn't support event indexing.
*/
get() {
@@ -47,25 +49,25 @@ class EventIndexPeg {
const platform = PlatformPeg.get();
if (!platform.supportsEventIndexing()) return false;
- let index = new EventIndex();
+ const index = new EventIndex();
const userId = MatrixClientPeg.get().getUserId();
// TODO log errors here and return false if it errors out.
await index.init(userId);
this.index = index;
- return true
+ return true;
}
stop() {
if (this.index === null) return;
- index.stop();
+ this.index.stop();
this.index = null;
}
async deleteEventIndex() {
if (this.index === null) return;
- index.deleteEventIndex();
+ this.index.deleteEventIndex();
}
}
diff --git a/src/Lifecycle.js b/src/Lifecycle.js
index 1e68bcc062..aa900c81a1 100644
--- a/src/Lifecycle.js
+++ b/src/Lifecycle.js
@@ -633,10 +633,10 @@ function _clearStorage() {
baseUrl: "",
});
- const clear = async() => {
+ const clear = async () => {
await EventIndexPeg.deleteEventIndex();
await cli.clearStores();
- }
+ };
return clear();
}
diff --git a/src/MatrixClientPeg.js b/src/MatrixClientPeg.js
index 6c5b465bb0..bebb254afc 100644
--- a/src/MatrixClientPeg.js
+++ b/src/MatrixClientPeg.js
@@ -30,8 +30,6 @@ import {verificationMethods} from 'matrix-js-sdk/lib/crypto';
import MatrixClientBackedSettingsHandler from "./settings/handlers/MatrixClientBackedSettingsHandler";
import * as StorageManager from './utils/StorageManager';
import IdentityAuthClient from './IdentityAuthClient';
-import PlatformPeg from "./PlatformPeg";
-import EventIndexPeg from "./EventIndexPeg";
interface MatrixClientCreds {
homeserverUrl: string,
diff --git a/src/Searching.js b/src/Searching.js
index cff5742b04..84e73b91f4 100644
--- a/src/Searching.js
+++ b/src/Searching.js
@@ -26,7 +26,7 @@ function serverSideSearch(term, roomId = undefined) {
};
}
- let searchPromise = MatrixClientPeg.get().searchRoomEvents({
+ const searchPromise = MatrixClientPeg.get().searchRoomEvents({
filter: filter,
term: term,
});
@@ -37,7 +37,6 @@ function serverSideSearch(term, roomId = undefined) {
async function combinedSearchFunc(searchTerm) {
// Create two promises, one for the local search, one for the
// server-side search.
- const client = MatrixClientPeg.get();
const serverSidePromise = serverSideSearch(searchTerm);
const localPromise = localSearchFunc(searchTerm);
@@ -126,7 +125,7 @@ function eventIndexSearch(term, roomId = undefined) {
searchPromise = combinedSearchFunc(term);
}
- return searchPromise
+ return searchPromise;
}
export default function eventSearch(term, roomId = undefined) {
From 54b352f69cd1e9d82fff759c6838af1affca4f32 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Damir=20Jeli=C4=87?=
Date: Wed, 13 Nov 2019 10:37:20 +0100
Subject: [PATCH 080/334] MatrixChat: Fix the limited timeline checkpoint
adding.
---
src/EventIndexing.js | 9 ++-------
src/components/structures/MatrixChat.js | 4 +---
2 files changed, 3 insertions(+), 10 deletions(-)
diff --git a/src/EventIndexing.js b/src/EventIndexing.js
index ebd2ffe983..bf3f50690f 100644
--- a/src/EventIndexing.js
+++ b/src/EventIndexing.js
@@ -347,15 +347,10 @@ export default class EventIndexer {
console.log("Seshat: Stopping crawler function");
}
- async addCheckpointForLimitedRoom(roomId) {
+ async addCheckpointForLimitedRoom(room) {
const platform = PlatformPeg.get();
if (!platform.supportsEventIndexing()) return;
- if (!MatrixClientPeg.get().isRoomEncrypted(roomId)) return;
-
- const client = MatrixClientPeg.get();
- const room = client.getRoom(roomId);
-
- if (room === null) return;
+ if (!MatrixClientPeg.get().isRoomEncrypted(room.roomId)) return;
const timeline = room.getLiveTimeline();
const token = timeline.getPaginationToken("b");
diff --git a/src/components/structures/MatrixChat.js b/src/components/structures/MatrixChat.js
index 0d3d5abd55..ccc8b5e1d6 100644
--- a/src/components/structures/MatrixChat.js
+++ b/src/components/structures/MatrixChat.js
@@ -1223,8 +1223,6 @@ export default createReactClass({
* Called when the session is logged out
*/
_onLoggedOut: async function() {
- const platform = PlatformPeg.get();
-
this.notifyNewScreen('login');
this.setStateForNewView({
view: VIEWS.LOGIN,
@@ -1313,7 +1311,7 @@ export default createReactClass({
const eventIndex = EventIndexPeg.get();
if (eventIndex === null) return;
if (resetAllTimelines === true) return;
- await eventIndex.addCheckpointForLimitedRoom(roomId);
+ await eventIndex.addCheckpointForLimitedRoom(room);
});
cli.on('sync', function(state, prevState, data) {
From 80b28004e15821bd127bee3121baabd1cf6226a5 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Damir=20Jeli=C4=87?=
Date: Wed, 13 Nov 2019 11:02:54 +0100
Subject: [PATCH 081/334] Searching: Define the room id in the const object.
---
src/Searching.js | 1 +
1 file changed, 1 insertion(+)
diff --git a/src/Searching.js b/src/Searching.js
index 84e73b91f4..ee46a66fb8 100644
--- a/src/Searching.js
+++ b/src/Searching.js
@@ -79,6 +79,7 @@ async function localSearchFunc(searchTerm, roomId = undefined) {
before_limit: 1,
after_limit: 1,
order_by_recency: true,
+ room_id: undefined,
};
if (roomId !== undefined) {
From f453fea24acf110d0b297d8374234a8c873bec80 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Damir=20Jeli=C4=87?=
Date: Wed, 13 Nov 2019 12:25:16 +0100
Subject: [PATCH 082/334] BasePlatform: Move the event indexing methods into a
separate class.
---
src/BaseEventIndexManager.js | 208 +++++++++++++++++++++++++++++++++++
src/BasePlatform.js | 41 +------
src/EventIndexPeg.js | 6 +-
src/EventIndexing.js | 62 +++++------
4 files changed, 246 insertions(+), 71 deletions(-)
create mode 100644 src/BaseEventIndexManager.js
diff --git a/src/BaseEventIndexManager.js b/src/BaseEventIndexManager.js
new file mode 100644
index 0000000000..cd7a735e8d
--- /dev/null
+++ b/src/BaseEventIndexManager.js
@@ -0,0 +1,208 @@
+// @flow
+
+/*
+Copyright 2019 New Vector Ltd
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+export interface MatrixEvent {
+ type: string;
+ sender: string;
+ content: {};
+ event_id: string;
+ origin_server_ts: number;
+ unsigned: ?{};
+ room_id: string;
+}
+
+export interface MatrixProfile {
+ avatar_url: string;
+ displayname: string;
+}
+
+export interface CrawlerCheckpoint {
+ roomId: string;
+ token: string;
+ fullCrawl: boolean;
+ direction: string;
+}
+
+export interface ResultContext {
+ events_before: [MatrixEvent];
+ events_after: [MatrixEvent];
+ profile_info: Map;
+}
+
+export interface ResultsElement {
+ rank: number;
+ result: MatrixEvent;
+ context: ResultContext;
+}
+
+export interface SearchResult {
+ count: number;
+ results: [ResultsElement];
+ highlights: [string];
+}
+
+export interface SearchArgs {
+ search_term: string;
+ before_limit: number;
+ after_limit: number;
+ order_by_recency: boolean;
+ room_id: ?string;
+}
+
+export interface HistoricEvent {
+ event: MatrixEvent;
+ profile: MatrixProfile;
+}
+
+/**
+ * Base class for classes that provide platform-specific event indexing.
+ *
+ * Instances of this class are provided by the application.
+ */
+export default class BaseEventIndexManager {
+ /**
+ * Initialize the event index for the given user.
+ *
+ * @param {string} userId The unique identifier of the logged in user that
+ * owns the index.
+ *
+ * @return {Promise} A promise that will resolve when the event index is
+ * initialized.
+ */
+ async initEventIndex(userId: string): Promise<> {
+ throw new Error("Unimplemented");
+ }
+
+ /**
+ * Queue up an event to be added to the index.
+ *
+ * @param {MatrixEvent} ev The event that should be added to the index.
+ * @param {MatrixProfile} profile The profile of the event sender at the
+ * time of the event receival.
+ *
+ * @return {Promise} A promise that will resolve when the was queued up for
+ * addition.
+ */
+ async addEventToIndex(ev: MatrixEvent, profile: MatrixProfile): Promise<> {
+ throw new Error("Unimplemented");
+ }
+
+ /**
+ * Check if our event index is empty.
+ *
+ * @return {Promise} A promise that will resolve to true if the
+ * event index is empty, false otherwise.
+ */
+ indexIsEmpty(): Promise {
+ throw new Error("Unimplemented");
+ }
+
+ /**
+ * Commit the previously queued up events to the index.
+ *
+ * @return {Promise} A promise that will resolve once the queued up events
+ * were added to the index.
+ */
+ async commitLiveEvents(): Promise<> {
+ throw new Error("Unimplemented");
+ }
+
+ /**
+ * Search the event index using the given term for matching events.
+ *
+ * @param {SearchArgs} searchArgs The search configuration sets what should
+ * be searched for and what should be contained in the search result.
+ *
+ * @return {Promise<[SearchResult]>} A promise that will resolve to an array
+ * of search results once the search is done.
+ */
+ async searchEventIndex(searchArgs: SearchArgs): Promise {
+ throw new Error("Unimplemented");
+ }
+
+ /**
+ * Add events from the room history to the event index.
+ *
+ * This is used to add a batch of events to the index.
+ *
+ * @param {[HistoricEvent]} events The list of events and profiles that
+ * should be added to the event index.
+ * @param {[CrawlerCheckpoint]} checkpoint A new crawler checkpoint that
+ * should be stored in the index which should be used to continue crawling
+ * the room.
+ * @param {[CrawlerCheckpoint]} oldCheckpoint The checkpoint that was used
+ * to fetch the current batch of events. This checkpoint will be removed
+ * from the index.
+ *
+ * @return {Promise} A promise that will resolve to true if all the events
+ * were already added to the index, false otherwise.
+ */
+ async addHistoricEvents(
+ events: [HistoricEvent],
+ checkpoint: CrawlerCheckpoint | null = null,
+ oldCheckpoint: CrawlerCheckpoint | null = null,
+ ): Promise {
+ throw new Error("Unimplemented");
+ }
+
+ /**
+ * Add a new crawler checkpoint to the index.
+ *
+ * @param {CrawlerCheckpoint} checkpoint The checkpoint that should be added
+ * to the index.
+ *
+ * @return {Promise} A promise that will resolve once the checkpoint has
+ * been stored.
+ */
+ async addCrawlerCheckpoint(checkpoint: CrawlerCheckpoint): Promise<> {
+ throw new Error("Unimplemented");
+ }
+
+ /**
+ * Add a new crawler checkpoint to the index.
+ *
+ * @param {CrawlerCheckpoint} checkpoint The checkpoint that should be
+ * removed from the index.
+ *
+ * @return {Promise} A promise that will resolve once the checkpoint has
+ * been removed.
+ */
+ async removeCrawlerCheckpoint(checkpoint: CrawlerCheckpoint): Promise<> {
+ throw new Error("Unimplemented");
+ }
+
+ /**
+ * Load the stored checkpoints from the index.
+ *
+ * @return {Promise<[CrawlerCheckpoint]>} A promise that will resolve to an
+ * array of crawler checkpoints once they have been loaded from the index.
+ */
+ async loadCheckpoints(): Promise<[CrawlerCheckpoint]> {
+ throw new Error("Unimplemented");
+ }
+
+ /**
+ * Delete our current event index.
+ *
+ * @return {Promise} A promise that will resolve once the event index has
+ * been deleted.
+ */
+ async deleteEventIndex(): Promise<> {
+ throw new Error("Unimplemented");
+ }
+}
diff --git a/src/BasePlatform.js b/src/BasePlatform.js
index 7f5df822e4..582ac24cb0 100644
--- a/src/BasePlatform.js
+++ b/src/BasePlatform.js
@@ -19,6 +19,7 @@ limitations under the License.
*/
import dis from './dispatcher';
+import BaseEventIndexManager from './BaseEventIndexManager';
/**
* Base class for classes that provide platform-specific functionality
@@ -152,43 +153,7 @@ export default class BasePlatform {
throw new Error("Unimplemented");
}
- supportsEventIndexing(): boolean {
- return false;
- }
-
- async initEventIndex(userId: string): boolean {
- throw new Error("Unimplemented");
- }
-
- async addEventToIndex(ev: {}, profile: {}): void {
- throw new Error("Unimplemented");
- }
-
- indexIsEmpty(): Promise {
- throw new Error("Unimplemented");
- }
-
- async commitLiveEvents(): void {
- throw new Error("Unimplemented");
- }
-
- async searchEventIndex(term: string): Promise<{}> {
- throw new Error("Unimplemented");
- }
-
- async addHistoricEvents(events: [], checkpoint: {} = null, oldCheckpoint: {} = null): Promise {
- throw new Error("Unimplemented");
- }
-
- async addCrawlerCheckpoint(checkpoint: {}): Promise<> {
- throw new Error("Unimplemented");
- }
-
- async removeCrawlerCheckpoint(checkpoint: {}): Promise<> {
- throw new Error("Unimplemented");
- }
-
- async deleteEventIndex(): Promise<> {
- throw new Error("Unimplemented");
+ getEventIndexingManager(): BaseEventIndexManager | null {
+ return null;
}
}
diff --git a/src/EventIndexPeg.js b/src/EventIndexPeg.js
index 15d34ea230..bec3f075b6 100644
--- a/src/EventIndexPeg.js
+++ b/src/EventIndexPeg.js
@@ -46,9 +46,11 @@ class EventIndexPeg {
* otherwise.
*/
async init() {
- const platform = PlatformPeg.get();
- if (!platform.supportsEventIndexing()) return false;
+ const indexManager = PlatformPeg.get().getEventIndexingManager();
+ console.log("Initializing event index, got {}", indexManager);
+ if (indexManager === null) return false;
+ console.log("Seshat: Creatingnew EventIndex object", indexManager);
const index = new EventIndex();
const userId = MatrixClientPeg.get().getUserId();
diff --git a/src/EventIndexing.js b/src/EventIndexing.js
index bf3f50690f..60482b76b5 100644
--- a/src/EventIndexing.js
+++ b/src/EventIndexing.js
@@ -31,19 +31,19 @@ export default class EventIndexer {
}
async init(userId) {
- const platform = PlatformPeg.get();
- if (!platform.supportsEventIndexing()) return false;
- platform.initEventIndex(userId);
+ const indexManager = PlatformPeg.get().getEventIndexingManager();
+ if (indexManager === null) return false;
+ indexManager.initEventIndex(userId);
return true;
}
async onSync(state, prevState, data) {
- const platform = PlatformPeg.get();
- if (!platform.supportsEventIndexing()) return;
+ const indexManager = PlatformPeg.get().getEventIndexingManager();
+ if (indexManager === null) return;
if (prevState === null && state === "PREPARED") {
// Load our stored checkpoints, if any.
- this.crawlerChekpoints = await platform.loadCheckpoints();
+ this.crawlerChekpoints = await indexManager.loadCheckpoints();
console.log("Seshat: Loaded checkpoints",
this.crawlerChekpoints);
return;
@@ -85,8 +85,8 @@ export default class EventIndexer {
direction: "f",
};
- await platform.addCrawlerCheckpoint(backCheckpoint);
- await platform.addCrawlerCheckpoint(forwardCheckpoint);
+ await indexManager.addCrawlerCheckpoint(backCheckpoint);
+ await indexManager.addCrawlerCheckpoint(forwardCheckpoint);
this.crawlerChekpoints.push(backCheckpoint);
this.crawlerChekpoints.push(forwardCheckpoint);
}));
@@ -95,7 +95,7 @@ export default class EventIndexer {
// If our indexer is empty we're most likely running Riot the
// first time with indexing support or running it with an
// initial sync. Add checkpoints to crawl our encrypted rooms.
- const eventIndexWasEmpty = await platform.isEventIndexEmpty();
+ const eventIndexWasEmpty = await indexManager.isEventIndexEmpty();
if (eventIndexWasEmpty) await addInitialCheckpoints();
// Start our crawler.
@@ -107,14 +107,14 @@ export default class EventIndexer {
// A sync was done, presumably we queued up some live events,
// commit them now.
console.log("Seshat: Committing events");
- await platform.commitLiveEvents();
+ await indexManager.commitLiveEvents();
return;
}
}
async onRoomTimeline(ev, room, toStartOfTimeline, removed, data) {
- const platform = PlatformPeg.get();
- if (!platform.supportsEventIndexing()) return;
+ const indexManager = PlatformPeg.get().getEventIndexingManager();
+ if (indexManager === null) return;
// We only index encrypted rooms locally.
if (!MatrixClientPeg.get().isRoomEncrypted(room.roomId)) return;
@@ -139,8 +139,8 @@ export default class EventIndexer {
}
async onEventDecrypted(ev, err) {
- const platform = PlatformPeg.get();
- if (!platform.supportsEventIndexing()) return;
+ const indexManager = PlatformPeg.get().getEventIndexingManager();
+ if (indexManager === null) return;
const eventId = ev.getId();
@@ -151,8 +151,8 @@ export default class EventIndexer {
}
async addLiveEventToIndex(ev) {
- const platform = PlatformPeg.get();
- if (!platform.supportsEventIndexing()) return;
+ const indexManager = PlatformPeg.get().getEventIndexingManager();
+ if (indexManager === null) return;
if (["m.room.message", "m.room.name", "m.room.topic"]
.indexOf(ev.getType()) == -1) {
@@ -165,7 +165,7 @@ export default class EventIndexer {
avatar_url: ev.sender.getMxcAvatarUrl(),
};
- platform.addEventToIndex(e, profile);
+ indexManager.addEventToIndex(e, profile);
}
async crawlerFunc(handle) {
@@ -180,7 +180,7 @@ export default class EventIndexer {
console.log("Seshat: Started crawler function");
const client = MatrixClientPeg.get();
- const platform = PlatformPeg.get();
+ const indexManager = PlatformPeg.get().getEventIndexingManager();
handle.cancel = () => {
cancelled = true;
@@ -223,14 +223,14 @@ export default class EventIndexer {
} catch (e) {
console.log("Seshat: Error crawling events:", e);
this.crawlerChekpoints.push(checkpoint);
- continue
+ continue;
}
if (res.chunk.length === 0) {
console.log("Seshat: Done with the checkpoint", checkpoint);
// We got to the start/end of our timeline, lets just
// delete our checkpoint and go back to sleep.
- await platform.removeCrawlerCheckpoint(checkpoint);
+ await indexManager.removeCrawlerCheckpoint(checkpoint);
continue;
}
@@ -323,7 +323,7 @@ export default class EventIndexer {
);
try {
- const eventsAlreadyAdded = await platform.addHistoricEvents(
+ const eventsAlreadyAdded = await indexManager.addHistoricEvents(
events, newCheckpoint, checkpoint);
// If all events were already indexed we assume that we catched
// up with our index and don't need to crawl the room further.
@@ -332,7 +332,7 @@ export default class EventIndexer {
if (eventsAlreadyAdded === true && newCheckpoint.fullCrawl !== true) {
console.log("Seshat: Checkpoint had already all events",
"added, stopping the crawl", checkpoint);
- await platform.removeCrawlerCheckpoint(newCheckpoint);
+ await indexManager.removeCrawlerCheckpoint(newCheckpoint);
} else {
this.crawlerChekpoints.push(newCheckpoint);
}
@@ -348,8 +348,8 @@ export default class EventIndexer {
}
async addCheckpointForLimitedRoom(room) {
- const platform = PlatformPeg.get();
- if (!platform.supportsEventIndexing()) return;
+ const indexManager = PlatformPeg.get().getEventIndexingManager();
+ if (indexManager === null) return;
if (!MatrixClientPeg.get().isRoomEncrypted(room.roomId)) return;
const timeline = room.getLiveTimeline();
@@ -372,19 +372,19 @@ export default class EventIndexer {
console.log("Seshat: Added checkpoint because of a limited timeline",
backwardsCheckpoint, forwardsCheckpoint);
- await platform.addCrawlerCheckpoint(backwardsCheckpoint);
- await platform.addCrawlerCheckpoint(forwardsCheckpoint);
+ await indexManager.addCrawlerCheckpoint(backwardsCheckpoint);
+ await indexManager.addCrawlerCheckpoint(forwardsCheckpoint);
this.crawlerChekpoints.push(backwardsCheckpoint);
this.crawlerChekpoints.push(forwardsCheckpoint);
}
async deleteEventIndex() {
- const platform = PlatformPeg.get();
- if (platform.supportsEventIndexing()) {
+ const indexManager = PlatformPeg.get().getEventIndexingManager();
+ if (indexManager !== null) {
console.log("Seshat: Deleting event index.");
this.crawlerRef.cancel();
- await platform.deleteEventIndex();
+ await indexManager.deleteEventIndex();
}
}
@@ -400,7 +400,7 @@ export default class EventIndexer {
}
async search(searchArgs) {
- const platform = PlatformPeg.get();
- return platform.searchEventIndex(searchArgs)
+ const indexManager = PlatformPeg.get().getEventIndexingManager();
+ return indexManager.searchEventIndex(searchArgs);
}
}
From 1316e04776b90ec7cc3d7770822b400795de171b Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Damir=20Jeli=C4=87?=
Date: Wed, 13 Nov 2019 15:23:08 +0100
Subject: [PATCH 083/334] EventIndexing: Check if there is a room when
resetting the timeline.
---
src/EventIndexing.js | 13 ++-----------
src/components/structures/MatrixChat.js | 4 ++--
2 files changed, 4 insertions(+), 13 deletions(-)
diff --git a/src/EventIndexing.js b/src/EventIndexing.js
index 60482b76b5..4817df4b32 100644
--- a/src/EventIndexing.js
+++ b/src/EventIndexing.js
@@ -347,7 +347,7 @@ export default class EventIndexer {
console.log("Seshat: Stopping crawler function");
}
- async addCheckpointForLimitedRoom(room) {
+ async onLimitedTimeline(room) {
const indexManager = PlatformPeg.get().getEventIndexingManager();
if (indexManager === null) return;
if (!MatrixClientPeg.get().isRoomEncrypted(room.roomId)) return;
@@ -362,21 +362,12 @@ export default class EventIndexer {
direction: "b",
};
- const forwardsCheckpoint = {
- roomId: room.roomId,
- token: token,
- fullCrawl: false,
- direction: "f",
- };
-
console.log("Seshat: Added checkpoint because of a limited timeline",
- backwardsCheckpoint, forwardsCheckpoint);
+ backwardsCheckpoint);
await indexManager.addCrawlerCheckpoint(backwardsCheckpoint);
- await indexManager.addCrawlerCheckpoint(forwardsCheckpoint);
this.crawlerChekpoints.push(backwardsCheckpoint);
- this.crawlerChekpoints.push(forwardsCheckpoint);
}
async deleteEventIndex() {
diff --git a/src/components/structures/MatrixChat.js b/src/components/structures/MatrixChat.js
index ccc8b5e1d6..f78bb5c168 100644
--- a/src/components/structures/MatrixChat.js
+++ b/src/components/structures/MatrixChat.js
@@ -1310,8 +1310,8 @@ export default createReactClass({
cli.on("Room.timelineReset", async (room, timelineSet, resetAllTimelines) => {
const eventIndex = EventIndexPeg.get();
if (eventIndex === null) return;
- if (resetAllTimelines === true) return;
- await eventIndex.addCheckpointForLimitedRoom(room);
+ if (room === null) return;
+ await eventIndex.onLimitedTimeline(room);
});
cli.on('sync', function(state, prevState, data) {
From ab7f34b45a66748fde1ee361faa7f31bc86db0e4 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Damir=20Jeli=C4=87?=
Date: Wed, 13 Nov 2019 15:26:27 +0100
Subject: [PATCH 084/334] EventIndexing: Don't mention Seshat in the logs.
---
src/EventIndexing.js | 34 +++++++++++++++++-----------------
1 file changed, 17 insertions(+), 17 deletions(-)
diff --git a/src/EventIndexing.js b/src/EventIndexing.js
index 4817df4b32..f67d4c9eb3 100644
--- a/src/EventIndexing.js
+++ b/src/EventIndexing.js
@@ -44,7 +44,7 @@ export default class EventIndexer {
if (prevState === null && state === "PREPARED") {
// Load our stored checkpoints, if any.
this.crawlerChekpoints = await indexManager.loadCheckpoints();
- console.log("Seshat: Loaded checkpoints",
+ console.log("EventIndex: Loaded checkpoints",
this.crawlerChekpoints);
return;
}
@@ -62,7 +62,7 @@ export default class EventIndexer {
// rooms can use the search provided by the Homeserver.
const encryptedRooms = rooms.filter(isRoomEncrypted);
- console.log("Seshat: Adding initial crawler checkpoints");
+ console.log("EventIndex: Adding initial crawler checkpoints");
// Gather the prev_batch tokens and create checkpoints for
// our message crawler.
@@ -70,7 +70,7 @@ export default class EventIndexer {
const timeline = room.getLiveTimeline();
const token = timeline.getPaginationToken("b");
- console.log("Seshat: Got token for indexer",
+ console.log("EventIndex: Got token for indexer",
room.roomId, token);
const backCheckpoint = {
@@ -106,7 +106,7 @@ export default class EventIndexer {
if (prevState === "SYNCING" && state === "SYNCING") {
// A sync was done, presumably we queued up some live events,
// commit them now.
- console.log("Seshat: Committing events");
+ console.log("EventIndex: Committing events");
await indexManager.commitLiveEvents();
return;
}
@@ -177,7 +177,7 @@ export default class EventIndexer {
let cancelled = false;
- console.log("Seshat: Started crawler function");
+ console.log("EventIndex: Started crawler function");
const client = MatrixClientPeg.get();
const indexManager = PlatformPeg.get().getEventIndexingManager();
@@ -192,10 +192,10 @@ export default class EventIndexer {
// here.
await sleep(this._crawler_timeout);
- console.log("Seshat: Running the crawler loop.");
+ console.log("EventIndex: Running the crawler loop.");
if (cancelled) {
- console.log("Seshat: Cancelling the crawler.");
+ console.log("EventIndex: Cancelling the crawler.");
break;
}
@@ -207,7 +207,7 @@ export default class EventIndexer {
continue;
}
- console.log("Seshat: crawling using checkpoint", checkpoint);
+ console.log("EventIndex: crawling using checkpoint", checkpoint);
// We have a checkpoint, let us fetch some messages, again, very
// conservatively to not bother our Homeserver too much.
@@ -221,13 +221,13 @@ export default class EventIndexer {
checkpoint.roomId, checkpoint.token, 100,
checkpoint.direction);
} catch (e) {
- console.log("Seshat: Error crawling events:", e);
+ console.log("EventIndex: Error crawling events:", e);
this.crawlerChekpoints.push(checkpoint);
continue;
}
if (res.chunk.length === 0) {
- console.log("Seshat: Done with the checkpoint", checkpoint);
+ console.log("EventIndex: Done with the checkpoint", checkpoint);
// We got to the start/end of our timeline, lets just
// delete our checkpoint and go back to sleep.
await indexManager.removeCrawlerCheckpoint(checkpoint);
@@ -289,7 +289,7 @@ export default class EventIndexer {
// stage?
const filteredEvents = matrixEvents.filter(isValidEvent);
- // Let us convert the events back into a format that Seshat can
+ // Let us convert the events back into a format that EventIndex can
// consume.
const events = filteredEvents.map((ev) => {
const jsonEvent = ev.toJSON();
@@ -317,7 +317,7 @@ export default class EventIndexer {
};
console.log(
- "Seshat: Crawled room",
+ "EventIndex: Crawled room",
client.getRoom(checkpoint.roomId).name,
"and fetched", events.length, "events.",
);
@@ -330,21 +330,21 @@ export default class EventIndexer {
// Let us delete the checkpoint in that case, otherwise push
// the new checkpoint to be used by the crawler.
if (eventsAlreadyAdded === true && newCheckpoint.fullCrawl !== true) {
- console.log("Seshat: Checkpoint had already all events",
+ console.log("EventIndex: Checkpoint had already all events",
"added, stopping the crawl", checkpoint);
await indexManager.removeCrawlerCheckpoint(newCheckpoint);
} else {
this.crawlerChekpoints.push(newCheckpoint);
}
} catch (e) {
- console.log("Seshat: Error durring a crawl", e);
+ console.log("EventIndex: Error durring a crawl", e);
// An error occured, put the checkpoint back so we
// can retry.
this.crawlerChekpoints.push(checkpoint);
}
}
- console.log("Seshat: Stopping crawler function");
+ console.log("EventIndex: Stopping crawler function");
}
async onLimitedTimeline(room) {
@@ -362,7 +362,7 @@ export default class EventIndexer {
direction: "b",
};
- console.log("Seshat: Added checkpoint because of a limited timeline",
+ console.log("EventIndex: Added checkpoint because of a limited timeline",
backwardsCheckpoint);
await indexManager.addCrawlerCheckpoint(backwardsCheckpoint);
@@ -373,7 +373,7 @@ export default class EventIndexer {
async deleteEventIndex() {
const indexManager = PlatformPeg.get().getEventIndexingManager();
if (indexManager !== null) {
- console.log("Seshat: Deleting event index.");
+ console.log("EventIndex: Deleting event index.");
this.crawlerRef.cancel();
await indexManager.deleteEventIndex();
}
From c33f5ba0ca8292116e1623a9d0c932aac62479a0 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Damir=20Jeli=C4=87?=
Date: Wed, 13 Nov 2019 15:39:06 +0100
Subject: [PATCH 085/334] BaseEventIndexManager: Add a method to perform
runtime checks for indexing support.
---
src/BaseEventIndexManager.js | 13 +++++++++++++
1 file changed, 13 insertions(+)
diff --git a/src/BaseEventIndexManager.js b/src/BaseEventIndexManager.js
index cd7a735e8d..a74eac658a 100644
--- a/src/BaseEventIndexManager.js
+++ b/src/BaseEventIndexManager.js
@@ -75,6 +75,19 @@ export interface HistoricEvent {
* Instances of this class are provided by the application.
*/
export default class BaseEventIndexManager {
+ /**
+ * Does our EventIndexManager support event indexing.
+ *
+ * If an EventIndexManager imlpementor has runtime dependencies that
+ * optionally enable event indexing they may override this method to perform
+ * the necessary runtime checks here.
+ *
+ * @return {Promise} A promise that will resolve to true if event indexing
+ * is supported, false otherwise.
+ */
+ async supportsEventIndexing(): Promise {
+ return true;
+ }
/**
* Initialize the event index for the given user.
*
From bf558b46c3cfc9ee7b19dbe7a92ac79ed118e498 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Damir=20Jeli=C4=87?=
Date: Wed, 13 Nov 2019 15:39:39 +0100
Subject: [PATCH 086/334] EventIndexPeg: Clean up the event index
initialization.
---
src/EventIndexPeg.js | 20 +++++++++++++++-----
1 file changed, 15 insertions(+), 5 deletions(-)
diff --git a/src/EventIndexPeg.js b/src/EventIndexPeg.js
index bec3f075b6..3ce88339eb 100644
--- a/src/EventIndexPeg.js
+++ b/src/EventIndexPeg.js
@@ -47,15 +47,25 @@ class EventIndexPeg {
*/
async init() {
const indexManager = PlatformPeg.get().getEventIndexingManager();
- console.log("Initializing event index, got {}", indexManager);
if (indexManager === null) return false;
- console.log("Seshat: Creatingnew EventIndex object", indexManager);
- const index = new EventIndex();
+ if (await indexManager.supportsEventIndexing() !== true) {
+ console.log("EventIndex: Platform doesn't support event indexing,",
+ "not initializing.");
+ return false;
+ }
+ const index = new EventIndex();
const userId = MatrixClientPeg.get().getUserId();
- // TODO log errors here and return false if it errors out.
- await index.init(userId);
+
+ try {
+ await index.init(userId);
+ } catch (e) {
+ console.log("EventIndex: Error initializing the event index", e);
+ }
+
+ console.log("EventIndex: Successfully initialized the event index");
+
this.index = index;
return true;
From c26df9d9efc836b1a6b5d660edd702448a22b3a9 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Damir=20Jeli=C4=87?=
Date: Wed, 13 Nov 2019 15:57:12 +0100
Subject: [PATCH 087/334] EventIndexing: Fix a typo.
---
src/EventIndexing.js | 20 ++++++++++----------
1 file changed, 10 insertions(+), 10 deletions(-)
diff --git a/src/EventIndexing.js b/src/EventIndexing.js
index f67d4c9eb3..af77979040 100644
--- a/src/EventIndexing.js
+++ b/src/EventIndexing.js
@@ -22,7 +22,7 @@ import MatrixClientPeg from "./MatrixClientPeg";
*/
export default class EventIndexer {
constructor() {
- this.crawlerChekpoints = [];
+ this.crawlerCheckpoints = [];
// The time that the crawler will wait between /rooms/{room_id}/messages
// requests
this._crawler_timeout = 3000;
@@ -43,9 +43,9 @@ export default class EventIndexer {
if (prevState === null && state === "PREPARED") {
// Load our stored checkpoints, if any.
- this.crawlerChekpoints = await indexManager.loadCheckpoints();
+ this.crawlerCheckpoints = await indexManager.loadCheckpoints();
console.log("EventIndex: Loaded checkpoints",
- this.crawlerChekpoints);
+ this.crawlerCheckpoints);
return;
}
@@ -87,8 +87,8 @@ export default class EventIndexer {
await indexManager.addCrawlerCheckpoint(backCheckpoint);
await indexManager.addCrawlerCheckpoint(forwardCheckpoint);
- this.crawlerChekpoints.push(backCheckpoint);
- this.crawlerChekpoints.push(forwardCheckpoint);
+ this.crawlerCheckpoints.push(backCheckpoint);
+ this.crawlerCheckpoints.push(forwardCheckpoint);
}));
};
@@ -199,7 +199,7 @@ export default class EventIndexer {
break;
}
- const checkpoint = this.crawlerChekpoints.shift();
+ const checkpoint = this.crawlerCheckpoints.shift();
/// There is no checkpoint available currently, one may appear if
// a sync with limited room timelines happens, so go back to sleep.
@@ -222,7 +222,7 @@ export default class EventIndexer {
checkpoint.direction);
} catch (e) {
console.log("EventIndex: Error crawling events:", e);
- this.crawlerChekpoints.push(checkpoint);
+ this.crawlerCheckpoints.push(checkpoint);
continue;
}
@@ -334,13 +334,13 @@ export default class EventIndexer {
"added, stopping the crawl", checkpoint);
await indexManager.removeCrawlerCheckpoint(newCheckpoint);
} else {
- this.crawlerChekpoints.push(newCheckpoint);
+ this.crawlerCheckpoints.push(newCheckpoint);
}
} catch (e) {
console.log("EventIndex: Error durring a crawl", e);
// An error occured, put the checkpoint back so we
// can retry.
- this.crawlerChekpoints.push(checkpoint);
+ this.crawlerCheckpoints.push(checkpoint);
}
}
@@ -367,7 +367,7 @@ export default class EventIndexer {
await indexManager.addCrawlerCheckpoint(backwardsCheckpoint);
- this.crawlerChekpoints.push(backwardsCheckpoint);
+ this.crawlerCheckpoints.push(backwardsCheckpoint);
}
async deleteEventIndex() {
From f2f8a82876a4dc46701ee71b79fa47bf697f7002 Mon Sep 17 00:00:00 2001
From: Jeff Huang
Date: Wed, 13 Nov 2019 03:31:51 +0000
Subject: [PATCH 088/334] Translated using Weblate (Chinese (Traditional))
Currently translated at 100.0% (1898 of 1898 strings)
Translation: Riot Web/matrix-react-sdk
Translate-URL: https://translate.riot.im/projects/riot-web/matrix-react-sdk/zh_Hant/
---
src/i18n/strings/zh_Hant.json | 36 ++++++++++++++++++++++++++++++++++-
1 file changed, 35 insertions(+), 1 deletion(-)
diff --git a/src/i18n/strings/zh_Hant.json b/src/i18n/strings/zh_Hant.json
index 185026aad5..a4898bd328 100644
--- a/src/i18n/strings/zh_Hant.json
+++ b/src/i18n/strings/zh_Hant.json
@@ -2284,5 +2284,39 @@
"You cancelled": "您已取消",
"%(name)s cancelled": "%(name)s 已取消",
"%(name)s wants to verify": "%(name)s 想要驗證",
- "You sent a verification request": "您已傳送了驗證請求"
+ "You sent a verification request": "您已傳送了驗證請求",
+ "Try out new ways to ignore people (experimental)": "試用新的方式來忽略人們(實驗性)",
+ "My Ban List": "我的封鎖清單",
+ "This is your list of users/servers you have blocked - don't leave the room!": "這是您已封鎖的的使用者/伺服器清單,不要離開聊天室!",
+ "Ignored/Blocked": "已忽略/已封鎖",
+ "Error adding ignored user/server": "新增要忽略的使用者/伺服器錯誤",
+ "Something went wrong. Please try again or view your console for hints.": "有東西出問題了。請重試或檢視您的主控臺以取得更多資訊。",
+ "Error subscribing to list": "訂閱清單發生錯誤",
+ "Please verify the room ID or alias and try again.": "請驗證聊天室 ID 或別名並再試一次。",
+ "Error removing ignored user/server": "移除要忽略的使用者/伺服器發生錯誤",
+ "Error unsubscribing from list": "從清單取消訂閱時發生錯誤",
+ "Please try again or view your console for hints.": "請重試或檢視您的主控臺以取得更多資訊。",
+ "None": "無",
+ "Ban list rules - %(roomName)s": "封鎖清單規則 - %(roomName)s",
+ "Server rules": "伺服器規則",
+ "User rules": "使用者規則",
+ "You have not ignored anyone.": "您尚未忽略任何人。",
+ "You are currently ignoring:": "您目前正在忽略:",
+ "You are not subscribed to any lists": "您尚未訂閱任何清單",
+ "Unsubscribe": "取消訂閱",
+ "View rules": "檢視規則",
+ "You are currently subscribed to:": "您目前已訂閱:",
+ "⚠ These settings are meant for advanced users.": "⚠ 這些設定適用於進階使用者。",
+ "Add users and servers you want to ignore here. Use asterisks to have Riot match any characters. For example, @bot:* would ignore all users that have the name 'bot' on any server.": "在此新增您想要忽略的使用者與伺服器。使用星號以讓 Riot 核對所有字元。舉例來說,@bot:* 將會忽略在任何伺服器上,所有有 'bot' 名稱的使用者。",
+ "Ignoring people is done through ban lists which contain rules for who to ban. Subscribing to a ban list means the users/servers blocked by that list will be hidden from you.": "忽略人們已透過封鎖清單完成,其中包含了誰要被封鎖的規則。訂閱封鎖清單代表被此清單封鎖的使用者/伺服器會對您隱藏。",
+ "Personal ban list": "個人封鎖清單",
+ "Your personal ban list holds all the users/servers you personally don't want to see messages from. After ignoring your first user/server, a new room will show up in your room list named 'My Ban List' - stay in this room to keep the ban list in effect.": "您的個人封鎖清單包含了您個人不想要看到的所有使用者/伺服器。在忽略您的第一個使用者/伺服器後,您的聊天室清單中會出現新的聊天室,其名為「我的封鎖清單」,留在這個聊天室裡面以讓封鎖清單生效。",
+ "Server or user ID to ignore": "要忽略的伺服器或使用者 ID",
+ "eg: @bot:* or example.org": "例子:@bot:* 或 example.org",
+ "Subscribed lists": "已訂閱的清單",
+ "Subscribing to a ban list will cause you to join it!": "訂閱封鎖清單會讓您加入它!",
+ "If this isn't what you want, please use a different tool to ignore users.": "如果這不是您想要的,請使用不同的工具來忽略使用者。",
+ "Room ID or alias of ban list": "聊天室 ID 或封鎖清單的別名",
+ "Subscribe": "訂閱",
+ "You have ignored this user, so their message is hidden. Show anyways.": "您已經忽略了這個使用者,所以他們的訊息會隱藏。無論如何都顯示。"
}
From 95b8e83cd3f97cf6ce3f2ff144bc505518e155c3 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?K=C3=A9vin=20C?=
Date: Wed, 13 Nov 2019 10:31:31 +0000
Subject: [PATCH 089/334] Translated using Weblate (French)
Currently translated at 100.0% (1898 of 1898 strings)
Translation: Riot Web/matrix-react-sdk
Translate-URL: https://translate.riot.im/projects/riot-web/matrix-react-sdk/fr/
---
src/i18n/strings/fr.json | 36 +++++++++++++++++++++++++++++++++++-
1 file changed, 35 insertions(+), 1 deletion(-)
diff --git a/src/i18n/strings/fr.json b/src/i18n/strings/fr.json
index 40840c8d58..f4e889d955 100644
--- a/src/i18n/strings/fr.json
+++ b/src/i18n/strings/fr.json
@@ -2291,5 +2291,39 @@
"You cancelled": "Vous avez annulé",
"%(name)s cancelled": "%(name)s a annulé",
"%(name)s wants to verify": "%(name)s veut vérifier",
- "You sent a verification request": "Vous avez envoyé une demande de vérification"
+ "You sent a verification request": "Vous avez envoyé une demande de vérification",
+ "Try out new ways to ignore people (experimental)": "Essayez de nouvelles façons d’ignorer les gens (expérimental)",
+ "My Ban List": "Ma liste de bannissement",
+ "This is your list of users/servers you have blocked - don't leave the room!": "C’est la liste des utilisateurs/serveurs que vous avez bloqués − ne quittez pas le salon !",
+ "Ignored/Blocked": "Ignoré/bloqué",
+ "Error adding ignored user/server": "Erreur lors de l’ajout de l’utilisateur/du serveur ignoré",
+ "Something went wrong. Please try again or view your console for hints.": "Une erreur est survenue. Réessayez ou consultez votre console pour des indices.",
+ "Error subscribing to list": "Erreur lors de l’inscription à la liste",
+ "Please verify the room ID or alias and try again.": "Vérifiez l’identifiant ou l’alias du salon et réessayez.",
+ "Error removing ignored user/server": "Erreur lors de la suppression de l’utilisateur/du serveur ignoré",
+ "Error unsubscribing from list": "Erreur lors de la désinscription de la liste",
+ "Please try again or view your console for hints.": "Réessayez ou consultez votre console pour des indices.",
+ "None": "Aucun",
+ "Ban list rules - %(roomName)s": "Règles de la liste de bannissement − %(roomName)s",
+ "Server rules": "Règles de serveur",
+ "User rules": "Règles d’utilisateur",
+ "You have not ignored anyone.": "Vous n’avez ignoré personne.",
+ "You are currently ignoring:": "Vous ignorez actuellement :",
+ "You are not subscribed to any lists": "Vous n’êtes inscrit(e) à aucune liste",
+ "Unsubscribe": "Se désinscrire",
+ "View rules": "Voir les règles",
+ "You are currently subscribed to:": "Vous êtes actuellement inscrit(e) à :",
+ "⚠ These settings are meant for advanced users.": "⚠ Ces paramètres sont prévus pour les utilisateurs avancés.",
+ "Add users and servers you want to ignore here. Use asterisks to have Riot match any characters. For example, @bot:* would ignore all users that have the name 'bot' on any server.": "Ajoutez les utilisateurs et les serveurs que vous voulez ignorer ici. Utilisez des astérisques pour remplacer n’importe quel caractère. Par exemple, @bot:* ignorerait tous les utilisateurs qui ont le nom « bot » sur n’importe quel serveur.",
+ "Ignoring people is done through ban lists which contain rules for who to ban. Subscribing to a ban list means the users/servers blocked by that list will be hidden from you.": "Ignorer les gens est possible grâce à des listes de bannissement qui contiennent des règles sur les personnes à bannir. L’inscription à une liste de bannissement signifie que les utilisateurs/serveurs bloqués par cette liste seront cachés pour vous.",
+ "Personal ban list": "Liste de bannissement personnelle",
+ "Your personal ban list holds all the users/servers you personally don't want to see messages from. After ignoring your first user/server, a new room will show up in your room list named 'My Ban List' - stay in this room to keep the ban list in effect.": "Votre liste de bannissement personnelle contient tous les utilisateurs/serveurs dont vous ne voulez pas voir les messages personnellement. Quand vous aurez ignoré votre premier utilisateur/serveur, un nouveau salon nommé « Ma liste de bannissement » apparaîtra dans la liste de vos salons − restez dans ce salon pour que la liste de bannissement soit effective.",
+ "Server or user ID to ignore": "Serveur ou identifiant d’utilisateur à ignorer",
+ "eg: @bot:* or example.org": "par ex. : @bot:* ou exemple.org",
+ "Subscribed lists": "Listes souscrites",
+ "Subscribing to a ban list will cause you to join it!": "En vous inscrivant à une liste de bannissement, vous la rejoindrez !",
+ "If this isn't what you want, please use a different tool to ignore users.": "Si ce n’est pas ce que vous voulez, utilisez un autre outil pour ignorer les utilisateurs.",
+ "Room ID or alias of ban list": "Identifiant ou alias du salon de la liste de bannissement",
+ "Subscribe": "S’inscrire",
+ "You have ignored this user, so their message is hidden. Show anyways.": "Vous avez ignoré cet utilisateur, donc ses messages sont cachés. Les montrer quand même."
}
From f0696dcc3f7922a007bf9b3f2b547de9d6b528d9 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?=EC=86=A1=ED=83=9C=EC=84=AD?=
Date: Wed, 13 Nov 2019 01:12:50 +0000
Subject: [PATCH 090/334] Translated using Weblate (Korean)
Currently translated at 98.4% (1868 of 1898 strings)
Translation: Riot Web/matrix-react-sdk
Translate-URL: https://translate.riot.im/projects/riot-web/matrix-react-sdk/ko/
---
src/i18n/strings/ko.json | 6 +++++-
1 file changed, 5 insertions(+), 1 deletion(-)
diff --git a/src/i18n/strings/ko.json b/src/i18n/strings/ko.json
index 3d01d5be67..01ef0e8ae1 100644
--- a/src/i18n/strings/ko.json
+++ b/src/i18n/strings/ko.json
@@ -2135,5 +2135,9 @@
"You cancelled": "당신이 취소했습니다",
"%(name)s cancelled": "%(name)s님이 취소했습니다",
"%(name)s wants to verify": "%(name)s님이 확인을 요청합니다",
- "You sent a verification request": "확인 요청을 보냈습니다"
+ "You sent a verification request": "확인 요청을 보냈습니다",
+ "Try out new ways to ignore people (experimental)": "새 방식으로 사람들을 무시하기 (실험)",
+ "My Ban List": "차단 목록",
+ "This is your list of users/servers you have blocked - don't leave the room!": "차단한 사용자/서버 목록입니다 - 방을 떠나지 마세요!",
+ "Ignored/Blocked": "무시됨/차단됨"
}
From cc2ee53824b955e513def52cf4a08118d853e646 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Damir=20Jeli=C4=87?=
Date: Wed, 13 Nov 2019 16:21:26 +0100
Subject: [PATCH 091/334] EventIndex: Add some more docs and fix some lint
issues.
---
src/BaseEventIndexManager.js | 2 +-
src/BasePlatform.js | 6 ++++++
src/EventIndexPeg.js | 20 ++++++++++++++++----
src/components/structures/RoomView.js | 2 +-
4 files changed, 24 insertions(+), 6 deletions(-)
diff --git a/src/BaseEventIndexManager.js b/src/BaseEventIndexManager.js
index a74eac658a..48a96c4d88 100644
--- a/src/BaseEventIndexManager.js
+++ b/src/BaseEventIndexManager.js
@@ -168,7 +168,7 @@ export default class BaseEventIndexManager {
async addHistoricEvents(
events: [HistoricEvent],
checkpoint: CrawlerCheckpoint | null = null,
- oldCheckpoint: CrawlerCheckpoint | null = null,
+ oldCheckpoint: CrawlerCheckpoint | null = null,
): Promise {
throw new Error("Unimplemented");
}
diff --git a/src/BasePlatform.js b/src/BasePlatform.js
index 582ac24cb0..f6301fd173 100644
--- a/src/BasePlatform.js
+++ b/src/BasePlatform.js
@@ -153,6 +153,12 @@ export default class BasePlatform {
throw new Error("Unimplemented");
}
+ /**
+ * Get our platform specific EventIndexManager.
+ *
+ * @return {BaseEventIndexManager} The EventIndex manager for our platform,
+ * can be null if the platform doesn't support event indexing.
+ */
getEventIndexingManager(): BaseEventIndexManager | null {
return null;
}
diff --git a/src/EventIndexPeg.js b/src/EventIndexPeg.js
index 3ce88339eb..1b380e273f 100644
--- a/src/EventIndexPeg.js
+++ b/src/EventIndexPeg.js
@@ -34,16 +34,16 @@ class EventIndexPeg {
/**
* Get the current event index.
*
- * @Returns The EventIndex object for the application. Can be null
- * if the platform doesn't support event indexing.
+ * @return {EventIndex} The current event index.
*/
get() {
return this.index;
}
/** Create a new EventIndex and initialize it if the platform supports it.
- * Returns true if an EventIndex was successfully initialized, false
- * otherwise.
+ *
+ * @return {Promise} A promise that will resolve to true if an
+ * EventIndex was successfully initialized, false otherwise.
*/
async init() {
const indexManager = PlatformPeg.get().getEventIndexingManager();
@@ -71,15 +71,27 @@ class EventIndexPeg {
return true;
}
+ /**
+ * Stop our event indexer.
+ */
stop() {
if (this.index === null) return;
this.index.stop();
this.index = null;
}
+ /**
+ * Delete our event indexer.
+ *
+ * After a call to this the init() method will need to be called again.
+ *
+ * @return {Promise} A promise that will resolve once the event index is
+ * deleted.
+ */
async deleteEventIndex() {
if (this.index === null) return;
this.index.deleteEventIndex();
+ this.index = null;
}
}
diff --git a/src/components/structures/RoomView.js b/src/components/structures/RoomView.js
index 9fe54ad164..6dee60bec7 100644
--- a/src/components/structures/RoomView.js
+++ b/src/components/structures/RoomView.js
@@ -1131,7 +1131,7 @@ module.exports = createReactClass({
this.searchId = new Date().getTime();
let roomId;
- if (scope === "Room") roomId = this.state.room.roomId,
+ if (scope === "Room") roomId = this.state.room.roomId;
debuglog("sending search request");
const searchPromise = eventSearch(term, roomId);
From 368a77ec3ef318f7e0e55832bf97877b8575f737 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Damir=20Jeli=C4=87?=
Date: Wed, 13 Nov 2019 16:35:04 +0100
Subject: [PATCH 092/334] EventIndexing: Fix a style issue.
---
src/EventIndexing.js | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/EventIndexing.js b/src/EventIndexing.js
index af77979040..5830106e84 100644
--- a/src/EventIndexing.js
+++ b/src/EventIndexing.js
@@ -25,7 +25,7 @@ export default class EventIndexer {
this.crawlerCheckpoints = [];
// The time that the crawler will wait between /rooms/{room_id}/messages
// requests
- this._crawler_timeout = 3000;
+ this._crawlerTimeout = 3000;
this._crawlerRef = null;
this.liveEventsForIndex = new Set();
}
From d4b31cb7e037301b0786372d3ae643c96b2b48e5 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Damir=20Jeli=C4=87?=
Date: Wed, 13 Nov 2019 16:35:26 +0100
Subject: [PATCH 093/334] EventIndexing: Move the max events per crawl constant
into the class.
---
src/EventIndexing.js | 5 ++++-
1 file changed, 4 insertions(+), 1 deletion(-)
diff --git a/src/EventIndexing.js b/src/EventIndexing.js
index 5830106e84..77c4022480 100644
--- a/src/EventIndexing.js
+++ b/src/EventIndexing.js
@@ -26,6 +26,9 @@ export default class EventIndexer {
// The time that the crawler will wait between /rooms/{room_id}/messages
// requests
this._crawlerTimeout = 3000;
+ // The maximum number of events our crawler should fetch in a single
+ // crawl.
+ this._eventsPerCrawl = 100;
this._crawlerRef = null;
this.liveEventsForIndex = new Set();
}
@@ -218,7 +221,7 @@ export default class EventIndexer {
try {
res = await client._createMessagesRequest(
- checkpoint.roomId, checkpoint.token, 100,
+ checkpoint.roomId, checkpoint.token, this._eventsPerCrawl,
checkpoint.direction);
} catch (e) {
console.log("EventIndex: Error crawling events:", e);
From 9b32ec10b43cc274df28d938610fbf8c4b53479b Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Damir=20Jeli=C4=87?=
Date: Wed, 13 Nov 2019 16:47:21 +0100
Subject: [PATCH 094/334] EventIndexing: Use the correct timeout value.
---
src/EventIndexing.js | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/EventIndexing.js b/src/EventIndexing.js
index 77c4022480..67bd894c67 100644
--- a/src/EventIndexing.js
+++ b/src/EventIndexing.js
@@ -193,7 +193,7 @@ export default class EventIndexer {
// This is a low priority task and we don't want to spam our
// Homeserver with /messages requests so we set a hefty timeout
// here.
- await sleep(this._crawler_timeout);
+ await sleep(this._crawlerTimeout);
console.log("EventIndex: Running the crawler loop.");
From 56ad164c69ab497efed2949de62b7282fe86da2e Mon Sep 17 00:00:00 2001
From: Travis Ralston
Date: Wed, 13 Nov 2019 14:01:07 -0700
Subject: [PATCH 095/334] Add a function to get the "base" theme for a theme
Useful for trying to load the right assets first.
See https://github.com/vector-im/riot-web/pull/11381
---
src/theme.js | 16 ++++++++++++++++
1 file changed, 16 insertions(+)
diff --git a/src/theme.js b/src/theme.js
index d479170792..8a15c606d7 100644
--- a/src/theme.js
+++ b/src/theme.js
@@ -60,6 +60,22 @@ function getCustomTheme(themeName) {
return customTheme;
}
+/**
+ * Gets the underlying theme name for the given theme. This is usually the theme or
+ * CSS resource that the theme relies upon to load.
+ * @param {string} theme The theme name to get the base of.
+ * @returns {string} The base theme (typically "light" or "dark").
+ */
+export function getBaseTheme(theme) {
+ if (!theme) return "light";
+ if (theme.startsWith("custom-")) {
+ const customTheme = getCustomTheme(theme.substr(7));
+ return customTheme.is_dark ? "dark-custom" : "light-custom";
+ }
+
+ return theme; // it's probably a base theme
+}
+
/**
* Called whenever someone changes the theme
*
From bc90789c71b5b6d90e445061b1692269c93dbf3c Mon Sep 17 00:00:00 2001
From: Michael Telatynski <7t3chguy@gmail.com>
Date: Thu, 14 Nov 2019 00:39:48 +0000
Subject: [PATCH 096/334] Remove unused promise utils method
Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>
---
src/utils/promise.js | 13 -------------
1 file changed, 13 deletions(-)
diff --git a/src/utils/promise.js b/src/utils/promise.js
index f7a2e7c3e7..8842bfa1b7 100644
--- a/src/utils/promise.js
+++ b/src/utils/promise.js
@@ -22,19 +22,6 @@ import Promise from "bluebird";
// Returns a promise which resolves with a given value after the given number of ms
export const sleep = (ms: number, value: any): Promise => new Promise((resolve => { setTimeout(resolve, ms, value); }));
-// Returns a promise which resolves when the input promise resolves with its value
-// or when the timeout of ms is reached with the value of given timeoutValue
-export async function timeout(promise: Promise, timeoutValue: any, ms: number): Promise {
- const timeoutPromise = new Promise((resolve) => {
- const timeoutId = setTimeout(resolve, ms, timeoutValue);
- promise.then(() => {
- clearTimeout(timeoutId);
- });
- });
-
- return Promise.race([promise, timeoutPromise]);
-}
-
// Returns a Deferred
export function defer(): {resolve: () => {}, reject: () => {}, promise: Promise} {
let resolve;
From 5c24547ef5215b00333da3e9e7b61a55f222f7f6 Mon Sep 17 00:00:00 2001
From: Michael Telatynski <7t3chguy@gmail.com>
Date: Thu, 14 Nov 2019 09:37:26 +0000
Subject: [PATCH 097/334] re-add and actually use promise timeout util
Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>
---
src/components/views/settings/SetIdServer.js | 12 +++++-------
src/utils/promise.js | 13 +++++++++++++
2 files changed, 18 insertions(+), 7 deletions(-)
diff --git a/src/components/views/settings/SetIdServer.js b/src/components/views/settings/SetIdServer.js
index 126cdc9557..a7a2e01c22 100644
--- a/src/components/views/settings/SetIdServer.js
+++ b/src/components/views/settings/SetIdServer.js
@@ -26,6 +26,7 @@ import { getThreepidsWithBindStatus } from '../../../boundThreepids';
import IdentityAuthClient from "../../../IdentityAuthClient";
import {abbreviateUrl, unabbreviateUrl} from "../../../utils/UrlUtils";
import { getDefaultIdentityServerUrl, doesIdentityServerHaveTerms } from '../../../utils/IdentityServerUtils';
+import {timeout} from "../../../utils/promise";
// We'll wait up to this long when checking for 3PID bindings on the IS.
const REACHABILITY_TIMEOUT = 10000; // ms
@@ -245,14 +246,11 @@ export default class SetIdServer extends React.Component {
let threepids = [];
let currentServerReachable = true;
try {
- threepids = await Promise.race([
+ threepids = await timeout(
getThreepidsWithBindStatus(MatrixClientPeg.get()),
- new Promise((resolve, reject) => {
- setTimeout(() => {
- reject(new Error("Timeout attempting to reach identity server"));
- }, REACHABILITY_TIMEOUT);
- }),
- ]);
+ Promise.reject(new Error("Timeout attempting to reach identity server")),
+ REACHABILITY_TIMEOUT,
+ );
} catch (e) {
currentServerReachable = false;
console.warn(
diff --git a/src/utils/promise.js b/src/utils/promise.js
index 8842bfa1b7..f7a2e7c3e7 100644
--- a/src/utils/promise.js
+++ b/src/utils/promise.js
@@ -22,6 +22,19 @@ import Promise from "bluebird";
// Returns a promise which resolves with a given value after the given number of ms
export const sleep = (ms: number, value: any): Promise => new Promise((resolve => { setTimeout(resolve, ms, value); }));
+// Returns a promise which resolves when the input promise resolves with its value
+// or when the timeout of ms is reached with the value of given timeoutValue
+export async function timeout(promise: Promise, timeoutValue: any, ms: number): Promise {
+ const timeoutPromise = new Promise((resolve) => {
+ const timeoutId = setTimeout(resolve, ms, timeoutValue);
+ promise.then(() => {
+ clearTimeout(timeoutId);
+ });
+ });
+
+ return Promise.race([promise, timeoutPromise]);
+}
+
// Returns a Deferred
export function defer(): {resolve: () => {}, reject: () => {}, promise: Promise} {
let resolve;
From 28d2e658a4d184d7f51d2423cc0cde5c6ad41986 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Damir=20Jeli=C4=87?=
Date: Thu, 14 Nov 2019 14:13:49 +0100
Subject: [PATCH 098/334] EventIndexing: Don't scope the event index per user.
---
src/BaseEventIndexManager.js | 5 +----
src/EventIndexPeg.js | 3 +--
src/EventIndexing.js | 4 ++--
3 files changed, 4 insertions(+), 8 deletions(-)
diff --git a/src/BaseEventIndexManager.js b/src/BaseEventIndexManager.js
index 48a96c4d88..073bdbec81 100644
--- a/src/BaseEventIndexManager.js
+++ b/src/BaseEventIndexManager.js
@@ -91,13 +91,10 @@ export default class BaseEventIndexManager {
/**
* Initialize the event index for the given user.
*
- * @param {string} userId The unique identifier of the logged in user that
- * owns the index.
- *
* @return {Promise} A promise that will resolve when the event index is
* initialized.
*/
- async initEventIndex(userId: string): Promise<> {
+ async initEventIndex(): Promise<> {
throw new Error("Unimplemented");
}
diff --git a/src/EventIndexPeg.js b/src/EventIndexPeg.js
index 1b380e273f..ff1b2099f2 100644
--- a/src/EventIndexPeg.js
+++ b/src/EventIndexPeg.js
@@ -56,10 +56,9 @@ class EventIndexPeg {
}
const index = new EventIndex();
- const userId = MatrixClientPeg.get().getUserId();
try {
- await index.init(userId);
+ await index.init();
} catch (e) {
console.log("EventIndex: Error initializing the event index", e);
}
diff --git a/src/EventIndexing.js b/src/EventIndexing.js
index 67bd894c67..1fc9197082 100644
--- a/src/EventIndexing.js
+++ b/src/EventIndexing.js
@@ -33,10 +33,10 @@ export default class EventIndexer {
this.liveEventsForIndex = new Set();
}
- async init(userId) {
+ async init() {
const indexManager = PlatformPeg.get().getEventIndexingManager();
if (indexManager === null) return false;
- indexManager.initEventIndex(userId);
+ indexManager.initEventIndex();
return true;
}
From 54dcaf130255c39abc89de8d67fab06f1d0bf712 Mon Sep 17 00:00:00 2001
From: Michael Telatynski <7t3chguy@gmail.com>
Date: Thu, 14 Nov 2019 13:52:17 +0000
Subject: [PATCH 099/334] Replace bluebird specific promise things. Fix uses of
sync promise code.
Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>
---
src/GroupAddressPicker.js | 5 +++--
src/autocomplete/Autocompleter.js | 26 +++++++++-------------
src/components/structures/GroupView.js | 12 +++++-----
src/components/structures/TimelinePanel.js | 13 +++++------
src/rageshake/rageshake.js | 18 ++++++++++-----
src/utils/promise.js | 17 ++++++++++++++
6 files changed, 53 insertions(+), 38 deletions(-)
diff --git a/src/GroupAddressPicker.js b/src/GroupAddressPicker.js
index 7da37b6df1..793f5c9227 100644
--- a/src/GroupAddressPicker.js
+++ b/src/GroupAddressPicker.js
@@ -21,6 +21,7 @@ import MultiInviter from './utils/MultiInviter';
import { _t } from './languageHandler';
import MatrixClientPeg from './MatrixClientPeg';
import GroupStore from './stores/GroupStore';
+import {allSettled} from "./utils/promise";
export function showGroupInviteDialog(groupId) {
return new Promise((resolve, reject) => {
@@ -118,7 +119,7 @@ function _onGroupInviteFinished(groupId, addrs) {
function _onGroupAddRoomFinished(groupId, addrs, addRoomsPublicly) {
const matrixClient = MatrixClientPeg.get();
const errorList = [];
- return Promise.all(addrs.map((addr) => {
+ return allSettled(addrs.map((addr) => {
return GroupStore
.addRoomToGroup(groupId, addr.address, addRoomsPublicly)
.catch(() => { errorList.push(addr.address); })
@@ -138,7 +139,7 @@ function _onGroupAddRoomFinished(groupId, addrs, addRoomsPublicly) {
groups.push(groupId);
return MatrixClientPeg.get().sendStateEvent(roomId, 'm.room.related_groups', {groups}, '');
}
- }).reflect();
+ });
})).then(() => {
if (errorList.length === 0) {
return;
diff --git a/src/autocomplete/Autocompleter.js b/src/autocomplete/Autocompleter.js
index af2744950f..c385e13878 100644
--- a/src/autocomplete/Autocompleter.js
+++ b/src/autocomplete/Autocompleter.js
@@ -27,6 +27,7 @@ import UserProvider from './UserProvider';
import EmojiProvider from './EmojiProvider';
import NotifProvider from './NotifProvider';
import Promise from 'bluebird';
+import {timeout} from "../utils/promise";
export type SelectionRange = {
beginning: boolean, // whether the selection is in the first block of the editor or not
@@ -77,23 +78,16 @@ export default class Autocompleter {
while the user is interacting with the list, which makes it difficult
to predict whether an action will actually do what is intended
*/
- const completionsList = await Promise.all(
- // Array of inspections of promises that might timeout. Instead of allowing a
- // single timeout to reject the Promise.all, reflect each one and once they've all
- // settled, filter for the fulfilled ones
- this.providers.map(provider =>
- provider
- .getCompletions(query, selection, force)
- .timeout(PROVIDER_COMPLETION_TIMEOUT)
- .reflect(),
- ),
- );
+ const completionsList = await Promise.all(this.providers.map(provider => {
+ return timeout(provider.getCompletions(query, selection, force), null, PROVIDER_COMPLETION_TIMEOUT);
+ }));
+
+ // map then filter to maintain the index for the map-operation, for this.providers to line up
+ return completionsList.map((completions, i) => {
+ if (!completions || !completions.length) return;
- return completionsList.filter(
- (inspection) => inspection.isFulfilled(),
- ).map((completionsState, i) => {
return {
- completions: completionsState.value(),
+ completions,
provider: this.providers[i],
/* the currently matched "command" the completer tried to complete
@@ -102,6 +96,6 @@ export default class Autocompleter {
*/
command: this.providers[i].getCurrentCommand(query, selection, force),
};
- });
+ }).filter(Boolean);
}
}
diff --git a/src/components/structures/GroupView.js b/src/components/structures/GroupView.js
index 4056557a7c..776e7f0d6d 100644
--- a/src/components/structures/GroupView.js
+++ b/src/components/structures/GroupView.js
@@ -38,7 +38,7 @@ import FlairStore from '../../stores/FlairStore';
import { showGroupAddRoomDialog } from '../../GroupAddressPicker';
import {makeGroupPermalink, makeUserPermalink} from "../../utils/permalinks/Permalinks";
import {Group} from "matrix-js-sdk";
-import {sleep} from "../../utils/promise";
+import {allSettled, sleep} from "../../utils/promise";
const LONG_DESC_PLACEHOLDER = _td(
`
HTML for your community's page
@@ -99,11 +99,10 @@ const CategoryRoomList = createReactClass({
onFinished: (success, addrs) => {
if (!success) return;
const errorList = [];
- Promise.all(addrs.map((addr) => {
+ allSettled(addrs.map((addr) => {
return GroupStore
.addRoomToGroupSummary(this.props.groupId, addr.address)
- .catch(() => { errorList.push(addr.address); })
- .reflect();
+ .catch(() => { errorList.push(addr.address); });
})).then(() => {
if (errorList.length === 0) {
return;
@@ -276,11 +275,10 @@ const RoleUserList = createReactClass({
onFinished: (success, addrs) => {
if (!success) return;
const errorList = [];
- Promise.all(addrs.map((addr) => {
+ allSettled(addrs.map((addr) => {
return GroupStore
.addUserToGroupSummary(addr.address)
- .catch(() => { errorList.push(addr.address); })
- .reflect();
+ .catch(() => { errorList.push(addr.address); });
})).then(() => {
if (errorList.length === 0) {
return;
diff --git a/src/components/structures/TimelinePanel.js b/src/components/structures/TimelinePanel.js
index faa6f2564a..3dd5ea761e 100644
--- a/src/components/structures/TimelinePanel.js
+++ b/src/components/structures/TimelinePanel.js
@@ -1064,8 +1064,6 @@ const TimelinePanel = createReactClass({
});
};
- let prom = this._timelineWindow.load(eventId, INITIAL_SIZE);
-
// if we already have the event in question, TimelineWindow.load
// returns a resolved promise.
//
@@ -1074,9 +1072,13 @@ const TimelinePanel = createReactClass({
// quite slow. So we detect that situation and shortcut straight to
// calling _reloadEvents and updating the state.
- if (prom.isFulfilled()) {
+ const timeline = this.props.timelineSet.getTimelineForEvent(eventId);
+ if (timeline) {
+ // This is a hot-path optimization by skipping a promise tick
+ // by repeating a no-op sync branch in TimelineSet.getTimelineForEvent & MatrixClient.getEventTimeline
onLoaded();
} else {
+ const prom = this._timelineWindow.load(eventId, INITIAL_SIZE);
this.setState({
events: [],
liveEvents: [],
@@ -1084,11 +1086,8 @@ const TimelinePanel = createReactClass({
canForwardPaginate: false,
timelineLoading: true,
});
-
- prom = prom.then(onLoaded, onError);
+ prom.then(onLoaded, onError);
}
-
- prom.done();
},
// handle the completion of a timeline load or localEchoUpdate, by
diff --git a/src/rageshake/rageshake.js b/src/rageshake/rageshake.js
index d61956c925..ee1aed2294 100644
--- a/src/rageshake/rageshake.js
+++ b/src/rageshake/rageshake.js
@@ -136,6 +136,8 @@ class IndexedDBLogStore {
this.id = "instance-" + Math.random() + Date.now();
this.index = 0;
this.db = null;
+
+ // these promises are cleared as soon as fulfilled
this.flushPromise = null;
// set if flush() is called whilst one is ongoing
this.flushAgainPromise = null;
@@ -208,15 +210,15 @@ class IndexedDBLogStore {
*/
flush() {
// check if a flush() operation is ongoing
- if (this.flushPromise && this.flushPromise.isPending()) {
- if (this.flushAgainPromise && this.flushAgainPromise.isPending()) {
- // this is the 3rd+ time we've called flush() : return the same
- // promise.
+ if (this.flushPromise) {
+ if (this.flushAgainPromise) {
+ // this is the 3rd+ time we've called flush() : return the same promise.
return this.flushAgainPromise;
}
- // queue up a flush to occur immediately after the pending one
- // completes.
+ // queue up a flush to occur immediately after the pending one completes.
this.flushAgainPromise = this.flushPromise.then(() => {
+ // clear this.flushAgainPromise
+ this.flushAgainPromise = null;
return this.flush();
});
return this.flushAgainPromise;
@@ -232,12 +234,16 @@ class IndexedDBLogStore {
}
const lines = this.logger.flush();
if (lines.length === 0) {
+ // clear this.flushPromise
+ this.flushPromise = null;
resolve();
return;
}
const txn = this.db.transaction(["logs", "logslastmod"], "readwrite");
const objStore = txn.objectStore("logs");
txn.oncomplete = (event) => {
+ // clear this.flushPromise
+ this.flushPromise = null;
resolve();
};
txn.onerror = (event) => {
diff --git a/src/utils/promise.js b/src/utils/promise.js
index f7a2e7c3e7..e6e6ccb5c8 100644
--- a/src/utils/promise.js
+++ b/src/utils/promise.js
@@ -47,3 +47,20 @@ export function defer(): {resolve: () => {}, reject: () => {}, promise: Promise}
return {resolve, reject, promise};
}
+
+// Promise.allSettled polyfill until browser support is stable in Firefox
+export function allSettled(promises: Promise[]): {status: string, value?: any, reason?: any}[] {
+ if (Promise.allSettled) {
+ return Promise.allSettled(promises);
+ }
+
+ return Promise.all(promises.map((promise) => {
+ return promise.then(value => ({
+ status: "fulfilled",
+ value,
+ })).catch(reason => ({
+ status: "rejected",
+ reason,
+ }));
+ }));
+}
From 41f4f3ef823646bdb82b8de5e0d29f9d35d0b7ee Mon Sep 17 00:00:00 2001
From: Michael Telatynski <7t3chguy@gmail.com>
Date: Thu, 14 Nov 2019 14:04:50 +0000
Subject: [PATCH 100/334] make end-to-end test failure more verbose
Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>
---
test/end-to-end-tests/src/usecases/signup.js | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/test/end-to-end-tests/src/usecases/signup.js b/test/end-to-end-tests/src/usecases/signup.js
index 391ce76441..fd2b948572 100644
--- a/test/end-to-end-tests/src/usecases/signup.js
+++ b/test/end-to-end-tests/src/usecases/signup.js
@@ -61,7 +61,7 @@ module.exports = async function signup(session, username, password, homeserver)
await session.query(".mx_Field_valid #mx_RegistrationForm_password");
//check no errors
const errorText = await session.tryGetInnertext('.mx_Login_error');
- assert.strictEqual(!!errorText, false);
+ assert.strictEqual(errorText, null);
//submit form
//await page.screenshot({path: "beforesubmit.png", fullPage: true});
await registerButton.click();
From b3760cdd6e10b387c4e534c3b8b611870463b7c5 Mon Sep 17 00:00:00 2001
From: Michael Telatynski <7t3chguy@gmail.com>
Date: Thu, 14 Nov 2019 14:25:54 +0000
Subject: [PATCH 101/334] Replace usages of Promise.delay(...) with own utils
Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>
---
test/components/views/dialogs/InteractiveAuthDialog-test.js | 3 ++-
test/components/views/rooms/MessageComposerInput-test.js | 3 ++-
2 files changed, 4 insertions(+), 2 deletions(-)
diff --git a/test/components/views/dialogs/InteractiveAuthDialog-test.js b/test/components/views/dialogs/InteractiveAuthDialog-test.js
index b14ea7c242..7612b43b48 100644
--- a/test/components/views/dialogs/InteractiveAuthDialog-test.js
+++ b/test/components/views/dialogs/InteractiveAuthDialog-test.js
@@ -26,6 +26,7 @@ import sdk from 'matrix-react-sdk';
import MatrixClientPeg from '../../../../src/MatrixClientPeg';
import * as test_utils from '../../../test-utils';
+import {sleep} from "../../../../src/utils/promise";
const InteractiveAuthDialog = sdk.getComponent(
'views.dialogs.InteractiveAuthDialog',
@@ -107,7 +108,7 @@ describe('InteractiveAuthDialog', function() {
},
})).toBe(true);
// let the request complete
- return Promise.delay(1);
+ return sleep(1);
}).then(() => {
expect(onFinished.callCount).toEqual(1);
expect(onFinished.calledWithExactly(true, {a: 1})).toBe(true);
diff --git a/test/components/views/rooms/MessageComposerInput-test.js b/test/components/views/rooms/MessageComposerInput-test.js
index 1105a4af17..04a5c83ed0 100644
--- a/test/components/views/rooms/MessageComposerInput-test.js
+++ b/test/components/views/rooms/MessageComposerInput-test.js
@@ -8,6 +8,7 @@ import * as testUtils from '../../../test-utils';
import sdk from 'matrix-react-sdk';
const MessageComposerInput = sdk.getComponent('views.rooms.MessageComposerInput');
import MatrixClientPeg from '../../../../src/MatrixClientPeg';
+import {sleep} from "../../../../src/utils/promise";
function addTextToDraft(text) {
const components = document.getElementsByClassName('public-DraftEditor-content');
@@ -49,7 +50,7 @@ xdescribe('MessageComposerInput', () => {
// warnings
// (please can we make the components not setState() after
// they are unmounted?)
- Promise.delay(10).done(() => {
+ sleep(10).done(() => {
if (parentDiv) {
ReactDOM.unmountComponentAtNode(parentDiv);
parentDiv.remove();
From 51a65f388bc3339e7378636c7c75808b63061e3d Mon Sep 17 00:00:00 2001
From: random
Date: Thu, 14 Nov 2019 14:15:42 +0000
Subject: [PATCH 102/334] Translated using Weblate (Italian)
Currently translated at 99.9% (1896 of 1898 strings)
Translation: Riot Web/matrix-react-sdk
Translate-URL: https://translate.riot.im/projects/riot-web/matrix-react-sdk/it/
---
src/i18n/strings/it.json | 48 +++++++++++++++++++++++++++++++++++++++-
1 file changed, 47 insertions(+), 1 deletion(-)
diff --git a/src/i18n/strings/it.json b/src/i18n/strings/it.json
index 6262315012..8c7edbadd8 100644
--- a/src/i18n/strings/it.json
+++ b/src/i18n/strings/it.json
@@ -2224,5 +2224,51 @@
"%(count)s unread messages including mentions.|one": "1 citazione non letta.",
"%(count)s unread messages.|one": "1 messaggio non letto.",
"Unread messages.": "Messaggi non letti.",
- "Show tray icon and minimize window to it on close": "Mostra icona in tray e usala alla chiusura della finestra"
+ "Show tray icon and minimize window to it on close": "Mostra icona in tray e usala alla chiusura della finestra",
+ "This action requires accessing the default identity server to validate an email address or phone number, but the server does not have any terms of service.": "Questa azione richiede l'accesso al server di identità predefinito per verificare un indirizzo email o numero di telefono, ma il server non ha termini di servizio.",
+ "%(name)s (%(userId)s)": "%(name)s (%(userId)s)",
+ "Try out new ways to ignore people (experimental)": "Prova nuovi metodi per ignorare persone (sperimentale)",
+ "Send verification requests in direct message": "Invia richieste di verifica in un messaggio diretto",
+ "My Ban List": "Mia lista ban",
+ "This is your list of users/servers you have blocked - don't leave the room!": "Questa è la lista degli utenti/server che hai bloccato - non lasciare la stanza!",
+ "Error adding ignored user/server": "Errore di aggiunta utente/server ignorato",
+ "Something went wrong. Please try again or view your console for hints.": "Qualcosa è andato storto. Riprova o controlla la console per suggerimenti.",
+ "Error subscribing to list": "Errore di iscrizione alla lista",
+ "Please verify the room ID or alias and try again.": "Verifica l'ID della stanza o l'alias e riprova.",
+ "Error removing ignored user/server": "Errore di rimozione utente/server ignorato",
+ "Error unsubscribing from list": "Errore di disiscrizione dalla lista",
+ "Please try again or view your console for hints.": "Riprova o controlla la console per suggerimenti.",
+ "None": "Nessuno",
+ "Ban list rules - %(roomName)s": "Regole lista banditi - %(roomName)s",
+ "Server rules": "Regole server",
+ "User rules": "Regole utente",
+ "You have not ignored anyone.": "Non hai ignorato nessuno.",
+ "You are currently ignoring:": "Attualmente stai ignorando:",
+ "You are not subscribed to any lists": "Non sei iscritto ad alcuna lista",
+ "Unsubscribe": "Disiscriviti",
+ "View rules": "Vedi regole",
+ "You are currently subscribed to:": "Attualmente sei iscritto a:",
+ "⚠ These settings are meant for advanced users.": "⚠ Queste opzioni sono pensate per utenti esperti.",
+ "Add users and servers you want to ignore here. Use asterisks to have Riot match any characters. For example, @bot:* would ignore all users that have the name 'bot' on any server.": "Aggiungi qui gli utenti e i server che vuoi ignorare. Usa l'asterisco perchè Riot consideri qualsiasi carattere. Ad esempio, @bot:* ignorerà tutti gli utenti che hanno il nome 'bot' su qualsiasi server.",
+ "Ignoring people is done through ban lists which contain rules for who to ban. Subscribing to a ban list means the users/servers blocked by that list will be hidden from you.": "Si possono ignorare persone attraverso liste di ban contenenti regole per chi bandire. Iscriversi ad una lista di ban significa che gli utenti/server bloccati da quella lista ti verranno nascosti.",
+ "Personal ban list": "Lista di ban personale",
+ "Your personal ban list holds all the users/servers you personally don't want to see messages from. After ignoring your first user/server, a new room will show up in your room list named 'My Ban List' - stay in this room to keep the ban list in effect.": "La tua lista personale di ban contiene tutti gli utenti/server da cui non vuoi vedere messaggi. Dopo aver ignorato il tuo primo utente/server, apparirà una nuova stanza nel tuo elenco stanze chiamata 'Mia lista ban' - resta in questa stanza per mantenere effettiva la lista ban.",
+ "Server or user ID to ignore": "Server o ID utente da ignorare",
+ "eg: @bot:* or example.org": "es: @bot:* o esempio.org",
+ "Subscribed lists": "Liste sottoscritte",
+ "Subscribing to a ban list will cause you to join it!": "Iscriversi ad una lista di ban implica di unirsi ad essa!",
+ "If this isn't what you want, please use a different tool to ignore users.": "Se non è ciò che vuoi, usa uno strumento diverso per ignorare utenti.",
+ "Room ID or alias of ban list": "ID stanza o alias della lista di ban",
+ "Subscribe": "Iscriviti",
+ "Message Actions": "Azioni messaggio",
+ "You have ignored this user, so their message is hidden. Show anyways.": "Hai ignorato questo utente, perciò il suo messaggio è nascosto. Mostra comunque.",
+ "You verified %(name)s": "Hai verificato %(name)s",
+ "You cancelled verifying %(name)s": "Hai annullato la verifica di %(name)s",
+ "%(name)s cancelled verifying": "%(name)s ha annullato la verifica",
+ "You accepted": "Hai accettato",
+ "%(name)s accepted": "%(name)s ha accettato",
+ "You cancelled": "Hai annullato",
+ "%(name)s cancelled": "%(name)s ha annullato",
+ "%(name)s wants to verify": "%(name)s vuole verificare",
+ "You sent a verification request": "Hai inviato una richiesta di verifica"
}
From 154fb7ecacc795facc748adf1e9be3a8dd05efde Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?=EC=86=A1=ED=83=9C=EC=84=AD?=
Date: Thu, 14 Nov 2019 03:08:03 +0000
Subject: [PATCH 103/334] Translated using Weblate (Korean)
Currently translated at 99.4% (1887 of 1898 strings)
Translation: Riot Web/matrix-react-sdk
Translate-URL: https://translate.riot.im/projects/riot-web/matrix-react-sdk/ko/
---
src/i18n/strings/ko.json | 21 ++++++++++++++++++++-
1 file changed, 20 insertions(+), 1 deletion(-)
diff --git a/src/i18n/strings/ko.json b/src/i18n/strings/ko.json
index 01ef0e8ae1..fe8c929acd 100644
--- a/src/i18n/strings/ko.json
+++ b/src/i18n/strings/ko.json
@@ -2139,5 +2139,24 @@
"Try out new ways to ignore people (experimental)": "새 방식으로 사람들을 무시하기 (실험)",
"My Ban List": "차단 목록",
"This is your list of users/servers you have blocked - don't leave the room!": "차단한 사용자/서버 목록입니다 - 방을 떠나지 마세요!",
- "Ignored/Blocked": "무시됨/차단됨"
+ "Ignored/Blocked": "무시됨/차단됨",
+ "Error adding ignored user/server": "무시한 사용자/서버 추가 중 오류",
+ "Something went wrong. Please try again or view your console for hints.": "무언가 잘못되었습니다. 다시 시도하거나 콘솔을 통해 원인을 알아봐주세요.",
+ "Error subscribing to list": "목록으로 구독하는 중 오류",
+ "Please verify the room ID or alias and try again.": "방 ID나 별칭을 확인한 후 다시 시도해주세요.",
+ "Error removing ignored user/server": "무시한 사용자/서버를 지우는 중 오류",
+ "Error unsubscribing from list": "목록에서 구독 해제 중 오류",
+ "Please try again or view your console for hints.": "다시 시도하거나 콘솔을 통해 원인을 알아봐주세요.",
+ "None": "없음",
+ "Ban list rules - %(roomName)s": "차단 목록 규칙 - %(roomName)s",
+ "Server rules": "서버 규칙",
+ "User rules": "사용자 규칙",
+ "You have not ignored anyone.": "아무도 무시하고 있지 않습니다.",
+ "You are currently ignoring:": "현재 무시하고 있음:",
+ "You are not subscribed to any lists": "어느 목록에도 구독하고 있지 않습니다",
+ "Unsubscribe": "구독 해제",
+ "View rules": "규칙 보기",
+ "You are currently subscribed to:": "현재 구독 중임:",
+ "⚠ These settings are meant for advanced users.": "⚠ 이 설정은 고급 사용자를 위한 것입니다.",
+ "Add users and servers you want to ignore here. Use asterisks to have Riot match any characters. For example, @bot:* would ignore all users that have the name 'bot' on any server.": "무시하고 싶은 사용자와 서버를 여기에 추가하세요. 별표(*)를 사용해서 Riot이 이름과 문자를 맞춰볼 수 있습니다. 예를 들어, @bot:*이라면 모든 서버에서 'bot'이라는 문자를 가진 이름의 모든 사용자를 무시합니다."
}
From bcb7ec081453b15fc2ea7640b5d1ace3e3e5e1f5 Mon Sep 17 00:00:00 2001
From: andriusign
Date: Wed, 13 Nov 2019 20:45:07 +0000
Subject: [PATCH 104/334] Translated using Weblate (Lithuanian)
Currently translated at 49.2% (934 of 1898 strings)
Translation: Riot Web/matrix-react-sdk
Translate-URL: https://translate.riot.im/projects/riot-web/matrix-react-sdk/lt/
---
src/i18n/strings/lt.json | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/src/i18n/strings/lt.json b/src/i18n/strings/lt.json
index 2aeb207387..5f3a76caa0 100644
--- a/src/i18n/strings/lt.json
+++ b/src/i18n/strings/lt.json
@@ -1117,5 +1117,6 @@
"Custom user status messages": "Pasirinktinės vartotojo būsenos žinutės",
"Group & filter rooms by custom tags (refresh to apply changes)": "Grupuoti ir filtruoti kambarius pagal pasirinktines žymas (atnaujinkite, kad pritaikytumėte pakeitimus)",
"Render simple counters in room header": "Užkrauti paprastus skaitiklius kambario antraštėje",
- "Multiple integration managers": "Daugialypiai integracijų valdikliai"
+ "Multiple integration managers": "Daugialypiai integracijų valdikliai",
+ "%(name)s (%(userId)s)": "%(name)s (%(userId)s)"
}
From 448c9a82908b9e1504ed28a66dd1a68cb9daf9c0 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Damir=20Jeli=C4=87?=
Date: Thu, 14 Nov 2019 16:01:14 +0100
Subject: [PATCH 105/334] EventIndexPeg: Add a missing return statement.
---
src/EventIndexPeg.js | 1 +
1 file changed, 1 insertion(+)
diff --git a/src/EventIndexPeg.js b/src/EventIndexPeg.js
index ff1b2099f2..a4ab1815c9 100644
--- a/src/EventIndexPeg.js
+++ b/src/EventIndexPeg.js
@@ -61,6 +61,7 @@ class EventIndexPeg {
await index.init();
} catch (e) {
console.log("EventIndex: Error initializing the event index", e);
+ return false;
}
console.log("EventIndex: Successfully initialized the event index");
From 7516f2724aeb34f13ae379f7d5c2124beca1b5bc Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Damir=20Jeli=C4=87?=
Date: Thu, 14 Nov 2019 16:13:22 +0100
Subject: [PATCH 106/334] EventIndexing: Rework the index initialization and
deletion.
---
src/BaseEventIndexManager.js | 10 +++++++++
src/EventIndexPeg.js | 43 ++++++++++++++++++++++++------------
src/EventIndexing.js | 40 +++++++++++++++++++--------------
src/Lifecycle.js | 1 +
4 files changed, 63 insertions(+), 31 deletions(-)
diff --git a/src/BaseEventIndexManager.js b/src/BaseEventIndexManager.js
index 073bdbec81..4e52344e76 100644
--- a/src/BaseEventIndexManager.js
+++ b/src/BaseEventIndexManager.js
@@ -206,6 +206,16 @@ export default class BaseEventIndexManager {
throw new Error("Unimplemented");
}
+ /**
+ * close our event index.
+ *
+ * @return {Promise} A promise that will resolve once the event index has
+ * been closed.
+ */
+ async closeEventIndex(): Promise<> {
+ throw new Error("Unimplemented");
+ }
+
/**
* Delete our current event index.
*
diff --git a/src/EventIndexPeg.js b/src/EventIndexPeg.js
index a4ab1815c9..dc25b11cf7 100644
--- a/src/EventIndexPeg.js
+++ b/src/EventIndexPeg.js
@@ -31,15 +31,6 @@ class EventIndexPeg {
this.index = null;
}
- /**
- * Get the current event index.
- *
- * @return {EventIndex} The current event index.
- */
- get() {
- return this.index;
- }
-
/** Create a new EventIndex and initialize it if the platform supports it.
*
* @return {Promise} A promise that will resolve to true if an
@@ -72,11 +63,30 @@ class EventIndexPeg {
}
/**
- * Stop our event indexer.
+ * Get the current event index.
+ *
+ * @return {EventIndex} The current event index.
*/
+ get() {
+ return this.index;
+ }
+
stop() {
if (this.index === null) return;
- this.index.stop();
+ this.index.stopCrawler();
+ }
+
+ /**
+ * Unset our event store
+ *
+ * After a call to this the init() method will need to be called again.
+ *
+ * @return {Promise} A promise that will resolve once the event index is
+ * closed.
+ */
+ async unset() {
+ if (this.index === null) return;
+ this.index.close();
this.index = null;
}
@@ -89,9 +99,14 @@ class EventIndexPeg {
* deleted.
*/
async deleteEventIndex() {
- if (this.index === null) return;
- this.index.deleteEventIndex();
- this.index = null;
+ const indexManager = PlatformPeg.get().getEventIndexingManager();
+
+ if (indexManager !== null) {
+ this.stop();
+ console.log("EventIndex: Deleting event index.");
+ await indexManager.deleteEventIndex();
+ this.index = null;
+ }
}
}
diff --git a/src/EventIndexing.js b/src/EventIndexing.js
index 1fc9197082..37167cf600 100644
--- a/src/EventIndexing.js
+++ b/src/EventIndexing.js
@@ -35,9 +35,7 @@ export default class EventIndexer {
async init() {
const indexManager = PlatformPeg.get().getEventIndexingManager();
- if (indexManager === null) return false;
- indexManager.initEventIndex();
- return true;
+ return indexManager.initEventIndex();
}
async onSync(state, prevState, data) {
@@ -198,7 +196,6 @@ export default class EventIndexer {
console.log("EventIndex: Running the crawler loop.");
if (cancelled) {
- console.log("EventIndex: Cancelling the crawler.");
break;
}
@@ -373,26 +370,35 @@ export default class EventIndexer {
this.crawlerCheckpoints.push(backwardsCheckpoint);
}
+ startCrawler() {
+ if (this._crawlerRef !== null) return;
+
+ const crawlerHandle = {};
+ this.crawlerFunc(crawlerHandle);
+ this._crawlerRef = crawlerHandle;
+ }
+
+ stopCrawler() {
+ if (this._crawlerRef === null) return;
+
+ this._crawlerRef.cancel();
+ this._crawlerRef = null;
+ }
+
+ async close() {
+ const indexManager = PlatformPeg.get().getEventIndexingManager();
+ this.stopCrawler();
+ return indexManager.closeEventIndex();
+ }
+
async deleteEventIndex() {
const indexManager = PlatformPeg.get().getEventIndexingManager();
if (indexManager !== null) {
- console.log("EventIndex: Deleting event index.");
- this.crawlerRef.cancel();
+ this.stopCrawler();
await indexManager.deleteEventIndex();
}
}
- startCrawler() {
- const crawlerHandle = {};
- this.crawlerFunc(crawlerHandle);
- this.crawlerRef = crawlerHandle;
- }
-
- stop() {
- this._crawlerRef.cancel();
- this._crawlerRef = null;
- }
-
async search(searchArgs) {
const indexManager = PlatformPeg.get().getEventIndexingManager();
return indexManager.searchEventIndex(searchArgs);
diff --git a/src/Lifecycle.js b/src/Lifecycle.js
index aa900c81a1..1d38934ade 100644
--- a/src/Lifecycle.js
+++ b/src/Lifecycle.js
@@ -662,6 +662,7 @@ export function stopMatrixClient(unsetClient=true) {
if (unsetClient) {
MatrixClientPeg.unset();
+ EventIndexPeg.unset().done();
}
}
}
From d82d4246e92800588c77ed74f3e4f957a554ffbf Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Damir=20Jeli=C4=87?=
Date: Thu, 14 Nov 2019 16:17:50 +0100
Subject: [PATCH 107/334] BaseEventIndexManager: Remove a return from a
docstring.
---
src/BaseEventIndexManager.js | 3 ---
1 file changed, 3 deletions(-)
diff --git a/src/BaseEventIndexManager.js b/src/BaseEventIndexManager.js
index 4e52344e76..fe59cee673 100644
--- a/src/BaseEventIndexManager.js
+++ b/src/BaseEventIndexManager.js
@@ -114,9 +114,6 @@ export default class BaseEventIndexManager {
/**
* Check if our event index is empty.
- *
- * @return {Promise} A promise that will resolve to true if the
- * event index is empty, false otherwise.
*/
indexIsEmpty(): Promise {
throw new Error("Unimplemented");
From eb0b0a400f72d8ada1e9018192eff00c42dcf250 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Damir=20Jeli=C4=87?=
Date: Thu, 14 Nov 2019 16:18:36 +0100
Subject: [PATCH 108/334] EventIndexPeg: Remove the now unused import of
MatrixClientPeg.
---
src/EventIndexPeg.js | 1 -
1 file changed, 1 deletion(-)
diff --git a/src/EventIndexPeg.js b/src/EventIndexPeg.js
index dc25b11cf7..da5c5425e4 100644
--- a/src/EventIndexPeg.js
+++ b/src/EventIndexPeg.js
@@ -24,7 +24,6 @@ limitations under the License.
import PlatformPeg from "./PlatformPeg";
import EventIndex from "./EventIndexing";
-import MatrixClientPeg from "./MatrixClientPeg";
class EventIndexPeg {
constructor() {
From 413b90328fe3fc916045c194522cb0ba1721d4a4 Mon Sep 17 00:00:00 2001
From: "J. Ryan Stinnett"
Date: Thu, 14 Nov 2019 15:23:04 +0000
Subject: [PATCH 109/334] Show server details on login for unreachable
homeserver
This fixes the login page to be more helpful when the current homeserver is
unreachable: it reveals the server change field, so you have some chance to
progress forward.
Fixes https://github.com/vector-im/riot-web/issues/11077
---
src/components/structures/auth/Login.js | 6 +++++-
1 file changed, 5 insertions(+), 1 deletion(-)
diff --git a/src/components/structures/auth/Login.js b/src/components/structures/auth/Login.js
index ad77ed49a5..b35110bf6b 100644
--- a/src/components/structures/auth/Login.js
+++ b/src/components/structures/auth/Login.js
@@ -386,7 +386,11 @@ module.exports = createReactClass({
...AutoDiscoveryUtils.authComponentStateForError(e),
});
if (this.state.serverErrorIsFatal) {
- return; // Server is dead - do not continue.
+ // Server is dead: show server details prompt instead
+ this.setState({
+ phase: PHASE_SERVER_DETAILS,
+ });
+ return;
}
}
From 84f78ae7269400eba84ab42c37e8815c0db23fb6 Mon Sep 17 00:00:00 2001
From: Michael Telatynski <7t3chguy@gmail.com>
Date: Thu, 14 Nov 2019 16:05:09 +0000
Subject: [PATCH 110/334] Revert ripping bluebird out of rageshake.js for time
being
Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>
---
src/rageshake/rageshake.js | 18 ++++++------------
1 file changed, 6 insertions(+), 12 deletions(-)
diff --git a/src/rageshake/rageshake.js b/src/rageshake/rageshake.js
index ee1aed2294..d61956c925 100644
--- a/src/rageshake/rageshake.js
+++ b/src/rageshake/rageshake.js
@@ -136,8 +136,6 @@ class IndexedDBLogStore {
this.id = "instance-" + Math.random() + Date.now();
this.index = 0;
this.db = null;
-
- // these promises are cleared as soon as fulfilled
this.flushPromise = null;
// set if flush() is called whilst one is ongoing
this.flushAgainPromise = null;
@@ -210,15 +208,15 @@ class IndexedDBLogStore {
*/
flush() {
// check if a flush() operation is ongoing
- if (this.flushPromise) {
- if (this.flushAgainPromise) {
- // this is the 3rd+ time we've called flush() : return the same promise.
+ if (this.flushPromise && this.flushPromise.isPending()) {
+ if (this.flushAgainPromise && this.flushAgainPromise.isPending()) {
+ // this is the 3rd+ time we've called flush() : return the same
+ // promise.
return this.flushAgainPromise;
}
- // queue up a flush to occur immediately after the pending one completes.
+ // queue up a flush to occur immediately after the pending one
+ // completes.
this.flushAgainPromise = this.flushPromise.then(() => {
- // clear this.flushAgainPromise
- this.flushAgainPromise = null;
return this.flush();
});
return this.flushAgainPromise;
@@ -234,16 +232,12 @@ class IndexedDBLogStore {
}
const lines = this.logger.flush();
if (lines.length === 0) {
- // clear this.flushPromise
- this.flushPromise = null;
resolve();
return;
}
const txn = this.db.transaction(["logs", "logslastmod"], "readwrite");
const objStore = txn.objectStore("logs");
txn.oncomplete = (event) => {
- // clear this.flushPromise
- this.flushPromise = null;
resolve();
};
txn.onerror = (event) => {
From b05dabe2b7746975ba285d6e52b7594e27a4b8b6 Mon Sep 17 00:00:00 2001
From: Travis Ralston
Date: Thu, 14 Nov 2019 12:02:16 -0700
Subject: [PATCH 111/334] Add better error handling to Synapse user
deactivation
Also clearly flag it as a Synapse user deactivation in the analytics, so we don't get confused.
Fixes https://github.com/vector-im/riot-web/issues/10986
---
src/components/views/right_panel/UserInfo.js | 7 +++++--
src/components/views/rooms/MemberInfo.js | 11 ++++++++++-
2 files changed, 15 insertions(+), 3 deletions(-)
diff --git a/src/components/views/right_panel/UserInfo.js b/src/components/views/right_panel/UserInfo.js
index 207bf29998..7a88c80ce5 100644
--- a/src/components/views/right_panel/UserInfo.js
+++ b/src/components/views/right_panel/UserInfo.js
@@ -842,10 +842,13 @@ const UserInfo = withLegacyMatrixClient(({matrixClient: cli, user, groupId, room
const [accepted] = await finished;
if (!accepted) return;
try {
- cli.deactivateSynapseUser(user.userId);
+ await cli.deactivateSynapseUser(user.userId);
} catch (err) {
+ console.error("Failed to deactivate user");
+ console.error(err);
+
const ErrorDialog = sdk.getComponent('dialogs.ErrorDialog');
- Modal.createTrackedDialog('Failed to deactivate user', '', ErrorDialog, {
+ Modal.createTrackedDialog('Failed to deactivate Synapse user', '', ErrorDialog, {
title: _t('Failed to deactivate user'),
description: ((err && err.message) ? err.message : _t("Operation failed")),
});
diff --git a/src/components/views/rooms/MemberInfo.js b/src/components/views/rooms/MemberInfo.js
index 2ea6392e96..9364f2f49d 100644
--- a/src/components/views/rooms/MemberInfo.js
+++ b/src/components/views/rooms/MemberInfo.js
@@ -550,7 +550,16 @@ module.exports = createReactClass({
danger: true,
onFinished: (accepted) => {
if (!accepted) return;
- this.context.matrixClient.deactivateSynapseUser(this.props.member.userId);
+ this.context.matrixClient.deactivateSynapseUser(this.props.member.userId).catch(e => {
+ console.error("Failed to deactivate user");
+ console.error(e);
+
+ const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
+ Modal.createTrackedDialog('Failed to deactivate Synapse user', '', ErrorDialog, {
+ title: _t('Failed to deactivate user'),
+ description: ((e && e.message) ? e.message : _t("Operation failed")),
+ });
+ });
},
});
},
From 0f2f500a16078b23e818340f4bc2491e2d0e3d56 Mon Sep 17 00:00:00 2001
From: Travis Ralston
Date: Thu, 14 Nov 2019 12:08:04 -0700
Subject: [PATCH 112/334] i18n update
---
src/i18n/strings/en_EN.json | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json
index dc9773ad21..8f1344d5c9 100644
--- a/src/i18n/strings/en_EN.json
+++ b/src/i18n/strings/en_EN.json
@@ -867,6 +867,7 @@
"Deactivate user?": "Deactivate user?",
"Deactivating this user will log them out and prevent them from logging back in. Additionally, they will leave all the rooms they are in. This action cannot be reversed. Are you sure you want to deactivate this user?": "Deactivating this user will log them out and prevent them from logging back in. Additionally, they will leave all the rooms they are in. This action cannot be reversed. Are you sure you want to deactivate this user?",
"Deactivate user": "Deactivate user",
+ "Failed to deactivate user": "Failed to deactivate user",
"Failed to change power level": "Failed to change power level",
"You will not be able to undo this change as you are promoting the user to have the same power level as yourself.": "You will not be able to undo this change as you are promoting the user to have the same power level as yourself.",
"Are you sure?": "Are you sure?",
@@ -1073,7 +1074,6 @@
"Remove this user from community?": "Remove this user from community?",
"Failed to withdraw invitation": "Failed to withdraw invitation",
"Failed to remove user from community": "Failed to remove user from community",
- "Failed to deactivate user": "Failed to deactivate user",
"This client does not support end-to-end encryption.": "This client does not support end-to-end encryption.",
"Messages in this room are not end-to-end encrypted.": "Messages in this room are not end-to-end encrypted.",
"Sunday": "Sunday",
From af4ad488bdf753edb514e13b88957a2ef103dcda Mon Sep 17 00:00:00 2001
From: Bruno Windels
Date: Mon, 11 Nov 2019 16:54:12 +0100
Subject: [PATCH 113/334] Restyle Avatar
Make it a circle with the profile picture centered,
with a max height/width of 30vh
---
res/css/views/right_panel/_UserInfo.scss | 27 +++++++++++++++-----
src/components/views/right_panel/UserInfo.js | 18 +++++++++++--
2 files changed, 36 insertions(+), 9 deletions(-)
diff --git a/res/css/views/right_panel/_UserInfo.scss b/res/css/views/right_panel/_UserInfo.scss
index df536a7388..db08fe18bf 100644
--- a/res/css/views/right_panel/_UserInfo.scss
+++ b/res/css/views/right_panel/_UserInfo.scss
@@ -38,6 +38,7 @@ limitations under the License.
mask-repeat: no-repeat;
mask-position: 16px center;
background-color: $rightpanel-button-color;
+ position: absolute;
}
.mx_UserInfo_profile h2 {
@@ -47,7 +48,7 @@ limitations under the License.
}
.mx_UserInfo h2 {
- font-size: 16px;
+ font-size: 18px;
font-weight: 600;
margin: 16px 0 8px 0;
}
@@ -74,15 +75,27 @@ limitations under the License.
}
.mx_UserInfo_avatar {
- background: $tagpanel-bg-color;
+ margin: 24px 32px 0 32px;
}
-.mx_UserInfo_avatar > img {
- height: auto;
- width: 100%;
+.mx_UserInfo_avatar > div {
+ max-width: 30vh;
+ margin: 0 auto;
+}
+
+.mx_UserInfo_avatar > div > div {
+ /* use padding-top instead of height to make this element square,
+ as the % in padding is a % of the width (including margin,
+ that's why we had to put the margin to center on a parent div),
+ and not a % of the parent height. */
+ padding-top: 100%;
+ height: 0;
+ border-radius: 100%;
max-height: 30vh;
- object-fit: contain;
- display: block;
+ box-sizing: content-box;
+ background-repeat: no-repeat;
+ background-size: cover;
+ background-position: center;
}
.mx_UserInfo_avatar .mx_BaseAvatar.mx_BaseAvatar_image {
diff --git a/src/components/views/right_panel/UserInfo.js b/src/components/views/right_panel/UserInfo.js
index 207bf29998..e55aae74f5 100644
--- a/src/components/views/right_panel/UserInfo.js
+++ b/src/components/views/right_panel/UserInfo.js
@@ -40,6 +40,7 @@ import MatrixClientPeg from "../../../MatrixClientPeg";
import E2EIcon from "../rooms/E2EIcon";
import withLegacyMatrixClient from "../../../utils/withLegacyMatrixClient";
import {useEventEmitter} from "../../../hooks/useEventEmitter";
+import {ContentRepo} from 'matrix-js-sdk';
const _disambiguateDevices = (devices) => {
const names = Object.create(null);
@@ -917,6 +918,12 @@ const UserInfo = withLegacyMatrixClient(({matrixClient: cli, user, groupId, room
_applyPowerChange(roomId, target, powerLevel, powerLevelEvent);
}, [user.roomId, user.userId, room && room.currentState, cli]); // eslint-disable-line
+ const onMemberAvatarKey = e => {
+ if (e.key === "Enter") {
+ onMemberAvatarClick();
+ }
+ };
+
const onMemberAvatarClick = useCallback(() => {
const member = user;
const avatarUrl = member.getMxcAvatarUrl();
@@ -1045,8 +1052,15 @@ const UserInfo = withLegacyMatrixClient(({matrixClient: cli, user, groupId, room
let avatarElement;
if (avatarUrl) {
const httpUrl = cli.mxcUrlToHttp(avatarUrl, 800, 800);
- avatarElement =
-
+ avatarElement =
+
;
}
From f4988392f9cdec443ddd01c09bfb931f1beead7f Mon Sep 17 00:00:00 2001
From: Bruno Windels
Date: Tue, 12 Nov 2019 16:25:38 +0100
Subject: [PATCH 114/334] restyle e2e icons
---
res/css/views/rooms/_E2EIcon.scss | 53 +++++++++++++++++++++++----
res/img/e2e/verified.svg | 13 ++++++-
res/img/e2e/warning.svg | 16 +++++---
src/components/views/rooms/E2EIcon.js | 8 +++-
4 files changed, 75 insertions(+), 15 deletions(-)
diff --git a/res/css/views/rooms/_E2EIcon.scss b/res/css/views/rooms/_E2EIcon.scss
index 84a16611de..c609d70f4c 100644
--- a/res/css/views/rooms/_E2EIcon.scss
+++ b/res/css/views/rooms/_E2EIcon.scss
@@ -17,17 +17,56 @@ limitations under the License.
.mx_E2EIcon {
width: 25px;
height: 25px;
- mask-repeat: no-repeat;
- mask-position: center 0;
margin: 0 9px;
+ position: relative;
+ display: block;
}
-.mx_E2EIcon_verified {
- mask-image: url('$(res)/img/e2e/lock-verified.svg');
- background-color: $accent-color;
+.mx_E2EIcon_verified::before, .mx_E2EIcon_warning::before {
+ content: "";
+ display: block;
+ /* the symbols in the shield icons are cut out the make the themeable with css masking.
+ if they appear on a different background than white, the symbol wouldn't be white though, so we
+ add a rectangle here below the masked element to shine through the symbol cutout.
+ hardcoding white and not using a theme variable as this would probably be white for any theme. */
+ background-color: white;
+ position: absolute;
+ top: 0;
+ bottom: 0;
+ left: 0;
+ right: 0;
}
-.mx_E2EIcon_warning {
- mask-image: url('$(res)/img/e2e/lock-warning.svg');
+.mx_E2EIcon_verified::after, .mx_E2EIcon_warning::after {
+ content: "";
+ display: block;
+ position: absolute;
+ top: 0;
+ bottom: 0;
+ left: 0;
+ right: 0;
+ mask-repeat: no-repeat;
+ mask-size: contain;
+}
+
+.mx_E2EIcon_verified::before {
+ /* white rectangle below checkmark of shield */
+ margin: 25% 28% 38% 25%;
+}
+
+
+.mx_E2EIcon_verified::after {
+ mask-image: url('$(res)/img/e2e/verified.svg');
+ background-color: $warning-color;
+}
+
+
+.mx_E2EIcon_warning::before {
+ /* white rectangle below "!" of shield */
+ margin: 18% 40% 25% 40%;
+}
+
+.mx_E2EIcon_warning::after {
+ mask-image: url('$(res)/img/e2e/warning.svg');
background-color: $warning-color;
}
diff --git a/res/img/e2e/verified.svg b/res/img/e2e/verified.svg
index 459a552a40..af6bb92297 100644
--- a/res/img/e2e/verified.svg
+++ b/res/img/e2e/verified.svg
@@ -1,3 +1,12 @@
-