Merge branch 'develop' into travis/pinned-room-list

This commit is contained in:
Travis Ralston 2018-10-25 10:57:08 -06:00 committed by GitHub
commit 9cdb527163
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
53 changed files with 999 additions and 416 deletions

View file

@ -1,3 +1,139 @@
Changes in [0.14.1](https://github.com/matrix-org/matrix-react-sdk/releases/tag/v0.14.1) (2018-10-19)
=====================================================================================================
[Full Changelog](https://github.com/matrix-org/matrix-react-sdk/compare/v0.14.0...v0.14.1)
* Apply the user's tint once the MatrixClientPeg is moderately ready
[\#2214](https://github.com/matrix-org/matrix-react-sdk/pull/2214)
Changes in [0.14.0](https://github.com/matrix-org/matrix-react-sdk/releases/tag/v0.14.0) (2018-10-16)
=====================================================================================================
[Full Changelog](https://github.com/matrix-org/matrix-react-sdk/compare/v0.14.0-rc.1...v0.14.0)
* Phased rollout of lazy loading
[\#2218](https://github.com/matrix-org/matrix-react-sdk/pull/2218)
Changes in [0.14.0-rc.1](https://github.com/matrix-org/matrix-react-sdk/releases/tag/v0.14.0-rc.1) (2018-10-11)
===============================================================================================================
[Full Changelog](https://github.com/matrix-org/matrix-react-sdk/compare/v0.13.6...v0.14.0-rc.1)
* turn LL on by default!
[\#2209](https://github.com/matrix-org/matrix-react-sdk/pull/2209)
* Update from Weblate.
[\#2207](https://github.com/matrix-org/matrix-react-sdk/pull/2207)
* Fix quote post slate update
[\#2206](https://github.com/matrix-org/matrix-react-sdk/pull/2206)
* Handle InvalidStoreError from js-sdk
[\#2205](https://github.com/matrix-org/matrix-react-sdk/pull/2205)
* Fall back to default avatar in RR when member isn't loaded yet
[\#2204](https://github.com/matrix-org/matrix-react-sdk/pull/2204)
* Update to new version of slate
[\#2202](https://github.com/matrix-org/matrix-react-sdk/pull/2202)
* Update karma to webpack 4
[\#2203](https://github.com/matrix-org/matrix-react-sdk/pull/2203)
* More accessible buttons - take 2
[\#2194](https://github.com/matrix-org/matrix-react-sdk/pull/2194)
* log correct error code when opening log idb
[\#2200](https://github.com/matrix-org/matrix-react-sdk/pull/2200)
* show warning when LL is disabled but was enabled before
[\#2201](https://github.com/matrix-org/matrix-react-sdk/pull/2201)
* Fall back to another store if indexeddb start fails
[\#2195](https://github.com/matrix-org/matrix-react-sdk/pull/2195)
* Silence bluebird warnings
[\#2198](https://github.com/matrix-org/matrix-react-sdk/pull/2198)
* Use createObjectURL instead of readAsDataURL for videos
[\#2197](https://github.com/matrix-org/matrix-react-sdk/pull/2197)
* Revert "Use createObjectURL instead of readAsDataURL for videos"
[\#2196](https://github.com/matrix-org/matrix-react-sdk/pull/2196)
* Track how far the user travels before dismissing their user settings
[\#2183](https://github.com/matrix-org/matrix-react-sdk/pull/2183)
* Drop (IRC) suffix hacks
[\#2193](https://github.com/matrix-org/matrix-react-sdk/pull/2193)
* Use createObjectURL instead of readAsDataURL for videos
[\#2176](https://github.com/matrix-org/matrix-react-sdk/pull/2176)
* Remove old migration code
[\#2192](https://github.com/matrix-org/matrix-react-sdk/pull/2192)
* Fix brace style in TextForEvent.js
[\#2191](https://github.com/matrix-org/matrix-react-sdk/pull/2191)
* Fix error logging
[\#2190](https://github.com/matrix-org/matrix-react-sdk/pull/2190)
* Fix Promise.defer warning in ScalarAuthClient.js
[\#2188](https://github.com/matrix-org/matrix-react-sdk/pull/2188)
* Communicate early that a 3pid is required during registration if needed
[\#2180](https://github.com/matrix-org/matrix-react-sdk/pull/2180)
* try to encourage people to attach logs to bugs
[\#2185](https://github.com/matrix-org/matrix-react-sdk/pull/2185)
* Show the 'homeserver unavailable' warning when the first sync fails
[\#2182](https://github.com/matrix-org/matrix-react-sdk/pull/2182)
* allow passing initial is_url like hs_url in query params
[\#2083](https://github.com/matrix-org/matrix-react-sdk/pull/2083)
* Update karma
[\#2177](https://github.com/matrix-org/matrix-react-sdk/pull/2177)
* fudge hangup reasons
[\#2184](https://github.com/matrix-org/matrix-react-sdk/pull/2184)
* Provide more helpful errors when i18n generation fails
[\#2181](https://github.com/matrix-org/matrix-react-sdk/pull/2181)
Changes in [0.14.0-rc.1](https://github.com/matrix-org/matrix-react-sdk/releases/tag/v0.14.0-rc.1) (2018-10-11)
===============================================================================================================
[Full Changelog](https://github.com/matrix-org/matrix-react-sdk/compare/v0.13.6...v0.14.0-rc.1)
* turn LL on by default!
[\#2209](https://github.com/matrix-org/matrix-react-sdk/pull/2209)
* Update from Weblate.
[\#2207](https://github.com/matrix-org/matrix-react-sdk/pull/2207)
* Fix quote post slate update
[\#2206](https://github.com/matrix-org/matrix-react-sdk/pull/2206)
* Handle InvalidStoreError from js-sdk
[\#2205](https://github.com/matrix-org/matrix-react-sdk/pull/2205)
* Fall back to default avatar in RR when member isn't loaded yet
[\#2204](https://github.com/matrix-org/matrix-react-sdk/pull/2204)
* Update to new version of slate
[\#2202](https://github.com/matrix-org/matrix-react-sdk/pull/2202)
* Update karma to webpack 4
[\#2203](https://github.com/matrix-org/matrix-react-sdk/pull/2203)
* More accessible buttons - take 2
[\#2194](https://github.com/matrix-org/matrix-react-sdk/pull/2194)
* log correct error code when opening log idb
[\#2200](https://github.com/matrix-org/matrix-react-sdk/pull/2200)
* show warning when LL is disabled but was enabled before
[\#2201](https://github.com/matrix-org/matrix-react-sdk/pull/2201)
* Fall back to another store if indexeddb start fails
[\#2195](https://github.com/matrix-org/matrix-react-sdk/pull/2195)
* Silence bluebird warnings
[\#2198](https://github.com/matrix-org/matrix-react-sdk/pull/2198)
* Use createObjectURL instead of readAsDataURL for videos
[\#2197](https://github.com/matrix-org/matrix-react-sdk/pull/2197)
* Revert "Use createObjectURL instead of readAsDataURL for videos"
[\#2196](https://github.com/matrix-org/matrix-react-sdk/pull/2196)
* Track how far the user travels before dismissing their user settings
[\#2183](https://github.com/matrix-org/matrix-react-sdk/pull/2183)
* Drop (IRC) suffix hacks
[\#2193](https://github.com/matrix-org/matrix-react-sdk/pull/2193)
* Use createObjectURL instead of readAsDataURL for videos
[\#2176](https://github.com/matrix-org/matrix-react-sdk/pull/2176)
* Remove old migration code
[\#2192](https://github.com/matrix-org/matrix-react-sdk/pull/2192)
* Fix brace style in TextForEvent.js
[\#2191](https://github.com/matrix-org/matrix-react-sdk/pull/2191)
* Fix error logging
[\#2190](https://github.com/matrix-org/matrix-react-sdk/pull/2190)
* Fix Promise.defer warning in ScalarAuthClient.js
[\#2188](https://github.com/matrix-org/matrix-react-sdk/pull/2188)
* Communicate early that a 3pid is required during registration if needed
[\#2180](https://github.com/matrix-org/matrix-react-sdk/pull/2180)
* try to encourage people to attach logs to bugs
[\#2185](https://github.com/matrix-org/matrix-react-sdk/pull/2185)
* Show the 'homeserver unavailable' warning when the first sync fails
[\#2182](https://github.com/matrix-org/matrix-react-sdk/pull/2182)
* allow passing initial is_url like hs_url in query params
[\#2083](https://github.com/matrix-org/matrix-react-sdk/pull/2083)
* Update karma
[\#2177](https://github.com/matrix-org/matrix-react-sdk/pull/2177)
* fudge hangup reasons
[\#2184](https://github.com/matrix-org/matrix-react-sdk/pull/2184)
* Provide more helpful errors when i18n generation fails
[\#2181](https://github.com/matrix-org/matrix-react-sdk/pull/2181)
Changes in [0.13.6](https://github.com/matrix-org/matrix-react-sdk/releases/tag/v0.13.6) (2018-10-08)
=====================================================================================================
[Full Changelog](https://github.com/matrix-org/matrix-react-sdk/compare/v0.13.5...v0.13.6)

View file

@ -1,6 +1,6 @@
{
"name": "matrix-react-sdk",
"version": "0.13.6",
"version": "0.14.1",
"description": "SDK for matrix.org using React",
"author": "matrix.org",
"repository": {
@ -67,15 +67,15 @@
"flux": "2.1.1",
"focus-trap-react": "^3.0.5",
"fuse.js": "^2.2.0",
"gemini-scrollbar": "matrix-org/gemini-scrollbar#b302279",
"gemini-scrollbar": "github:matrix-org/gemini-scrollbar#b302279",
"gfm.css": "^1.1.1",
"glob": "^5.0.14",
"highlight.js": "^9.0.0",
"highlight.js": "^9.13.0",
"isomorphic-fetch": "^2.2.1",
"linkifyjs": "^2.1.6",
"lodash": "^4.13.1",
"lolex": "2.3.2",
"matrix-js-sdk": "0.11.1",
"matrix-js-sdk": "matrix-org/matrix-js-sdk#develop",
"optimist": "^0.6.1",
"pako": "^1.0.5",
"prop-types": "^15.5.8",
@ -85,16 +85,16 @@
"react-addons-css-transition-group": "15.3.2",
"react-beautiful-dnd": "^4.0.1",
"react-dom": "^15.6.0",
"react-gemini-scrollbar": "matrix-org/react-gemini-scrollbar#5e97aef",
"react-gemini-scrollbar": "github:matrix-org/react-gemini-scrollbar#5e97aef",
"resize-observer-polyfill": "^1.5.0",
"sanitize-html": "^1.18.4",
"slate": "^0.41.2",
"slate-html-serializer": "^0.6.1",
"slate-md-serializer": "matrix-org/slate-md-serializer#f7c4ad3",
"slate-md-serializer": "github:matrix-org/slate-md-serializer#f7c4ad3",
"slate-react": "^0.18.10",
"text-encoding-utf-8": "^1.0.1",
"url": "^0.11.0",
"velocity-vector": "vector-im/velocity#059e3b2",
"velocity-vector": "github:vector-im/velocity#059e3b2",
"whatwg-fetch": "^1.1.1"
},
"devDependencies": {

View file

@ -222,6 +222,11 @@ textarea {
word-wrap: break-word;
}
.mx_Dialog_buttons {
padding-right: 58px;
text-align: right;
}
.mx_Dialog button, .mx_Dialog input[type="submit"] {
@mixin mx_DialogButton;
margin-left: 0px;

View file

@ -38,7 +38,6 @@
@import "./views/dialogs/_DevtoolsDialog.scss";
@import "./views/dialogs/_EncryptedEventDialog.scss";
@import "./views/dialogs/_GroupAddressPicker.scss";
@import "./views/dialogs/_QuestionDialog.scss";
@import "./views/dialogs/_RoomUpgradeDialog.scss";
@import "./views/dialogs/_SetEmailDialog.scss";
@import "./views/dialogs/_SetMxIdDialog.scss";

View file

@ -19,6 +19,7 @@ limitations under the License.
height: unset !important;
padding-top: 13px !important;
padding-bottom: 14px !important;
order: 4;
}
.mx_LoginBox_loginButton_wrapper {

View file

@ -14,14 +14,6 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
.mx_ChatInviteDialog {
/* XXX: padding-left is on mx_Dialog but padding-right has subsequently
* been added on other dialogs. Surely all our dialogs should have consistent
* right hand padding?
*/
padding-right: 58px;
}
/* Using a textarea for this element, to circumvent autofill */
.mx_ChatInviteDialog_input,
.mx_ChatInviteDialog_input:focus

View file

@ -14,6 +14,10 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
.mx_DevTools_dialog {
padding-right: 58px;
}
.mx_DevTools_content {
margin: 10px 0;
}

View file

@ -1,18 +0,0 @@
/*
Copyright 2017 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.
*/
.mx_QuestionDialog {
padding-right: 58px;
}

View file

@ -111,6 +111,3 @@ limitations under the License.
width: 100%;
}
.mx_MemberList_outerWrapper {
height: 0px;
}

View file

@ -56,3 +56,7 @@ limitations under the License.
.mx_RoomPreviewBar_warningIcon {
padding: 12px;
}
.mx_RoomPreviewBar_spinnerIntro {
margin-top: 50px;
}

View file

@ -28,6 +28,13 @@ limitations under the License.
margin-right: 8px;
}
.mx_RoomSettings_devtoolsButton {
@mixin mx_DialogButton;
position: relative;
padding: 4px 1.5em;
margin-top: 8px;
}
.mx_RoomSettings_upgradeButton,
.mx_RoomSettings_leaveButton:hover,
.mx_RoomSettings_unbanButton:hover {

View file

@ -26,6 +26,7 @@ import EventTimelineSet from 'matrix-js-sdk/lib/models/event-timeline-set';
import createMatrixClient from './utils/createMatrixClient';
import SettingsStore from './settings/SettingsStore';
import MatrixActionCreators from './actions/MatrixActionCreators';
import {phasedRollOutExpiredForUser} from "./PhasedRollOut";
interface MatrixClientCreds {
homeserverUrl: string,
@ -124,8 +125,12 @@ class MatrixClientPeg {
// the react sdk doesn't work without this, so don't allow
opts.pendingEventOrdering = "detached";
if (SettingsStore.isFeatureEnabled('feature_lazyloading')) {
opts.lazyLoadMembers = true;
const LAZY_LOADING_FEATURE = "feature_lazyloading";
if (SettingsStore.isFeatureEnabled(LAZY_LOADING_FEATURE)) {
const userId = this.matrixClient.credentials.userId;
if (phasedRollOutExpiredForUser(userId, LAZY_LOADING_FEATURE, Date.now())) {
opts.lazyLoadMembers = true;
}
}
// Connect the matrix client to the dispatcher

65
src/PhasedRollOut.js Normal file
View file

@ -0,0 +1,65 @@
/*
Copyright 2018 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 SdkConfig from './SdkConfig';
function hashCode(str) {
let hash = 0;
let i;
let chr;
if (str.length === 0) {
return hash;
}
for (i = 0; i < str.length; i++) {
chr = str.charCodeAt(i);
hash = ((hash << 5) - hash) + chr;
hash |= 0;
}
return Math.abs(hash);
}
export function phasedRollOutExpiredForUser(username, feature, now, rollOutConfig = SdkConfig.get().phasedRollOut) {
if (!rollOutConfig) {
console.log(`no phased rollout configuration, so enabling ${feature}`);
return true;
}
const featureConfig = rollOutConfig[feature];
if (!featureConfig) {
console.log(`${feature} doesn't have phased rollout configured, so enabling`);
return true;
}
if (!Number.isFinite(featureConfig.offset) || !Number.isFinite(featureConfig.period)) {
console.error(`phased rollout of ${feature} is misconfigured, ` +
`offset and/or period are not numbers, so disabling`, featureConfig);
return false;
}
const hash = hashCode(username);
//ms -> min, enable users at minute granularity
const bucketRatio = 1000 * 60;
const bucketCount = featureConfig.period / bucketRatio;
const userBucket = hash % bucketCount;
const userMs = userBucket * bucketRatio;
const enableAt = featureConfig.offset + userMs;
const result = now >= enableAt;
const bucketStr = `(bucket ${userBucket}/${bucketCount})`;
if (result) {
console.log(`${feature} enabled for ${username} ${bucketStr}`);
} else {
console.log(`${feature} will be enabled for ${username} in ${Math.ceil((enableAt - now)/1000)}s ${bucketStr}`);
}
return result;
}

View file

@ -20,7 +20,7 @@ limitations under the License.
import React from 'react';
import {_t} from '../languageHandler';
import AutocompleteProvider from './AutocompleteProvider';
import FuzzyMatcher from './FuzzyMatcher';
import QueryMatcher from './QueryMatcher';
import {TextualCompletion} from './Components';
import type {Completion, SelectionRange} from "./Autocompleter";
import {CommandMap} from '../SlashCommands';
@ -32,7 +32,7 @@ const COMMAND_RE = /(^\/\w*)(?: .*)?/g;
export default class CommandProvider extends AutocompleteProvider {
constructor() {
super(COMMAND_RE);
this.matcher = new FuzzyMatcher(COMMANDS, {
this.matcher = new QueryMatcher(COMMANDS, {
keys: ['command', 'args', 'description'],
});
}

View file

@ -19,7 +19,7 @@ import React from 'react';
import { _t } from '../languageHandler';
import AutocompleteProvider from './AutocompleteProvider';
import MatrixClientPeg from '../MatrixClientPeg';
import FuzzyMatcher from './FuzzyMatcher';
import QueryMatcher from './QueryMatcher';
import {PillCompletion} from './Components';
import sdk from '../index';
import _sortBy from 'lodash/sortBy';
@ -41,7 +41,7 @@ function score(query, space) {
export default class CommunityProvider extends AutocompleteProvider {
constructor() {
super(COMMUNITY_REGEX);
this.matcher = new FuzzyMatcher([], {
this.matcher = new QueryMatcher([], {
keys: ['groupId', 'name', 'shortDescription'],
});
}

View file

@ -20,7 +20,7 @@ import React from 'react';
import { _t } from '../languageHandler';
import AutocompleteProvider from './AutocompleteProvider';
import {shortnameToUnicode, asciiRegexp, unicodeRegexp} from 'emojione';
import FuzzyMatcher from './FuzzyMatcher';
import QueryMatcher from './QueryMatcher';
import sdk from '../index';
import {PillCompletion} from './Components';
import type {Completion, SelectionRange} from './Autocompleter';
@ -84,12 +84,12 @@ function score(query, space) {
export default class EmojiProvider extends AutocompleteProvider {
constructor() {
super(EMOJI_REGEX);
this.matcher = new FuzzyMatcher(EMOJI_SHORTNAMES, {
this.matcher = new QueryMatcher(EMOJI_SHORTNAMES, {
keys: ['aliases_ascii', 'shortname', 'aliases'],
// For matching against ascii equivalents
shouldMatchWordsOnly: false,
});
this.nameMatcher = new FuzzyMatcher(EMOJI_SHORTNAMES, {
this.nameMatcher = new QueryMatcher(EMOJI_SHORTNAMES, {
keys: ['name'],
// For removing punctuation
shouldMatchWordsOnly: true,

View file

@ -1,107 +0,0 @@
/*
Copyright 2017 Aviral Dasgupta
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 Levenshtein from 'liblevenshtein';
//import _at from 'lodash/at';
//import _flatMap from 'lodash/flatMap';
//import _sortBy from 'lodash/sortBy';
//import _sortedUniq from 'lodash/sortedUniq';
//import _keys from 'lodash/keys';
//
//class KeyMap {
// keys: Array<String>;
// objectMap: {[String]: Array<Object>};
// priorityMap: {[String]: number}
//}
//
//const DEFAULT_RESULT_COUNT = 10;
//const DEFAULT_DISTANCE = 5;
// FIXME Until Fuzzy matching works better, we use prefix matching.
import PrefixMatcher from './QueryMatcher';
export default PrefixMatcher;
//class FuzzyMatcher { // eslint-disable-line no-unused-vars
// /**
// * @param {object[]} objects the objects to perform a match on
// * @param {string[]} keys an array of keys within each object to match on
// * Keys can refer to object properties by name and as in JavaScript (for nested properties)
// *
// * To use, simply presort objects by required criteria, run through this function and create a FuzzyMatcher with the
// * resulting KeyMap.
// *
// * TODO: Handle arrays and objects (Fuse did this, RoomProvider uses it)
// * @return {KeyMap}
// */
// static valuesToKeyMap(objects: Array<Object>, keys: Array<String>): KeyMap {
// const keyMap = new KeyMap();
// const map = {};
// const priorities = {};
//
// objects.forEach((object, i) => {
// const keyValues = _at(object, keys);
// console.log(object, keyValues, keys);
// for (const keyValue of keyValues) {
// if (!map.hasOwnProperty(keyValue)) {
// map[keyValue] = [];
// }
// map[keyValue].push(object);
// }
// priorities[object] = i;
// });
//
// keyMap.objectMap = map;
// keyMap.priorityMap = priorities;
// keyMap.keys = _sortBy(_keys(map), [(value) => priorities[value]]);
// return keyMap;
// }
//
// constructor(objects: Array<Object>, options: {[Object]: Object} = {}) {
// this.options = options;
// this.keys = options.keys;
// this.setObjects(objects);
// }
//
// setObjects(objects: Array<Object>) {
// this.keyMap = FuzzyMatcher.valuesToKeyMap(objects, this.keys);
// console.log(this.keyMap.keys);
// this.matcher = new Levenshtein.Builder()
// .dictionary(this.keyMap.keys, true)
// .algorithm('transposition')
// .sort_candidates(false)
// .case_insensitive_sort(true)
// .include_distance(true)
// .maximum_candidates(this.options.resultCount || DEFAULT_RESULT_COUNT) // result count 0 doesn't make much sense
// .build();
// }
//
// match(query: String): Array<Object> {
// const candidates = this.matcher.transduce(query, this.options.distance || DEFAULT_DISTANCE);
// // TODO FIXME This is hideous. Clean up when possible.
// const val = _sortedUniq(_sortBy(_flatMap(candidates, (candidate) => {
// return this.keyMap.objectMap[candidate[0]].map((value) => {
// return {
// distance: candidate[1],
// ...value,
// };
// });
// }),
// [(candidate) => candidate.distance, (candidate) => this.keyMap.priorityMap[candidate]]));
// console.log(val);
// return val;
// }
//}

View file

@ -2,6 +2,7 @@
/*
Copyright 2017 Aviral Dasgupta
Copyright 2018 Michael Telatynski <7t3chguy@gmail.com>
Copyright 2018 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.
@ -20,99 +21,99 @@ import _at from 'lodash/at';
import _flatMap from 'lodash/flatMap';
import _sortBy from 'lodash/sortBy';
import _uniq from 'lodash/uniq';
import _keys from 'lodash/keys';
class KeyMap {
keys: Array<String>;
objectMap: {[String]: Array<Object>};
priorityMap = new Map();
}
function stripDiacritics(str: string): string {
return str.normalize('NFD').replace(/[\u0300-\u036f]/g, '');
}
/**
* Simple search matcher that matches any results with the query string anywhere
* in the search string. Returns matches in the order the query string appears
* in the search key, earliest first, then in the order the items appeared in
* the source array.
*
* @param {Object[]} objects Initial list of objects. Equivalent to calling
* setObjects() after construction
* @param {Object} options Options object
* @param {string[]} options.keys List of keys to use as indexes on the objects
* @param {function[]} options.funcs List of functions that when called with the
* object as an arg will return a string to use as an index
*/
export default class QueryMatcher {
/**
* @param {object[]} objects the objects to perform a match on
* @param {string[]} keys an array of keys within each object to match on
* Keys can refer to object properties by name and as in JavaScript (for nested properties)
*
* To use, simply presort objects by required criteria, run through this function and create a QueryMatcher with the
* resulting KeyMap.
*
* TODO: Handle arrays and objects (Fuse did this, RoomProvider uses it)
* @return {KeyMap}
*/
static valuesToKeyMap(objects: Array<Object>, keys: Array<String>): KeyMap {
const keyMap = new KeyMap();
const map = {};
objects.forEach((object, i) => {
const keyValues = _at(object, keys);
for (const keyValue of keyValues) {
const key = stripDiacritics(keyValue).toLowerCase();
if (!map.hasOwnProperty(key)) {
map[key] = [];
}
map[key].push(object);
}
keyMap.priorityMap.set(object, i);
});
keyMap.objectMap = map;
keyMap.keys = _keys(map);
return keyMap;
}
constructor(objects: Array<Object>, options: {[Object]: Object} = {}) {
this.options = options;
this.keys = options.keys;
this._options = options;
this._keys = options.keys;
this._funcs = options.funcs || [];
this.setObjects(objects);
// By default, we remove any non-alphanumeric characters ([^A-Za-z0-9_]) from the
// query and the value being queried before matching
if (this.options.shouldMatchWordsOnly === undefined) {
this.options.shouldMatchWordsOnly = true;
if (this._options.shouldMatchWordsOnly === undefined) {
this._options.shouldMatchWordsOnly = true;
}
// By default, match anywhere in the string being searched. If enabled, only return
// matches that are prefixed with the query.
if (this.options.shouldMatchPrefix === undefined) {
this.options.shouldMatchPrefix = false;
if (this._options.shouldMatchPrefix === undefined) {
this._options.shouldMatchPrefix = false;
}
}
setObjects(objects: Array<Object>) {
this.keyMap = QueryMatcher.valuesToKeyMap(objects, this.keys);
this._items = new Map();
for (const object of objects) {
const keyValues = _at(object, this._keys);
for (const f of this._funcs) {
keyValues.push(f(object));
}
for (const keyValue of keyValues) {
const key = stripDiacritics(keyValue).toLowerCase();
if (!this._items.has(key)) {
this._items.set(key, []);
}
this._items.get(key).push(object);
}
}
}
match(query: String): Array<Object> {
query = stripDiacritics(query).toLowerCase();
if (this.options.shouldMatchWordsOnly) {
if (this._options.shouldMatchWordsOnly) {
query = query.replace(/[^\w]/g, '');
}
if (query.length === 0) {
return [];
}
const results = [];
this.keyMap.keys.forEach((key) => {
// Iterate through the map & check each key.
// ES6 Map iteration order is defined to be insertion order, so results
// here will come out in the order they were put in.
for (const key of this._items.keys()) {
let resultKey = key;
if (this.options.shouldMatchWordsOnly) {
if (this._options.shouldMatchWordsOnly) {
resultKey = resultKey.replace(/[^\w]/g, '');
}
const index = resultKey.indexOf(query);
if (index !== -1 && (!this.options.shouldMatchPrefix || index === 0)) {
if (index !== -1 && (!this._options.shouldMatchPrefix || index === 0)) {
results.push({key, index});
}
}
// Sort them by where the query appeared in the search key
// lodash sortBy is a stable sort, so results where the query
// appeared in the same place will retain their order with
// respect to each other.
const sortedResults = _sortBy(results, (candidate) => {
return candidate.index;
});
return _uniq(_flatMap(_sortBy(results, (candidate) => {
return candidate.index;
}).map((candidate) => {
// return an array of objects (those given to setObjects) that have the given
// key as a property.
return this.keyMap.objectMap[candidate.key];
})));
// Now map the keys to the result objects. Each result object is a list, so
// flatMap will flatten those lists out into a single list. Also remove any
// duplicates.
return _uniq(_flatMap(sortedResults, (candidate) => this._items.get(candidate.key)));
}
}

View file

@ -21,7 +21,7 @@ import React from 'react';
import { _t } from '../languageHandler';
import AutocompleteProvider from './AutocompleteProvider';
import MatrixClientPeg from '../MatrixClientPeg';
import FuzzyMatcher from './FuzzyMatcher';
import QueryMatcher from './QueryMatcher';
import {PillCompletion} from './Components';
import {getDisplayAliasForRoom} from '../Rooms';
import sdk from '../index';
@ -43,7 +43,7 @@ function score(query, space) {
export default class RoomProvider extends AutocompleteProvider {
constructor() {
super(ROOM_REGEX);
this.matcher = new FuzzyMatcher([], {
this.matcher = new QueryMatcher([], {
keys: ['displayedAlias', 'name'],
});
}

View file

@ -23,7 +23,7 @@ import { _t } from '../languageHandler';
import AutocompleteProvider from './AutocompleteProvider';
import {PillCompletion} from './Components';
import sdk from '../index';
import FuzzyMatcher from './FuzzyMatcher';
import QueryMatcher from './QueryMatcher';
import _sortBy from 'lodash/sortBy';
import MatrixClientPeg from '../MatrixClientPeg';
@ -44,8 +44,9 @@ export default class UserProvider extends AutocompleteProvider {
constructor(room) {
super(USER_REGEX, FORCED_USER_REGEX);
this.room = room;
this.matcher = new FuzzyMatcher([], {
keys: ['name', 'userId'],
this.matcher = new QueryMatcher([], {
keys: ['name'],
funcs: [obj => obj.userId.slice(1)], // index by user id minus the leading '@'
shouldMatchPrefix: true,
shouldMatchWordsOnly: false,
});
@ -104,7 +105,9 @@ export default class UserProvider extends AutocompleteProvider {
const fullMatch = command[0];
// Don't search if the query is a single "@"
if (fullMatch && fullMatch !== '@') {
completions = this.matcher.match(fullMatch).map((user) => {
// Don't include the '@' in our search query - it's only used as a way to trigger completion
const query = fullMatch.startsWith('@') ? fullMatch.substring(1) : fullMatch;
completions = this.matcher.match(query).map((user) => {
const displayName = (user.name || user.userId || '');
return {
// Length of completion should equal length of text in decorator. draft-js

View file

@ -746,13 +746,37 @@ export default React.createClass({
});
},
_leaveGroupWarnings: function() {
const warnings = [];
if (this.state.isUserPrivileged) {
warnings.push((
<span className="warning">
{ " " /* Whitespace, otherwise the sentences get smashed together */ }
{ _t("You are an administrator of this community. You will not be " +
"able to rejoin without an invite from another administrator.") }
</span>
));
}
return warnings;
},
_onLeaveClick: function() {
const QuestionDialog = sdk.getComponent("dialogs.QuestionDialog");
const warnings = this._leaveGroupWarnings();
Modal.createTrackedDialog('Leave Group', '', QuestionDialog, {
title: _t("Leave Community"),
description: _t("Leave %(groupName)s?", {groupName: this.props.groupId}),
description: (
<span>
{ _t("Leave %(groupName)s?", {groupName: this.props.groupId}) }
{ warnings }
</span>
),
button: _t("Leave"),
danger: true,
danger: this.state.isUserPrivileged,
onFinished: async (confirmed) => {
if (!confirmed) return;

View file

@ -181,14 +181,8 @@ var LeftPanel = React.createClass({
const BottomLeftMenu = sdk.getComponent('structures.BottomLeftMenu');
const CallPreview = sdk.getComponent('voip.CallPreview');
let topBox;
if (this.context.matrixClient.isGuest()) {
const LoginBox = sdk.getComponent('structures.LoginBox');
topBox = <LoginBox collapsed={ this.props.collapsed }/>;
} else {
const SearchBox = sdk.getComponent('structures.SearchBox');
topBox = <SearchBox collapsed={ this.props.collapsed } onSearch={ this.onSearch } />;
}
const SearchBox = sdk.getComponent('structures.SearchBox');
const topBox = <SearchBox collapsed={ this.props.collapsed } onSearch={ this.onSearch } />;
const classes = classNames(
"mx_LeftPanel",

View file

@ -1,5 +1,6 @@
/*
Copyright 2017 Vector Creations Ltd
Copyright 2018 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.
@ -16,31 +17,15 @@ limitations under the License.
'use strict';
var React = require('react');
const React = require('react');
import { _t } from '../../languageHandler';
var sdk = require('../../index')
var dis = require('../../dispatcher');
var rate_limited_func = require('../../ratelimitedfunc');
var AccessibleButton = require('../../components/views/elements/AccessibleButton');
const dis = require('../../dispatcher');
const AccessibleButton = require('../../components/views/elements/AccessibleButton');
module.exports = React.createClass({
displayName: 'LoginBox',
propTypes: {
collapsed: React.PropTypes.bool,
},
onToggleCollapse: function(show) {
if (show) {
dis.dispatch({
action: 'show_left_panel',
});
}
else {
dis.dispatch({
action: 'hide_left_panel',
});
}
},
onLoginClick: function() {
@ -52,41 +37,20 @@ module.exports = React.createClass({
},
render: function() {
var TintableSvg = sdk.getComponent('elements.TintableSvg');
var toggleCollapse;
if (this.props.collapsed) {
toggleCollapse =
<AccessibleButton className="mx_SearchBox_maximise" onClick={ this.onToggleCollapse.bind(this, true) }>
<TintableSvg src="img/maximise.svg" width="10" height="16" alt="Expand panel"/>
const loginButton = (
<div className="mx_LoginBox_loginButton_wrapper">
<AccessibleButton className="mx_LoginBox_loginButton" element="button" onClick={this.onLoginClick}>
{ _t("Login") }
</AccessibleButton>
}
else {
toggleCollapse =
<AccessibleButton className="mx_SearchBox_minimise" onClick={ this.onToggleCollapse.bind(this, false) }>
<TintableSvg src="img/minimise.svg" width="10" height="16" alt="Collapse panel"/>
<AccessibleButton className="mx_LoginBox_registerButton" element="button" onClick={this.onRegisterClick}>
{ _t("Register") }
</AccessibleButton>
}
</div>
);
var loginButton;
if (!this.props.collapsed) {
loginButton = (
<div className="mx_LoginBox_loginButton_wrapper">
<AccessibleButton className="mx_LoginBox_loginButton" element="button" onClick={this.onLoginClick}>
{ _t("Login") }
</AccessibleButton>
<AccessibleButton className="mx_LoginBox_registerButton" element="button" onClick={this.onRegisterClick}>
{ _t("Register") }
</AccessibleButton>
</div>
);
}
var self = this;
return (
<div className="mx_SearchBox mx_LoginBox">
<div className="mx_LoginBox">
{ loginButton }
{ toggleCollapse }
</div>
);
}

View file

@ -1034,6 +1034,7 @@ export default React.createClass({
{ warnings }
</span>
),
button: _t("Leave"),
onFinished: (shouldLeave) => {
if (shouldLeave) {
const d = MatrixClientPeg.get().leave(roomId);
@ -1403,6 +1404,11 @@ export default React.createClass({
break;
}
});
// Fire the tinter right on startup to ensure the default theme is applied
// A later sync can/will correct the tint to be the right value for the user
const colorScheme = SettingsStore.getValue("roomColor");
Tinter.tint(colorScheme.primary_color, colorScheme.secondary_color);
},
/**

View file

@ -66,6 +66,10 @@ module.exports = React.createClass({
// result in "X, Y, Z and 100 others are typing."
whoIsTypingLimit: PropTypes.number,
// true if the room is being peeked at. This affects components that shouldn't
// logically be shown when peeking, such as a prompt to invite people to a room.
isPeeking: PropTypes.bool,
// callback for when the user clicks on the 'resend all' button in the
// 'unsent messages' bar
onResendAllClick: PropTypes.func,
@ -457,7 +461,7 @@ module.exports = React.createClass({
}
// If you're alone in the room, and have sent a message, suggest to invite someone
if (this.props.sentMessageAndIsAlone) {
if (this.props.sentMessageAndIsAlone && !this.props.isPeeking) {
return (
<div className="mx_RoomStatusBar_isAlone">
{ _t("There's no one else here! Would you like to <inviteText>invite others</inviteText> " +

View file

@ -678,8 +678,8 @@ module.exports = React.createClass({
if (!room) return;
console.log("Tinter.tint from updateTint");
const color_scheme = SettingsStore.getValue("roomColor", room.roomId);
Tinter.tint(color_scheme.primary_color, color_scheme.secondary_color);
const colorScheme = SettingsStore.getValue("roomColor", room.roomId);
Tinter.tint(colorScheme.primary_color, colorScheme.secondary_color);
},
onAccountData: function(event) {
@ -694,10 +694,10 @@ module.exports = React.createClass({
if (room.roomId == this.state.roomId) {
const type = event.getType();
if (type === "org.matrix.room.color_scheme") {
const color_scheme = event.getContent();
const colorScheme = event.getContent();
// XXX: we should validate the event
console.log("Tinter.tint from onRoomAccountData");
Tinter.tint(color_scheme.primary_color, color_scheme.secondary_color);
Tinter.tint(colorScheme.primary_color, colorScheme.secondary_color);
} else if (type === "org.matrix.room.preview_urls" || type === "im.vector.web.settings") {
// non-e2ee url previews are stored in legacy event type `org.matrix.room.preview_urls`
this._updatePreviewUrlVisibility(room);
@ -1514,6 +1514,7 @@ module.exports = React.createClass({
canPreview={false} error={this.state.roomLoadError}
roomAlias={roomAlias}
spinner={this.state.joining}
spinnerState="joining"
inviterName={inviterName}
invitedEmail={invitedEmail}
room={this.state.room}
@ -1558,6 +1559,7 @@ module.exports = React.createClass({
inviterName={inviterName}
canPreview={false}
spinner={this.state.joining}
spinnerState="joining"
room={this.state.room}
/>
</div>
@ -1595,6 +1597,7 @@ module.exports = React.createClass({
atEndOfLiveTimeline={this.state.atEndOfLiveTimeline}
sentMessageAndIsAlone={this.state.isAlone}
hasActiveCall={inCall}
isPeeking={myMembership !== "join"}
onInviteClick={this.onInviteButtonClick}
onStopWarningClick={this.onStopAloneWarningClick}
onScrollToBottomClick={this.jumpToLiveTimeline}
@ -1644,6 +1647,7 @@ module.exports = React.createClass({
onForgetClick={this.onForgetClick}
onRejectClick={this.onRejectThreepidInviteButtonClicked}
spinner={this.state.joining}
spinnerState="joining"
inviterName={inviterName}
invitedEmail={invitedEmail}
canPreview={this.state.canPeek}
@ -1669,7 +1673,7 @@ module.exports = React.createClass({
let messageComposer, searchInfo;
const canSpeak = (
// joined and not showing search results
myMembership == 'join' && !this.state.searchResults
myMembership === 'join' && !this.state.searchResults
);
if (canSpeak) {
messageComposer =
@ -1683,6 +1687,11 @@ module.exports = React.createClass({
/>;
}
if (MatrixClientPeg.get().isGuest()) {
const LoginBox = sdk.getComponent('structures.LoginBox');
messageComposer = <LoginBox/>;
}
// TODO: Why aren't we storing the term/scope/count in this format
// in this.state if this is what RoomHeader desires?
if (this.state.searchResults) {

View file

@ -84,6 +84,7 @@ const SIMPLE_SETTINGS = [
{ id: "RoomSubList.showEmpty" },
{ id: "pinMentionedRooms" },
{ id: "pinUnreadRooms" },
{ id: "showDeveloperTools" },
];
// These settings must be defined in SettingsStore

View file

@ -625,7 +625,7 @@ export default class DevtoolsDialog extends React.Component {
let body;
if (this.state.mode) {
body = <div>
body = <div className="mx_DevTools_dialog">
<div className="mx_DevTools_label_left">{ this.state.mode.getLabel() }</div>
<div className="mx_DevTools_label_right">Room ID: { this.props.roomId }</div>
<div className="mx_DevTools_label_bottom" />
@ -634,7 +634,7 @@ export default class DevtoolsDialog extends React.Component {
} else {
const classes = "mx_DevTools_RoomStateExplorer_button";
body = <div>
<div>
<div className="mx_DevTools_dialog">
<div className="mx_DevTools_label_left">{ _t('Toolbox') }</div>
<div className="mx_DevTools_label_right">Room ID: { this.props.roomId }</div>
<div className="mx_DevTools_label_bottom" />

View file

@ -70,15 +70,15 @@ module.exports = React.createClass({
}
return (
<div className="mx_Dialog_buttons">
{ cancelButton }
{ this.props.children }
<button className={primaryButtonClassName}
onClick={this.props.onPrimaryButtonClick}
autoFocus={this.props.focus}
disabled={this.props.disabled}
onClick={this.props.onPrimaryButtonClick}
autoFocus={this.props.focus}
disabled={this.props.disabled}
>
{ this.props.primaryButton }
</button>
{ this.props.children }
{ cancelButton }
</div>
);
},

View file

@ -165,7 +165,7 @@ export default React.createClass({
return (
<div className="mx_MemberList">
{ inputBox }
<GeminiScrollbarWrapper autoshow={true} className="mx_MemberList_outerWrapper">
<GeminiScrollbarWrapper autoshow={true}>
{ joined }
{ invited }
</GeminiScrollbarWrapper>

View file

@ -22,6 +22,7 @@ import classnames from 'classnames';
import sdk from '../../../index';
import { _t } from '../../../languageHandler';
import SettingsStore from "../../../settings/SettingsStore";
/* This file contains a collection of components which are used by the
* InteractiveAuth to prompt the user to enter the information needed
@ -209,6 +210,125 @@ export const RecaptchaAuthEntry = React.createClass({
},
});
export const TermsAuthEntry = React.createClass({
displayName: 'TermsAuthEntry',
statics: {
LOGIN_TYPE: "m.login.terms",
},
propTypes: {
submitAuthDict: PropTypes.func.isRequired,
stageParams: PropTypes.object.isRequired,
errorText: PropTypes.string,
busy: PropTypes.bool,
},
componentWillMount: function() {
// example stageParams:
//
// {
// "policies": {
// "privacy_policy": {
// "version": "1.0",
// "en": {
// "name": "Privacy Policy",
// "url": "https://example.org/privacy-1.0-en.html",
// },
// "fr": {
// "name": "Politique de confidentialité",
// "url": "https://example.org/privacy-1.0-fr.html",
// },
// },
// "other_policy": { ... },
// }
// }
const allPolicies = this.props.stageParams.policies || {};
const prefLang = SettingsStore.getValue("language");
const initToggles = {};
const pickedPolicies = [];
for (const policyId of Object.keys(allPolicies)) {
const policy = allPolicies[policyId];
// Pick a language based on the user's language, falling back to english,
// and finally to the first language available. If there's still no policy
// available then the homeserver isn't respecting the spec.
let langPolicy = policy[prefLang];
if (!langPolicy) langPolicy = policy["en"];
if (!langPolicy) {
// last resort
const firstLang = Object.keys(policy).find(e => e !== "version");
langPolicy = policy[firstLang];
}
if (!langPolicy) throw new Error("Failed to find a policy to show the user");
initToggles[policyId] = false;
langPolicy.id = policyId;
pickedPolicies.push(langPolicy);
}
this.setState({
"toggledPolicies": initToggles,
"policies": pickedPolicies,
});
},
_trySubmit: function(policyId) {
const newToggles = {};
let allChecked = true;
for (const policy of this.state.policies) {
let checked = this.state.toggledPolicies[policy.id];
if (policy.id === policyId) checked = !checked;
newToggles[policy.id] = checked;
allChecked = allChecked && checked;
}
this.setState({"toggledPolicies": newToggles});
if (allChecked) this.props.submitAuthDict({type: TermsAuthEntry.LOGIN_TYPE});
},
render: function() {
if (this.props.busy) {
const Loader = sdk.getComponent("elements.Spinner");
return <Loader />;
}
let checkboxes = [];
let allChecked = true;
for (const policy of this.state.policies) {
const checked = this.state.toggledPolicies[policy.id];
allChecked = allChecked && checked;
checkboxes.push(
<label key={"policy_checkbox_" + policy.id}>
<input type="checkbox" onClick={() => this._trySubmit(policy.id)} checked={checked} />
<a href={policy.url} target="_blank" rel="noopener">{ policy.name }</a>
</label>
);
}
let errorSection;
if (this.props.errorText) {
errorSection = (
<div className="error" role="alert">
{ this.props.errorText }
</div>
);
}
return (
<div>
<p>{_t("Please review and accept the policies of this homeserver:")}</p>
{ checkboxes }
{ errorSection }
</div>
);
},
});
export const EmailIdentityAuthEntry = React.createClass({
displayName: 'EmailIdentityAuthEntry',
@ -496,6 +616,7 @@ const AuthEntryComponents = [
RecaptchaAuthEntry,
EmailIdentityAuthEntry,
MsisdnAuthEntry,
TermsAuthEntry,
];
export function getEntryComponentForLoginType(loginType) {

View file

@ -220,8 +220,9 @@ module.exports = React.createClass({
let canonical_alias_section;
if (this.props.canSetCanonicalAlias) {
let found = false;
const canonicalValue = this.state.canonicalAlias || "";
canonical_alias_section = (
<select onChange={this.onCanonicalAliasChange} value={this.state.canonicalAlias}>
<select onChange={this.onCanonicalAliasChange} value={canonicalValue}>
<option value="" key="unset">{ _t('not specified') }</option>
{
Object.keys(self.state.domainToAliases).map((domain, i) => {

View file

@ -447,7 +447,7 @@ module.exports = React.createClass({
return (
<div className="mx_MemberList">
{ inputBox }
<GeminiScrollbarWrapper autoshow={true} className="mx_MemberList_joined mx_MemberList_outerWrapper">
<GeminiScrollbarWrapper autoshow={true} className="mx_MemberList_joined">
<TruncatedList className="mx_MemberList_wrapper" truncateAt={this.state.truncateAtJoined}
createOverflowElement={this._createOverflowTileJoined}
getChildren={this._getChildrenJoined}

View file

@ -573,29 +573,42 @@ export default class MessageComposerInput extends React.Component {
}
// emojioneify any emoji
editorState.document.getTexts().forEach(node => {
if (node.text !== '' && HtmlUtils.containsEmoji(node.text)) {
let match;
while ((match = EMOJI_REGEX.exec(node.text)) !== null) {
const range = Range.create({
anchor: {
key: node.key,
offset: match.index,
},
focus: {
key: node.key,
offset: match.index + match[0].length,
},
});
const inline = Inline.create({
type: 'emoji',
data: { emojiUnicode: match[0] },
});
change = change.insertInlineAtRange(range, inline);
editorState = change.value;
let foundEmoji;
do {
foundEmoji = false;
for (const node of editorState.document.getTexts()) {
if (node.text !== '' && HtmlUtils.containsEmoji(node.text)) {
let match;
EMOJI_REGEX.lastIndex = 0;
while ((match = EMOJI_REGEX.exec(node.text)) !== null) {
const range = Range.create({
anchor: {
key: node.key,
offset: match.index,
},
focus: {
key: node.key,
offset: match.index + match[0].length,
},
});
const inline = Inline.create({
type: 'emoji',
data: { emojiUnicode: match[0] },
});
change = change.insertInlineAtRange(range, inline);
editorState = change.value;
// if we replaced an emoji, start again looking for more
// emoji in the new editor state since doing the replacement
// will change the node structure & offsets so we can't compute
// insertion ranges from node.key / match.index anymore.
foundEmoji = true;
break;
}
}
}
});
} while (foundEmoji);
// work around weird bug where inserting emoji via the macOS
// emoji picker can leave the selection stuck in the emoji's

View file

@ -44,9 +44,13 @@ module.exports = React.createClass({
error: PropTypes.object,
canPreview: PropTypes.bool,
spinner: PropTypes.bool,
room: PropTypes.object,
// When a spinner is present, a spinnerState can be specified to indicate the
// purpose of the spinner.
spinner: PropTypes.bool,
spinnerState: PropTypes.oneOf(["joining"]),
// The alias that was used to access this room, if appropriate
// If given, this will be how the room is referred to (eg.
// in error messages).
@ -93,7 +97,12 @@ module.exports = React.createClass({
if (this.props.spinner || this.state.busy) {
const Spinner = sdk.getComponent("elements.Spinner");
let spinnerIntro = "";
if (this.props.spinnerState === "joining") {
spinnerIntro = _t("Joining room...");
}
return (<div className="mx_RoomPreviewBar">
<p className="mx_RoomPreviewBar_spinnerIntro">{ spinnerIntro }</p>
<Spinner />
</div>);
}

View file

@ -590,6 +590,11 @@ module.exports = React.createClass({
}
},
_openDevtools: function() {
const DevtoolsDialog = sdk.getComponent('dialogs.DevtoolsDialog');
Modal.createDialog(DevtoolsDialog, {roomId: this.props.room.roomId});
},
_renderEncryptionSection: function() {
const SettingsFlag = sdk.getComponent("elements.SettingsFlag");
@ -942,6 +947,11 @@ module.exports = React.createClass({
</AccessibleButton>;
}
const devtoolsButton = SettingsStore.getValue("showDeveloperTools") ?
(<AccessibleButton className="mx_RoomSettings_devtoolsButton" onClick={this._openDevtools}>
{ _t("Open Devtools") }
</AccessibleButton>) : null;
return (
<div className="mx_RoomSettings">
@ -1055,6 +1065,7 @@ module.exports = React.createClass({
{ _t('Internal room ID: ') } <code>{ this.props.room.roomId }</code><br />
{ _t('Room version number: ') } <code>{ this.props.room.getVersion() }</code><br />
{ roomUpgradeButton }
{ devtoolsButton }
</div>
</div>
);

View file

@ -1288,5 +1288,8 @@
"You've previously used Riot on %(host)s with lazy loading of members enabled. In this version lazy loading is disabled. As the local cache is not compatible between these two settings, Riot needs to resync your account.": "Преди сте използвали Riot на %(host)s с включено постепенно зареждане на членове. В тази версия, тази настройка е изключена. Понеже локалният кеш не е съвместим при тези две настройки, Riot трябва да синхронизира акаунта Ви наново.",
"If the other version of Riot is still open in another tab, please close it as using Riot on the same host with both lazy loading enabled and disabled simultaneously will cause issues.": "Ако другата версия на Riot все още е отворена в друг таб, моля затворете я. Използването на Riot на един адрес във версии с постепенно и без постепенно зареждане ще причини проблеми.",
"Incompatible local cache": "Несъвместим локален кеш",
"Clear cache and resync": "Изчисти кеша и ресинхронизирай"
"Clear cache and resync": "Изчисти кеша и ресинхронизирай",
"Please accept all of the policies": "Моля, приемете всички политики",
"Please review and accept the policies of this homeserver:": "Моля, прегледайте и приемете политиките на този сървър:",
"Add some now": "Добави сега"
}

View file

@ -1289,5 +1289,7 @@
"You've previously used Riot on %(host)s with lazy loading of members enabled. In this version lazy loading is disabled. As the local cache is not compatible between these two settings, Riot needs to resync your account.": "Du hast zuvor Riot auf %(host)s ohne verzögertem Laden von Mitgliedern genutzt. In dieser Version war das verzögerte Laden deaktiviert. Da die lokal zwischengespeicherten Daten zwischen diesen Einstellungen nicht kompatibel ist, muss Riot dein Konto neu synchronisieren.",
"If the other version of Riot is still open in another tab, please close it as using Riot on the same host with both lazy loading enabled and disabled simultaneously will cause issues.": "Wenn Riot mit der alten Version in einem anderen Tab geöffnet ist, schließe dies bitte, da das parallele Nutzen von Riot auf demselben Host mit aktivierten und deaktivierten verzögertem Laden, Probleme verursachen wird.",
"Incompatible local cache": "Inkompatibler lokaler Zwischenspeicher",
"Clear cache and resync": "Zwischenspeicher löschen und erneut synchronisieren"
"Clear cache and resync": "Zwischenspeicher löschen und erneut synchronisieren",
"Please accept all of the policies": "Bitte akzeptiere alle Bedingungen",
"Please review and accept the policies of this homeserver:": "Bitte sieh dir alle Bedingungen dieses Heimservers an und akzeptiere sie:"
}

View file

@ -43,10 +43,6 @@
"The file '%(fileName)s' failed to upload": "The file '%(fileName)s' failed to upload",
"The file '%(fileName)s' exceeds this home server's size limit for uploads": "The file '%(fileName)s' exceeds this home server's size limit for uploads",
"Upload Failed": "Upload Failed",
"Failure to create room": "Failure to create room",
"Server may be unavailable, overloaded, or you hit a bug.": "Server may be unavailable, overloaded, or you hit a bug.",
"Send anyway": "Send anyway",
"Send": "Send",
"Sun": "Sun",
"Mon": "Mon",
"Tue": "Tue",
@ -86,7 +82,6 @@
"Failed to invite users to community": "Failed to invite users to community",
"Failed to invite users to %(groupId)s": "Failed to invite users to %(groupId)s",
"Failed to add the following rooms to %(groupId)s:": "Failed to add the following rooms to %(groupId)s:",
"Unnamed Room": "Unnamed Room",
"Riot does not have permission to send you notifications - please check your browser settings": "Riot does not have permission to send you notifications - please check your browser settings",
"Riot was not given permission to send notifications - please try again": "Riot was not given permission to send notifications - please try again",
"Unable to enable Notifications": "Unable to enable Notifications",
@ -211,6 +206,11 @@
"%(names)s and %(count)s others are typing|other": "%(names)s and %(count)s others are typing",
"%(names)s and %(count)s others are typing|one": "%(names)s and one other is typing",
"%(names)s and %(lastPerson)s are typing": "%(names)s and %(lastPerson)s are typing",
"Failure to create room": "Failure to create room",
"Server may be unavailable, overloaded, or you hit a bug.": "Server may be unavailable, overloaded, or you hit a bug.",
"Send anyway": "Send anyway",
"Send": "Send",
"Unnamed Room": "Unnamed Room",
"This homeserver has hit its Monthly Active User limit.": "This homeserver has hit its Monthly Active User limit.",
"This homeserver has exceeded one of its resource limits.": "This homeserver has exceeded one of its resource limits.",
"Please <a>contact your service administrator</a> to continue using the service.": "Please <a>contact your service administrator</a> to continue using the service.",
@ -333,31 +333,6 @@
"Off": "Off",
"On": "On",
"Noisy": "Noisy",
"Invalid alias format": "Invalid alias format",
"'%(alias)s' is not a valid format for an alias": "'%(alias)s' is not a valid format for an alias",
"Invalid address format": "Invalid address format",
"'%(alias)s' is not a valid format for an address": "'%(alias)s' is not a valid format for an address",
"not specified": "not specified",
"not set": "not set",
"Remote addresses for this room:": "Remote addresses for this room:",
"Addresses": "Addresses",
"The main address for this room is": "The main address for this room is",
"Local addresses for this room:": "Local addresses for this room:",
"This room has no local addresses": "This room has no local addresses",
"New address (e.g. #foo:%(localDomain)s)": "New address (e.g. #foo:%(localDomain)s)",
"Invalid community ID": "Invalid community ID",
"'%(groupId)s' is not a valid community ID": "'%(groupId)s' is not a valid community ID",
"Flair": "Flair",
"Showing flair for these communities:": "Showing flair for these communities:",
"This room is not showing flair for any communities": "This room is not showing flair for any communities",
"New community ID (e.g. +foo:%(localDomain)s)": "New community ID (e.g. +foo:%(localDomain)s)",
"You have <a>enabled</a> URL previews by default.": "You have <a>enabled</a> URL previews by default.",
"You have <a>disabled</a> URL previews by default.": "You have <a>disabled</a> URL previews by default.",
"URL previews are enabled by default for participants in this room.": "URL previews are enabled by default for participants in this room.",
"URL previews are disabled by default for participants in this room.": "URL previews are disabled by default for participants in this room.",
"In encrypted rooms, like this one, URL previews are disabled by default to ensure that your homeserver (where the previews are generated) cannot gather information about links you see in this room.": "In encrypted rooms, like this one, URL previews are disabled by default to ensure that your homeserver (where the previews are generated) cannot gather information about links you see in this room.",
"URL Previews": "URL Previews",
"When someone puts a URL in their message, a URL preview can be shown to give more information about that link such as the title, description, and an image from the website.": "When someone puts a URL in their message, a URL preview can be shown to give more information about that link such as the title, description, and an image from the website.",
"Cannot add any more widgets": "Cannot add any more widgets",
"The maximum permitted number of widgets have already been added to this room.": "The maximum permitted number of widgets have already been added to this room.",
"Add a widget": "Add a widget",
@ -421,6 +396,7 @@
"Make Moderator": "Make Moderator",
"Admin Tools": "Admin Tools",
"Level:": "Level:",
"Close": "Close",
"and %(count)s others...|other": "and %(count)s others...",
"and %(count)s others...|one": "and one other...",
"Invited": "Invited",
@ -462,11 +438,11 @@
"At this time it is not possible to reply with an emote.": "At this time it is not possible to reply with an emote.",
"Markdown is disabled": "Markdown is disabled",
"Markdown is enabled": "Markdown is enabled",
"Unpin Message": "Unpin Message",
"Jump to message": "Jump to message",
"No pinned messages.": "No pinned messages.",
"Loading...": "Loading...",
"Pinned Messages": "Pinned Messages",
"Unpin Message": "Unpin Message",
"Jump to message": "Jump to message",
"%(duration)ss": "%(duration)ss",
"%(duration)sm": "%(duration)sm",
"%(duration)sh": "%(duration)sh",
@ -514,6 +490,7 @@
"You have no historical rooms": "You have no historical rooms",
"Historical": "Historical",
"System Alerts": "System Alerts",
"Joining room...": "Joining room...",
"Unable to ascertain that the address this invite was sent to matches one associated with your account.": "Unable to ascertain that the address this invite was sent to matches one associated with your account.",
"This invitation was sent to an email address which is not associated with this account:": "This invitation was sent to an email address which is not associated with this account:",
"You may wish to login with a different account, or add this email to this account.": "You may wish to login with a different account, or add this email to this account.",
@ -600,13 +577,37 @@
"All Rooms": "All Rooms",
"Cancel": "Cancel",
"You don't currently have any stickerpacks enabled": "You don't currently have any stickerpacks enabled",
"Add a stickerpack": "Add a stickerpack",
"Add some now": "Add some now",
"Stickerpack": "Stickerpack",
"Hide Stickers": "Hide Stickers",
"Show Stickers": "Show Stickers",
"Scroll to unread messages": "Scroll to unread messages",
"Jump to first unread message.": "Jump to first unread message.",
"Close": "Close",
"Invalid alias format": "Invalid alias format",
"'%(alias)s' is not a valid format for an alias": "'%(alias)s' is not a valid format for an alias",
"Invalid address format": "Invalid address format",
"'%(alias)s' is not a valid format for an address": "'%(alias)s' is not a valid format for an address",
"not specified": "not specified",
"not set": "not set",
"Remote addresses for this room:": "Remote addresses for this room:",
"Addresses": "Addresses",
"The main address for this room is": "The main address for this room is",
"Local addresses for this room:": "Local addresses for this room:",
"This room has no local addresses": "This room has no local addresses",
"New address (e.g. #foo:%(localDomain)s)": "New address (e.g. #foo:%(localDomain)s)",
"Invalid community ID": "Invalid community ID",
"'%(groupId)s' is not a valid community ID": "'%(groupId)s' is not a valid community ID",
"Flair": "Flair",
"Showing flair for these communities:": "Showing flair for these communities:",
"This room is not showing flair for any communities": "This room is not showing flair for any communities",
"New community ID (e.g. +foo:%(localDomain)s)": "New community ID (e.g. +foo:%(localDomain)s)",
"You have <a>enabled</a> URL previews by default.": "You have <a>enabled</a> URL previews by default.",
"You have <a>disabled</a> URL previews by default.": "You have <a>disabled</a> URL previews by default.",
"URL previews are enabled by default for participants in this room.": "URL previews are enabled by default for participants in this room.",
"URL previews are disabled by default for participants in this room.": "URL previews are disabled by default for participants in this room.",
"In encrypted rooms, like this one, URL previews are disabled by default to ensure that your homeserver (where the previews are generated) cannot gather information about links you see in this room.": "In encrypted rooms, like this one, URL previews are disabled by default to ensure that your homeserver (where the previews are generated) cannot gather information about links you see in this room.",
"URL Previews": "URL Previews",
"When someone puts a URL in their message, a URL preview can be shown to give more information about that link such as the title, description, and an image from the website.": "When someone puts a URL in their message, a URL preview can be shown to give more information about that link such as the title, description, and an image from the website.",
"Sunday": "Sunday",
"Monday": "Monday",
"Tuesday": "Tuesday",
@ -645,6 +646,7 @@
"Dismiss": "Dismiss",
"To continue, please enter your password.": "To continue, please enter your password.",
"Password:": "Password:",
"Please review and accept the policies of this homeserver:": "Please review and accept the policies of this homeserver:",
"An email has been sent to %(emailAddress)s": "An email has been sent to %(emailAddress)s",
"Please check your email to continue registration.": "Please check your email to continue registration.",
"Token incorrect": "Token incorrect",
@ -886,6 +888,10 @@
"Ignore request": "Ignore request",
"Loading device info...": "Loading device info...",
"Encryption key request": "Encryption key request",
"You've previously used Riot on %(host)s with lazy loading of members enabled. In this version lazy loading is disabled. As the local cache is not compatible between these two settings, Riot needs to resync your account.": "You've previously used Riot on %(host)s with lazy loading of members enabled. In this version lazy loading is disabled. As the local cache is not compatible between these two settings, Riot needs to resync your account.",
"If the other version of Riot is still open in another tab, please close it as using Riot on the same host with both lazy loading enabled and disabled simultaneously will cause issues.": "If the other version of Riot is still open in another tab, please close it as using Riot on the same host with both lazy loading enabled and disabled simultaneously will cause issues.",
"Incompatible local cache": "Incompatible local cache",
"Clear cache and resync": "Clear cache and resync",
"Riot now uses 3-5x less memory, by only loading information about other users when needed. Please wait whilst we resynchronise with the server!": "Riot now uses 3-5x less memory, by only loading information about other users when needed. Please wait whilst we resynchronise with the server!",
"Updating Riot": "Updating Riot",
"Failed to upgrade room": "Failed to upgrade room",
@ -986,7 +992,7 @@
"You must <a>register</a> to use this functionality": "You must <a>register</a> to use this functionality",
"You must join the room to see its files": "You must join the room to see its files",
"There are no visible files in this room": "There are no visible files in this room",
"<h1>HTML for your community's page</h1>\r\n<p>\r\n Use the long description to introduce new members to the community, or distribute\r\n some important <a href=\"foo\">links</a>\r\n</p>\r\n<p>\r\n You can even use 'img' tags\r\n</p>\r\n": "<h1>HTML for your community's page</h1>\r\n<p>\r\n Use the long description to introduce new members to the community, or distribute\r\n some important <a href=\"foo\">links</a>\r\n</p>\r\n<p>\r\n You can even use 'img' tags\r\n</p>\r\n",
"<h1>HTML for your community's page</h1>\n<p>\n Use the long description to introduce new members to the community, or distribute\n some important <a href=\"foo\">links</a>\n</p>\n<p>\n You can even use 'img' tags\n</p>\n": "<h1>HTML for your community's page</h1>\n<p>\n Use the long description to introduce new members to the community, or distribute\n some important <a href=\"foo\">links</a>\n</p>\n<p>\n You can even use 'img' tags\n</p>\n",
"Add rooms to the community summary": "Add rooms to the community summary",
"Which rooms would you like to add to this summary?": "Which rooms would you like to add to this summary?",
"Add to summary": "Add to summary",
@ -1004,6 +1010,7 @@
"Failed to update community": "Failed to update community",
"Unable to accept invite": "Unable to accept invite",
"Unable to join community": "Unable to join community",
"You are an administrator of this community. You will not be able to rejoin without an invite from another administrator.": "You are an administrator of this community. You will not be able to rejoin without an invite from another administrator.",
"Leave Community": "Leave Community",
"Leave %(groupName)s?": "Leave %(groupName)s?",
"Unable to leave community": "Unable to leave community",
@ -1264,8 +1271,6 @@
"Failed to set direct chat tag": "Failed to set direct chat tag",
"Failed to remove tag %(tagName)s from room": "Failed to remove tag %(tagName)s from room",
"Failed to add tag %(tagName)s to room": "Failed to add tag %(tagName)s to room",
"You've previously used Riot on %(host)s with lazy loading of members enabled. In this version lazy loading is disabled. As the local cache is not compatible between these two settings, Riot needs to resync your account.": "You've previously used Riot on %(host)s with lazy loading of members enabled. In this version lazy loading is disabled. As the local cache is not compatible between these two settings, Riot needs to resync your account.",
"If the other version of Riot is still open in another tab, please close it as using Riot on the same host with both lazy loading enabled and disabled simultaneously will cause issues.": "If the other version of Riot is still open in another tab, please close it as using Riot on the same host with both lazy loading enabled and disabled simultaneously will cause issues.",
"Incompatible local cache": "Incompatible local cache",
"Clear cache and resync": "Clear cache and resync"
"Open Devtools": "Open Devtools",
"Show developer tools": "Show developer tools"
}

View file

@ -567,7 +567,7 @@
"Active call (%(roomName)s)": "Active call (%(roomName)s)",
"Accept": "Accept",
"Add": "Add",
"Admin Tools": "Admin tools",
"Admin Tools": "Admin Tools",
"Alias (optional)": "Alias (optional)",
"Can't connect to homeserver - please check your connectivity, ensure your <a>homeserver's SSL certificate</a> is trusted, and that a browser extension is not blocking requests.": "Can't connect to homeserver - please check your connectivity, ensure your <a>homeserver's SSL certificate</a> is trusted, and that a browser extension is not blocking requests.",
"<a>Click here</a> to join the discussion!": "<a>Click here</a> to join the discussion!",
@ -828,5 +828,15 @@
"Collapse panel": "Collapse panel",
"With your current browser, the look and feel of the application may be completely incorrect, and some or all features may not function. If you want to try it anyway you can continue, but you are on your own in terms of any issues you may encounter!": "With your current browser, the look and feel of the application may be completely incorrect, and some or all features may not function. If you want to try it anyway you can continue, but you are on your own in terms of any issues you may encounter!",
"Checking for an update...": "Checking for an update...",
"There are advanced notifications which are not shown here": "There are advanced notifications which are not shown here"
"There are advanced notifications which are not shown here": "There are advanced notifications which are not shown here",
"The platform you're on": "The platform you're on",
"The version of Riot.im": "The version of Riot.im",
"Whether or not you're logged in (we don't record your user name)": "Whether or not you're logged in (we don't record your user name)",
"Your language of choice": "Your language of choice",
"Which officially provided instance you are using, if any": "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 Richtext mode of the Rich Text Editor",
"Your homeserver's URL": "Your homeserver's URL",
"Your identity server's URL": "Your identity server's URL",
"e.g. %(exampleValue)s": "e.g. %(exampleValue)s",
"Every page you use in the app": "Every page you use in the app"
}

View file

@ -251,7 +251,7 @@
"Encrypted by a verified device": "Ĉifrita de kontrolita aparato",
"Encrypted by an unverified device": "Ĉifrita de nekontrolita aparato",
"Unencrypted message": "Neĉifrita mesaĝo",
"Please select the destination room for this message": "Bonvolu elekti celan ĉambron por ĉi tiu mesaĝo",
"Please select the destination room for this message": "Bonvolu elekti celan babilejon por tiu mesaĝo",
"Blacklisted": "Senpova legi ĉifritajn mesaĝojn",
"Verified": "Kontrolita",
"Unverified": "Nekontrolita",
@ -292,19 +292,19 @@
"and %(count)s others...|other": "kaj %(count)s aliaj…",
"and %(count)s others...|one": "kaj unu alia…",
"Invited": "Invititaj",
"Filter room members": "Filtri ĉambranojn",
"Filter room members": "Filtri babilejanojn",
"%(userName)s (power %(powerLevelNumber)s)": "%(userName)s (potenco je %(powerLevelNumber)s)",
"Attachment": "Kunsendaĵo",
"Upload Files": "Alŝuti dosierojn",
"Are you sure you want to upload the following files?": "Ĉu vi certe volas alŝuti la jenajn dosierojn?",
"Encrypted room": "Ĉifrita ĉambro",
"Unencrypted room": "Neĉifrita ĉambro",
"Encrypted room": "Ĉifrita babilejo",
"Unencrypted room": "Neĉifrita babilejo",
"Hangup": "Fini vokon",
"Voice call": "Voĉvoko",
"Video call": "Vidvoko",
"Upload file": "Alŝuti dosieron",
"Show Text Formatting Toolbar": "Montri tekstaranĝan breton",
"You do not have permission to post to this room": "Mankas al vi permeso afiŝi en la ĉambro",
"You do not have permission to post to this room": "Mankas al vi permeso afiŝi en tiu babilejo",
"Turn Markdown on": "Ŝalti Marksubon",
"Turn Markdown off": "Malŝalti Marksubon",
"Hide Text Formatting Toolbar": "Kaŝi tekstaranĝan breton",
@ -339,7 +339,7 @@
"Offline": "Eksterreta",
"Unknown": "Nekonata",
"Seen by %(userName)s at %(dateTime)s": "Vidita de %(userName)s je %(dateTime)s",
"Unnamed room": "Sennoma ĉambro",
"Unnamed room": "Sennoma babilejo",
"World readable": "Legebla de ĉiuj",
"Guests can join": "Gastoj povas aliĝi",
"No rooms to show": "Neniuj ĉambroj montreblas",
@ -347,11 +347,11 @@
"Save": "Konservi",
"(~%(count)s results)|other": "(~%(count)s rezultoj)",
"(~%(count)s results)|one": "(~%(count)s rezulto)",
"Join Room": "Aliĝi al ĉambro",
"Join Room": "Aliĝi al Babilejo",
"Upload avatar": "Alŝuti profilbildon",
"Remove avatar": "Forigi profilbildon",
"Settings": "Agordoj",
"Forget room": "Forgesi ĉambron",
"Forget room": "Forgesi babilejon",
"Search": "Serĉi",
"Show panel": "Montri panelon",
"Drop here to favourite": "Demetu tien ĉi por ŝati",
@ -360,7 +360,7 @@
"Drop here to demote": "Demeti tien ĉi por malpligravigi",
"Drop here to tag %(section)s": "Demeti tien ĉi por marki %(section)s",
"Press <StartChatButton> to start a chat with someone": "Premu <StartChatButton> por komenci babilon kun iu",
"You're not in any rooms yet! Press <CreateRoomButton> to make a room or <RoomDirectoryButton> to browse the directory": "Vi ankoraŭ estas en neniu ĉambro! Premu <CreateRoomButton> por krei ĉambron aŭ <RoomDirectoryButton> por esplori la ĉambrujon",
"You're not in any rooms yet! Press <CreateRoomButton> to make a room or <RoomDirectoryButton> to browse the directory": "Vi ankoraŭ estas en neniuj Babilejoj! Premu <CreateRoomButton> por krei Babilejon aŭ <RoomDirectoryButton> por esplori la Babilejujon",
"Community Invites": "Komunumaj invitoj",
"Invites": "Invitoj",
"Favourites": "Ŝatataj",
@ -371,19 +371,19 @@
"Unable to ascertain that the address this invite was sent to matches one associated with your account.": "Ne certigeblas, ke la adreso, kien ĉi tiu invito sendiĝis, kongruas kun tiu rilata al via konto.",
"This invitation was sent to an email address which is not associated with this account:": "Ĉi tiu invito sendiĝis al retpoŝtadreso, kiu ne rilatas al ĉi tiu konto:",
"You may wish to login with a different account, or add this email to this account.": "Vi povas saluti per alia konto, aŭ aldoni ĉi tiun retpoŝtadreson al tiu ĉi konto.",
"You have been invited to join this room by %(inviterName)s": "%(inviterName)s vin invitis al ĉi tiu ĉambro",
"You have been invited to join this room by %(inviterName)s": "%(inviterName)s vin invitis al ĉi tiu babilejo",
"Would you like to <acceptText>accept</acceptText> or <declineText>decline</declineText> this invitation?": "Ĉu vi volas <acceptText>akcepti</acceptText> aŭ <declineText>rifuzi</declineText> ĉi tiun inviton?",
"Reason: %(reasonText)s": "Kialo: %(reasonText)s",
"Rejoin": "Realiĝi",
"You have been kicked from %(roomName)s by %(userName)s.": "%(userName)s vin forpelis de %(roomName)s.",
"You have been kicked from this room by %(userName)s.": "%(userName)s vin forpelis de tiu ĉi ĉambro.",
"You have been kicked from this room by %(userName)s.": "%(userName)s vin forpelis de tiu babilejo.",
"You have been banned from %(roomName)s by %(userName)s.": "%(userName)s vi forbaris de %(roomName)s.",
"You have been banned from this room by %(userName)s.": "%(userName)s vin forbaris de tiu ĉi ĉambro.",
"This room": "Ĉi tiu ĉambro",
"You have been banned from this room by %(userName)s.": "%(userName)s vin forbaris de tiu babilejo.",
"This room": "Ĉi tiu babilejo",
"%(roomName)s does not exist.": "%(roomName)s ne ekzistas.",
"%(roomName)s is not accessible at this time.": "%(roomName)s ne estas atingebla nun.",
"You are trying to access %(roomName)s.": "Vi provas atingi %(roomName)s.",
"You are trying to access a room.": "Vi provas atingi ĉambron.",
"You are trying to access a room.": "Vi provas aliri babilejon.",
"<a>Click here</a> to join the discussion!": "<a>Klaku ĉi tie</a> por aliĝi al la diskuto!",
"This is a preview of this room. Room interactions have been disabled": "Tio ĉi estas antaŭrigardo al la ĉambro. Ĉambraj interagoj estas malŝaltitaj",
"To change the room's avatar, you must be a": "Por ŝanĝi la ĉambran profilbildon, vi devas esti",
@ -419,7 +419,7 @@
"To link to a room it must have <a>an address</a>.": "Por esti ligebla, ĉambro devas havi <a>adreson</a>.",
"Guests cannot join this room even if explicitly invited.": "Gastoj ne povas aliĝi ĉi tiun ĉambron eĉ kun malimplica invito.",
"Click here to fix": "Klaku ĉi tie por riparo",
"Who can access this room?": "Kiu povas atingi ĉi tiun ĉambron?",
"Who can access this room?": "Kiu povas aliri ĉi tiun ĉambron?",
"Only people who have been invited": "Nur invititaj uzantoj",
"Anyone who knows the room's link, apart from guests": "Iu ajn kun la ligilo, krom gastoj",
"Anyone who knows the room's link, including guests": "Iu ajn kun la ligilo, inkluzive gastojn",
@ -453,17 +453,17 @@
"not set": "neagordita",
"Remote addresses for this room:": "Foraj adresoj de ĉi tiu ĉambro:",
"Addresses": "Adresoj",
"The main address for this room is": "La ĉefadreso por ĉi tiu ĉambro estas",
"Local addresses for this room:": "Lokaj adresoj por ĉi tiu ĉambro:",
"This room has no local addresses": "Ĉi tiu ĉambro ne havas lokajn adresojn",
"The main address for this room is": "La ĉefadreso por ĉi tiu babilejo estas",
"Local addresses for this room:": "Lokaj adresoj por ĉi tiu babilejo:",
"This room has no local addresses": "Ĉi tiu babilejo ne havas lokajn adresojn",
"New address (e.g. #foo:%(localDomain)s)": "Nova adreso (ekz-e #io:%(localDomain)s)",
"Invalid community ID": "Malvalida komunuma identigaĵo",
"'%(groupId)s' is not a valid community ID": "%(groupId)s ne estas valida komunuma identigaĵo",
"New community ID (e.g. +foo:%(localDomain)s)": "Nova komunuma identigaĵo (ekz-e +io:%(localDomain)s)",
"You have <a>enabled</a> URL previews by default.": "Vi <a>ŝaltis</a> implicitajn antaŭrigardojn al retpaĝoj.",
"You have <a>disabled</a> URL previews by default.": "Vi <a>malŝaltis</a> implicitajn antaŭrigardojn al retpaĝoj.",
"URL previews are enabled by default for participants in this room.": "Antaŭrigardoj al retpaĝoj estas implicite ŝaltitaj por ĉambranoj ĉi tie.",
"URL previews are disabled by default for participants in this room.": "Antaŭrigardoj al retpaĝoj estas implicite malŝaltitaj por ĉambranoj ĉi tie.",
"URL previews are enabled by default for participants in this room.": "Antaŭrigardoj de URL-oj estas implicite ŝaltitaj por anoj de tiu ĉi babilejo.",
"URL previews are disabled by default for participants in this room.": "Antaŭrigardoj de URL-oj estas implicite malŝaltitaj por anoj de tiu ĉi babilejo.",
"URL Previews": "Antaŭrigardoj al retpaĝoj",
"Error decrypting audio": "Eraro malĉifrante sonon",
"Error decrypting attachment": "Eraro malĉifrante kunsendaĵon",

View file

@ -1285,5 +1285,12 @@
"<h1>HTML for your community's page</h1>\r\n<p>\r\n Use the long description to introduce new members to the community, or distribute\r\n some important <a href=\"foo\">links</a>\r\n</p>\r\n<p>\r\n You can even use 'img' tags\r\n</p>\r\n": "<h1>Zure komunitatearen orriaren HTMLa</h1>\n<p>\n Erabili deskripzio luzea kide berriek komunitatea ezagutu dezaten, edo eman ezagutzera <a href=\"foo\">esteka</a> garrantzitsuak\n</p>\n<p>\n 'img' etiketak erabili ditzakezu ere\n</p>\n",
"Submit Debug Logs": "Bidali arazketa egunkariak",
"An email address is required to register on this homeserver.": "e-mail helbide bat behar da hasiera-zerbitzari honetan izena emateko.",
"A phone number is required to register on this homeserver.": "telefono zenbaki bat behar da hasiera-zerbitzari honetan izena emateko."
"A phone number is required to register on this homeserver.": "telefono zenbaki bat behar da hasiera-zerbitzari honetan izena emateko.",
"Please accept all of the policies": "Onartu mesedez politika guztiak",
"Please review and accept the policies of this homeserver:": "Irakurri eta onartu hasiera zerbitzari honen politikak:",
"You've previously used Riot on %(host)s with lazy loading of members enabled. In this version lazy loading is disabled. As the local cache is not compatible between these two settings, Riot needs to resync your account.": "Aurretik Riot erabili duzu %(host)s zerbitzarian kideen karga alferra gaituta zenuela. Bertsio honetan karga alferra desgaituta dago. Katxe lokala bi ezarpen hauen artean bateragarria ez denez, Riotek zure kontua berriro sinkronizatu behar du.",
"If the other version of Riot is still open in another tab, please close it as using Riot on the same host with both lazy loading enabled and disabled simultaneously will cause issues.": "Rioten beste bertsioa oraindik beste fitxat batean irekita badago, itxi ezazu zerbitzari bera aldi berean karga alferra gaituta eta desgaituta erabiltzeak arazoak sor ditzakeelako.",
"Incompatible local cache": "Katxe lokal bateraezina",
"Clear cache and resync": "Garbitu katxea eta sinkronizatu berriro",
"Add some now": "Gehitu batzuk orain"
}

View file

@ -1291,5 +1291,8 @@
"You've previously used Riot on %(host)s with lazy loading of members enabled. In this version lazy loading is disabled. As the local cache is not compatible between these two settings, Riot needs to resync your account.": "Vous avez utilisé auparavant Riot sur %(host)s avec le chargement différé activé. Dans cette version le chargement différé est désactivé. Comme le cache local n'est pas compatible entre ces deux réglages, Riot doit resynchroniser votre compte.",
"If the other version of Riot is still open in another tab, please close it as using Riot on the same host with both lazy loading enabled and disabled simultaneously will cause issues.": "Si l'autre version de Riot est encore ouverte dans un autre onglet, merci de le fermer car l'utilisation de Riot sur le même hôte avec le chargement différé activé et désactivé à la fois causera des problèmes.",
"Incompatible local cache": "Cache local incompatible",
"Clear cache and resync": "Vider le cache et resynchroniser"
"Clear cache and resync": "Vider le cache et resynchroniser",
"Please accept all of the policies": "Veuillez accepter toutes les politiques",
"Please review and accept the policies of this homeserver:": "Veuillez lire et accepter les politiques de ce serveur d'accueil :",
"Add some now": "En ajouter maintenant"
}

View file

@ -1291,5 +1291,9 @@
"You've previously used Riot on %(host)s with lazy loading of members enabled. In this version lazy loading is disabled. As the local cache is not compatible between these two settings, Riot needs to resync your account.": "Előzőleg a szoba tagság késleltetett betöltésének engedélyével itt használtad a Riotot: %(host)s. Ebben a verzióban viszont a késleltetett betöltés nem engedélyezett. Mivel a két gyorsítótár nem kompatibilis egymással így Riotnak újra kell szinkronizálnia a fiókot.",
"If the other version of Riot is still open in another tab, please close it as using Riot on the same host with both lazy loading enabled and disabled simultaneously will cause issues.": "Ha a másik Riot verzió fut még egy másik fülön, kérlek zárd be, mivel ha ugyanott használod a Riotot bekapcsolt késleltetett betöltéssel és kikapcsolva is akkor problémák adódhatnak.",
"Incompatible local cache": "A helyi gyorsítótár nem kompatibilis ezzel a verzióval",
"Clear cache and resync": "Gyorsítótár törlése és újraszinkronizálás"
"Clear cache and resync": "Gyorsítótár törlése és újraszinkronizálás",
"Please accept all of the policies": "Kérlek fogadd el a felhasználói feltételeket",
"Please review and accept the policies of this homeserver:": "Kérlek nézd át és fogadd el a Matrix szerver felhasználói feltételeit:",
"Add some now": "Adj hozzá párat",
"Joining room...": "Belépés a szobába.."
}

View file

@ -1288,5 +1288,7 @@
"You've previously used Riot on %(host)s with lazy loading of members enabled. In this version lazy loading is disabled. As the local cache is not compatible between these two settings, Riot needs to resync your account.": "Hai usato Riot precedentemente su %(host)s con il caricamento lento dei membri attivato. In questa versione il caricamento lento è disattivato. Dato che la cache locale non è compatibile tra queste due impostazioni, Riot deve risincronizzare il tuo account.",
"If the other version of Riot is still open in another tab, please close it as using Riot on the same host with both lazy loading enabled and disabled simultaneously will cause issues.": "Se l'altra versione di Riot è ancora aperta in un'altra scheda, chiudila perchè usare Riot nello stesso host con il caricamento lento sia attivato che disattivato può causare errori.",
"Incompatible local cache": "Cache locale non compatibile",
"Clear cache and resync": "Svuota cache e risincronizza"
"Clear cache and resync": "Svuota cache e risincronizza",
"Please accept all of the policies": "Si prega di accettare tutte le condizioni",
"Please review and accept the policies of this homeserver:": "Consulta ed accetta le condizioni di questo homeserver:"
}

View file

@ -143,11 +143,11 @@
"Can't connect to homeserver via HTTP when an HTTPS URL is in your browser bar. Either use HTTPS or <a>enable unsafe scripts</a>.": "Nie można nawiązać połączenia z serwerem przy użyciu HTTP podczas korzystania z HTTPS dla bieżącej strony. Użyj HTTPS lub <a>włącz niebezpieczne skrypty</a>.",
"Can't load user settings": "Nie można załadować ustawień użytkownika",
"Cannot add any more widgets": "Nie można dodać już więcej widżetów",
"%(senderName)s changed their profile picture.": "%(senderName)s zmienił swoje zdjęcie profilowe.",
"%(senderName)s changed the power level of %(powerLevelDiffText)s.": "%(senderName)s zmienił poziom mocy %(powerLevelDiffText)s.",
"%(senderDisplayName)s changed the room name to %(roomName)s.": "%(senderDisplayName)s zmienił nazwę pokoju na %(roomName)s.",
"%(senderDisplayName)s removed the room name.": "%(senderDisplayName)s usunął nazwę pokoju.",
"%(senderDisplayName)s changed the topic to \"%(topic)s\".": "%(senderDisplayName)s zmienił temat na \"%(topic)s\".",
"%(senderName)s changed their profile picture.": "%(senderName)s zmienił(a) swoje zdjęcie profilowe.",
"%(senderName)s changed the power level of %(powerLevelDiffText)s.": "%(senderName)s zmienił(a) poziom mocy %(powerLevelDiffText)s.",
"%(senderDisplayName)s changed the room name to %(roomName)s.": "%(senderDisplayName)s zmienił(a) nazwę pokoju na %(roomName)s.",
"%(senderDisplayName)s removed the room name.": "%(senderDisplayName)s usunął(-ęła) nazwę pokoju.",
"%(senderDisplayName)s changed the topic to \"%(topic)s\".": "%(senderDisplayName)s zmienił(a) temat na \"%(topic)s\".",
"Changes to who can read history will only apply to future messages in this room": "Zmiany w dostępie do historii będą dotyczyć tylko przyszłych wiadomości w tym pokoju",
"Changes your display nickname": "Zmień swój pseudonim",
"Changes colour scheme of current room": "Zmień schemat kolorystyczny bieżącego pokoju",
@ -370,8 +370,8 @@
"Rejoin": "Dołącz ponownie",
"Remote addresses for this room:": "Adresy zdalne dla tego pokoju:",
"Remove Contact Information?": "Usunąć dane kontaktowe?",
"%(senderName)s removed their display name (%(oldDisplayName)s).": "%(senderName)s usunął swoją nazwę ekranową (%(oldDisplayName)s).",
"%(senderName)s removed their profile picture.": "%(senderName)s usunął swoje zdjęcie profilowe.",
"%(senderName)s removed their display name (%(oldDisplayName)s).": "%(senderName)s usunął(-ęła) swoją wyświetlaną nazwę (%(oldDisplayName)s).",
"%(senderName)s removed their profile picture.": "%(senderName)s usunął(-ęła) swoje zdjęcie profilowe.",
"Remove %(threePid)s?": "Usunąć %(threePid)s?",
"%(senderName)s requested a VoIP conference.": "%(senderName)s zażądał grupowego połączenia głosowego VoIP.",
"Results from DuckDuckGo": "Wyniki z DuckDuckGo",
@ -629,9 +629,9 @@
" (unsupported)": " (niewspierany)",
"Idle": "Bezczynny",
"Check for update": "Sprawdź aktualizacje",
"%(senderDisplayName)s changed the room avatar to <img/>": "%(senderDisplayName)s zmienił awatar pokoju na <img/>",
"%(senderDisplayName)s removed the room avatar.": "%(senderDisplayName)s usunął awatar pokoju.",
"%(senderDisplayName)s changed the avatar for %(roomName)s": "%(senderDisplayName)s zmienił awatar %(roomName)s",
"%(senderDisplayName)s changed the room avatar to <img/>": "%(senderDisplayName)s zmienił(a) awatar pokoju na <img/>",
"%(senderDisplayName)s removed the room avatar.": "%(senderDisplayName)s usunął(-ęła) awatar pokoju.",
"%(senderDisplayName)s changed the avatar for %(roomName)s": "%(senderDisplayName)s zmienił(a) awatar %(roomName)s",
"This will be your account name on the <span></span> homeserver, or you can pick a <a>different server</a>.": "To będzie twoja nazwa konta na <span></span> serwerze domowym; możesz też wybrać <a>inny serwer</a>.",
"If you already have a Matrix account you can <a>log in</a> instead.": "Jeśli już posiadasz konto Matrix możesz się <a>zalogować</a>.",
"Not a valid Riot keyfile": "Niepoprawny plik klucza Riot",
@ -640,7 +640,7 @@
"Do you want to set an email address?": "Czy chcesz ustawić adres e-mail?",
"To return to your account in future you need to set a password": "By móc powrócić do swojego konta w przyszłości musisz ustawić hasło",
"Share without verifying": "Udostępnij bez weryfikacji",
"You added a new device '%(displayName)s', which is requesting encryption keys.": "Dodałeś nowe urządzenie '%(displayName)s', które żąda kluczy szyfrujących.",
"You added a new device '%(displayName)s', which is requesting encryption keys.": "Dodałeś(-aś) nowe urządzenie '%(displayName)s', które żąda kluczy szyfrujących.",
"Your unverified device '%(displayName)s' is requesting encryption keys.": "Twoje niezweryfikowane urządzenie '%(displayName)s' żąda kluczy szyfrujących.",
"Encryption key request": "Żądanie klucza szyfrującego",
"Autocomplete Delay (ms):": "Opóźnienie autouzupełniania (ms):",
@ -700,8 +700,8 @@
"Ignored user": "Użytkownik ignorowany",
"You are now ignoring %(userId)s": "Ignorujesz teraz %(userId)s",
"You are no longer ignoring %(userId)s": "Nie ignorujesz już %(userId)s",
"%(oldDisplayName)s changed their display name to %(displayName)s.": "%(oldDisplayName)s zmienił swoją wyświetlaną nazwę na %(displayName)s.",
"%(senderName)s changed the pinned messages for the room.": "%(senderName)s zmienił przypiętą wiadomość dla tego pokoju.",
"%(oldDisplayName)s changed their display name to %(displayName)s.": "%(oldDisplayName)s zmienił(a) swoją wyświetlaną nazwę na %(displayName)s.",
"%(senderName)s changed the pinned messages for the room.": "%(senderName)s zmienił(a) przypiętą wiadomość dla tego pokoju.",
"Message Pinning": "Przypinanie wiadomości",
"%(names)s and %(count)s others are typing|other": "%(names)s oraz %(count)s innych piszą",
"%(names)s and %(count)s others are typing|one": "%(names)s oraz jedna inna osoba piszą",
@ -1146,5 +1146,39 @@
"was invited %(count)s times|one": "został zaproszony",
"was banned %(count)s times|one": "został zablokowany",
"was kicked %(count)s times|one": "został wyrzucony",
"Whether or not you're using the Richtext mode of the Rich Text Editor": ""
"Whether or not you're using the Richtext mode of the Rich Text Editor": "Niezależnie od tego, czy używasz trybu Richtext edytora tekstu w formacie RTF",
"Call in Progress": "Łączenie w toku",
"Permission Required": "Wymagane Pozwolenie",
"Registration Required": "Wymagana Rejestracja",
"You need to register to do this. Would you like to register now?": "Musisz się zarejestrować, aby to zrobić. Czy chcesz się teraz zarejestrować?",
"underlined": "podkreślenie",
"deleted": "przekreślenie",
"numbered-list": "lista numerowana",
"bulleted-list": "wykropkowana lista",
"block-quote": "blok cytowany",
"A conference call could not be started because the intgrations server is not available": "Połączenie grupowe nie może zostać rozpoczęte, ponieważ serwer jest niedostępny",
"A call is currently being placed!": "W tej chwili trwa rozmowa!",
"A call is already in progress!": "W tej chwili trwa połączenie!",
"You do not have permission to start a conference call in this room": "Nie posiadasz permisji do rozpoczęcia rozmowy grupowej w tym pokoju",
"Unignored user": "Nieignorowany użytkownik",
"Forces the current outbound group session in an encrypted room to be discarded": "Wymusza odrzucenie bieżącej sesji grupy wychodzącej w zaszyfrowanym pokoju",
"%(senderName)s added %(count)s %(addedAddresses)s as addresses for this room.|other": "%(senderName)s dodał(a) %(addedAddresses)s jako adres tego pokoju.",
"%(senderName)s added %(count)s %(addedAddresses)s as addresses for this room.|one": "%(senderName)s dodał(a) %(addedAddresses)s jako adres tego pokoju.",
"%(senderName)s removed %(count)s %(removedAddresses)s as addresses for this room.|other": "%(senderName)s usunął(-ęła) %(removedAddresses)s jako adres tego pokoju.",
"%(senderName)s removed %(count)s %(removedAddresses)s as addresses for this room.|one": "%(senderName)s usunął(-ęła) %(removedAddresses)s jako adres tego pokoju.",
"%(senderName)s added %(addedAddresses)s and removed %(removedAddresses)s as addresses for this room.": "%(senderName)s dodał %(addedAddresses)s i %(removedAddresses)s usunął adresy z tego pokoju.",
"%(senderName)s set the main address for this room to %(address)s.": "%(senderName)s ustawił(a) główny adres dla tego pokoju na %(address)s.",
"%(senderName)s removed the main address for this room.": "%(senderName)s usunął(-ęła) główny adres tego pokoju.",
"This homeserver has hit its Monthly Active User limit.": "Ten serwer osiągnął miesięczny limit aktywnego użytkownika.",
"This homeserver has exceeded one of its resource limits.": "Ten serwer przekroczył jeden z limitów.",
"Please <a>contact your service administrator</a> to continue using the service.": "Proszę, <a>skontaktuj się z administratorem</a> aby korzystać dalej z funkcji.",
"Unable to connect to Homeserver. Retrying...": "Nie można się połączyć z serwerem. Ponawanianie...",
"Sorry, your homeserver is too old to participate in this room.": "Przepraszamy, twój serwer jest zbyt stary by wziąć udział w tym pokoju.",
"Please contact your homeserver administrator.": "Proszę o kontakt z administratorem serwera.",
"Increase performance by only loading room members on first view": "Zwiększ wydajność, ładując tylko członków pokoju w pierwszym widoku",
"Enable widget screenshots on supported widgets": "Włącz widżety zrzutów ekranów na obsługiwanych widżetach",
"Show empty room list headings": "Pokaż nagłówki z pustym pokojem",
"In encrypted rooms, like this one, URL previews are disabled by default to ensure that your homeserver (where the previews are generated) cannot gather information about links you see in this room.": "W zaszyfrowanych pokojach, takich jak ten, podgląd adresów URL jest domyślnie wyłączony, aby upewnić się, że serwer (w którym generowane są podglądy) nie może zbierać informacji o linkach widocznych w tym pokoju.",
"When someone puts a URL in their message, a URL preview can be shown to give more information about that link such as the title, description, and an image from the website.": "Gdy ktoś umieści URL w wiadomości, można wyświetlić podgląd adresu URL, aby podać więcej informacji o tym łączu, takich jak tytuł, opis i obraz ze strony internetowej.",
"This event could not be displayed": "Ten event nie może zostać wyświetlony"
}

View file

@ -1234,5 +1234,7 @@
"Put a link back to the old room at the start of the new room so people can see old messages": "Разместим ссылку на старую комнату, чтобы люди могли видеть старые сообщения",
"Please <a>contact your service administrator</a> to continue using this service.": "Пожалуйста, <a>обратитесь к вашему администратору</a>, чтобы продолжить использовать этот сервис.",
"Increase performance by only loading room members on first view": "Увеличьте производительность, загрузив только список участников комнаты",
"Lazy loading members not supported": "Задержка загрузки элементов не поддерживается"
"Lazy loading members not supported": "Задержка загрузки элементов не поддерживается",
"Registration Required": "Требуется регистрация",
"You need to register to do this. Would you like to register now?": "Необходимо зарегистрироваться. Хотите зарегистрироваться?"
}

View file

@ -17,7 +17,7 @@
"Disinvite": "取消邀请",
"Display name": "昵称",
"Displays action": "显示操作",
"Don't send typing notifications": "不要发送我的打字状态",
"Don't send typing notifications": "不要发送“正在输入”提示",
"Download %(text)s": "下载 %(text)s",
"Email": "电子邮箱",
"Email address": "邮箱地址",
@ -488,7 +488,7 @@
"This room is not recognised.": "无法识别此聊天室。",
"To get started, please pick a username!": "请点击用户名!",
"Unable to add email address": "无法添加邮箱地址",
"Automatically replace plain text Emoji": "文字、表情自动转换",
"Automatically replace plain text Emoji": "将符号表情转换为 Emoji",
"To reset your password, enter the email address linked to your account": "要重置你的密码,请输入关联你的帐号的邮箱地址",
"Unable to verify email address.": "无法验证邮箱地址。",
"Unknown room %(roomId)s": "未知聊天室 %(roomId)s",
@ -1210,7 +1210,7 @@
"These rooms are displayed to community members on the community page. Community members can join the rooms by clicking on them.": "这些聊天室对社区成员可见。社区成员可通过点击来加入它们。",
"Your community hasn't got a Long Description, a HTML page to show to community members.<br />Click here to open settings and give it one!": "您的社区还没有详细介绍,一个展示给社区成员的 HTML 页面。<br>点击这里即可打开设置添加详细介绍!",
"Failed to load %(groupId)s": "%(groupId)s 加载失败",
"This room is not public. You will not be able to rejoin without an invite.": "此聊天室不是公开的。没有邀请的话,您将无法重新加入。",
"This room is not public. You will not be able to rejoin without an invite.": "此聊天室不是公开聊天室。如果没有成员邀请,您将无法重新加入。",
"Can't leave Server Notices room": "无法退出服务器公告聊天室",
"This room is used for important messages from the Homeserver, so you cannot leave it.": "此聊天室是用于发布来自主服务器的重要讯息的,所以您不能退出它。",
"Terms and Conditions": "条款与要求",

View file

@ -165,7 +165,7 @@
"Success": "成功",
"The default role for new room members is": "此聊天室新成員的預設角色是",
"The main address for this room is": "此聊天室的主要地址是",
"This email address is already in use": "此電子郵件地址已經被使用",
"This email address is already in use": "這個電子郵件位址已被使用",
"This email address was not found": "未找到此電子郵件地址",
"The email address linked to your account must be entered.": "必須輸入和你帳號關聯的電子郵件地址。",
"The file '%(fileName)s' exceeds this home server's size limit for uploads": "文件 '%(fileName)s' 超過了這個家伺服器的上傳大小限制",
@ -1289,5 +1289,9 @@
"You've previously used Riot on %(host)s with lazy loading of members enabled. In this version lazy loading is disabled. As the local cache is not compatible between these two settings, Riot needs to resync your account.": "您之前曾在 %(host)s 上使用 Riot 並啟用成員列表的延遲載入。在此版本中延遲載入已停用。由於本機快取在這兩個設定間不相容Riot 必須重新同步您的帳號。",
"If the other version of Riot is still open in another tab, please close it as using Riot on the same host with both lazy loading enabled and disabled simultaneously will cause issues.": "若其他分頁仍有不同版本的 Riot請將其關閉因為在同一個主機上同時啟用和停用延遲載入將會發生問題。",
"Incompatible local cache": "不相容的本機快取",
"Clear cache and resync": "清除快取並重新同步"
"Clear cache and resync": "清除快取並重新同步",
"Please accept all of the policies": "請接受所有政策",
"Please review and accept the policies of this homeserver:": "請審閱並接受此家伺服器的政策:",
"Add some now": "現在就新增一些",
"Joining room...": "正在加入聊天室……"
}

View file

@ -241,20 +241,27 @@ class IndexedDBLogStore {
// Returns: a string representing the concatenated logs for this ID.
function fetchLogs(id) {
const o = db.transaction("logs", "readonly").objectStore("logs");
return selectQuery(o.index("id"), IDBKeyRange.only(id),
(cursor) => {
return {
lines: cursor.value.lines,
index: cursor.value.index,
const objectStore = db.transaction("logs", "readonly").objectStore("logs");
return new Promise((resolve, reject) => {
const query = objectStore.index("id").openCursor(IDBKeyRange.only(id), 'next');
let lines = '';
query.onerror = (event) => {
reject(new Error("Query failed: " + event.target.errorCode));
};
query.onsuccess = (event) => {
const cursor = event.target.result;
if (!cursor) {
resolve(lines);
return; // end of results
}
lines += cursor.value.lines;
if (lines.length >= MAX_LOG_SIZE) {
resolve(lines);
} else {
cursor.continue();
}
};
}).then((linesArray) => {
// We have been storing logs periodically, so string them all
// together *in order of index* now
linesArray.sort((a, b) => {
return a.index - b.index;
});
return linesArray.map((l) => l.lines).join("");
});
}
@ -322,9 +329,6 @@ class IndexedDBLogStore {
if (i > 0 && size + lines.length > MAX_LOG_SIZE) {
// the remaining log IDs should be removed. If we go out of
// bounds this is just []
//
// XXX: there's nothing stopping the current session exceeding
// MAX_LOG_SIZE. We ought to think about culling it.
removeLogIds = allLogIds.slice(i + 1);
break;
}

View file

@ -300,4 +300,9 @@ export const SETTINGS = {
displayName: _td('Show empty room list headings'),
default: true,
},
"showDeveloperTools": {
supportedLevels: LEVELS_ACCOUNT_SETTINGS,
displayName: _td('Show developer tools'),
default: false,
},
};

View file

@ -0,0 +1,72 @@
/*
Copyright 2018 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 expect from 'expect';
import {phasedRollOutExpiredForUser} from '../src/PhasedRollOut';
const OFFSET = 6000000;
// phasedRollOutExpiredForUser enables users in bucks of 1 minute
const MS_IN_MINUTE = 60 * 1000;
describe('PhasedRollOut', function() {
it('should return true if phased rollout is not configured', function() {
expect(phasedRollOutExpiredForUser("@user:hs", "feature_test", 0, null)).toBeTruthy();
});
it('should return true if phased rollout feature is not configured', function() {
expect(phasedRollOutExpiredForUser("@user:hs", "feature_test", 0, {
"feature_other": {offset: 0, period: 0},
})).toBeTruthy();
});
it('should return false if phased rollout for feature is misconfigured', function() {
expect(phasedRollOutExpiredForUser("@user:hs", "feature_test", 0, {
"feature_test": {},
})).toBeFalsy();
});
it("should return false if phased rollout hasn't started yet", function() {
expect(phasedRollOutExpiredForUser("@user:hs", "feature_test", 5000000, {
"feature_test": {offset: OFFSET, period: MS_IN_MINUTE},
})).toBeFalsy();
});
it("should start to return true in bucket 2/10 for '@user:hs'", function() {
expect(phasedRollOutExpiredForUser("@user:hs", "feature_test",
OFFSET + (MS_IN_MINUTE * 2) - 1, {
"feature_test": {offset: OFFSET, period: MS_IN_MINUTE * 10},
})).toBeFalsy();
expect(phasedRollOutExpiredForUser("@user:hs", "feature_test",
OFFSET + (MS_IN_MINUTE * 2), {
"feature_test": {offset: OFFSET, period: MS_IN_MINUTE * 10},
})).toBeTruthy();
});
it("should start to return true in bucket 4/10 for 'alice@other-hs'", function() {
expect(phasedRollOutExpiredForUser("alice@other-hs", "feature_test",
OFFSET + (MS_IN_MINUTE * 4) - 1, {
"feature_test": {offset: OFFSET, period: MS_IN_MINUTE * 10},
})).toBeFalsy();
expect(phasedRollOutExpiredForUser("alice@other-hs", "feature_test",
OFFSET + (MS_IN_MINUTE * 4), {
"feature_test": {offset: OFFSET, period: MS_IN_MINUTE * 10},
})).toBeTruthy();
});
it("should return true after complete rollout period'", function() {
expect(phasedRollOutExpiredForUser("user:hs", "feature_test",
OFFSET + (MS_IN_MINUTE * 20), {
"feature_test": {offset: OFFSET, period: MS_IN_MINUTE * 10},
})).toBeTruthy();
});
});

View file

@ -0,0 +1,175 @@
/*
Copyright 2018 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 expect from 'expect';
import QueryMatcher from '../../src/autocomplete/QueryMatcher';
const OBJECTS = [
{ name: "Mel B", nick: "Scary" },
{ name: "Mel C", nick: "Sporty" },
{ name: "Emma", nick: "Baby" },
{ name: "Geri", nick: "Ginger" },
{ name: "Victoria", nick: "Posh" },
];
const NONWORDOBJECTS = [
{ name: "B.O.B" },
{ name: "bob" },
];
describe('QueryMatcher', function() {
it('Returns results by key', function() {
const qm = new QueryMatcher(OBJECTS, {keys: ["name"]});
const results = qm.match('Geri');
expect(results.length).toBe(1);
expect(results[0].name).toBe('Geri');
});
it('Returns results by prefix', function() {
const qm = new QueryMatcher(OBJECTS, {keys: ["name"]});
const results = qm.match('Ge');
expect(results.length).toBe(1);
expect(results[0].name).toBe('Geri');
});
it('Matches case-insensitive', function() {
const qm = new QueryMatcher(OBJECTS, {keys: ["name"]});
const results = qm.match('geri');
expect(results.length).toBe(1);
expect(results[0].name).toBe('Geri');
});
it('Matches ignoring accents', function() {
const qm = new QueryMatcher([{name: "Gëri", foo: 46}], {keys: ["name"]});
const results = qm.match('geri');
expect(results.length).toBe(1);
expect(results[0].foo).toBe(46);
});
it('Returns multiple results in order of search string appearance', function() {
const qm = new QueryMatcher(OBJECTS, {keys: ["name", "nick"]});
const results = qm.match('or');
expect(results.length).toBe(2);
expect(results[0].name).toBe('Mel C');
expect(results[1].name).toBe('Victoria');
qm.setObjects(OBJECTS.slice().reverse());
const reverseResults = qm.match('or');
// should still be in the same order: search string position
// takes precedence over input order
expect(reverseResults.length).toBe(2);
expect(reverseResults[0].name).toBe('Mel C');
expect(reverseResults[1].name).toBe('Victoria');
});
it('Returns results with search string in same place in insertion order', function() {
const qm = new QueryMatcher(OBJECTS, {keys: ["name"]});
const results = qm.match('Mel');
expect(results.length).toBe(2);
expect(results[0].name).toBe('Mel B');
expect(results[1].name).toBe('Mel C');
qm.setObjects(OBJECTS.slice().reverse());
const reverseResults = qm.match('Mel');
expect(reverseResults.length).toBe(2);
expect(reverseResults[0].name).toBe('Mel C');
expect(reverseResults[1].name).toBe('Mel B');
});
it('Returns numeric results in correct order (input pos)', function() {
// regression test for depending on object iteration order
const qm = new QueryMatcher([
{name: "123456badger"},
{name: "123456"},
], {keys: ["name"]});
const results = qm.match('123456');
expect(results.length).toBe(2);
expect(results[0].name).toBe('123456badger');
expect(results[1].name).toBe('123456');
});
it('Returns numeric results in correct order (query pos)', function() {
const qm = new QueryMatcher([
{name: "999999123456"},
{name: "123456badger"},
], {keys: ["name"]});
const results = qm.match('123456');
expect(results.length).toBe(2);
expect(results[0].name).toBe('123456badger');
expect(results[1].name).toBe('999999123456');
});
it('Returns results by function', function() {
const qm = new QueryMatcher(OBJECTS, {
keys: ["name"],
funcs: [x => x.name.replace('Mel', 'Emma')],
});
const results = qm.match('Emma');
expect(results.length).toBe(3);
expect(results[0].name).toBe('Mel B');
expect(results[1].name).toBe('Mel C');
expect(results[2].name).toBe('Emma');
});
it('Matches words only by default', function() {
const qm = new QueryMatcher(NONWORDOBJECTS, { keys: ["name"] });
const results = qm.match('bob');
expect(results.length).toBe(2);
expect(results[0].name).toBe('B.O.B');
expect(results[1].name).toBe('bob');
});
it('Matches all chars with words-only off', function() {
const qm = new QueryMatcher(NONWORDOBJECTS, {
keys: ["name"],
shouldMatchWordsOnly: false,
});
const results = qm.match('bob');
expect(results.length).toBe(1);
expect(results[0].name).toBe('bob');
});
it('Matches only by prefix with shouldMatchPrefix on', function() {
const qm = new QueryMatcher([
{name: "Victoria"},
{name: "Tori"},
], {
keys: ["name"],
shouldMatchPrefix: true,
});
const results = qm.match('tori');
expect(results.length).toBe(1);
expect(results[0].name).toBe('Tori');
});
});