Conflicts:
	src/i18n/strings/cs.json
	src/i18n/strings/de_DE.json
	src/i18n/strings/fi.json
	src/i18n/strings/fr.json
	src/i18n/strings/it.json
	src/i18n/strings/nl.json
	src/i18n/strings/ru.json
	src/i18n/strings/th.json
pull/21833/head
Weblate 2017-10-12 16:20:59 +00:00
commit 11b4ffe302
235 changed files with 6666 additions and 4357 deletions

View File

@ -1,10 +1,7 @@
# autogenerated file: run scripts/generate-eslint-error-ignore-file to update.
src/async-components/views/dialogs/EncryptedEventDialog.js
src/autocomplete/AutocompleteProvider.js
src/autocomplete/Autocompleter.js
src/autocomplete/Components.js
src/autocomplete/DuckDuckGoProvider.js
src/autocomplete/EmojiProvider.js
src/autocomplete/UserProvider.js
src/CallHandler.js
@ -12,11 +9,9 @@ src/component-index.js
src/components/structures/ContextualMenu.js
src/components/structures/CreateRoom.js
src/components/structures/FilePanel.js
src/components/structures/InteractiveAuth.js
src/components/structures/LoggedInView.js
src/components/structures/login/ForgotPassword.js
src/components/structures/login/Login.js
src/components/structures/login/PostRegistration.js
src/components/structures/login/Registration.js
src/components/structures/MessagePanel.js
src/components/structures/NotificationPanel.js
@ -27,52 +22,32 @@ src/components/structures/TimelinePanel.js
src/components/structures/UploadBar.js
src/components/views/avatars/BaseAvatar.js
src/components/views/avatars/MemberAvatar.js
src/components/views/avatars/RoomAvatar.js
src/components/views/create_room/CreateRoomButton.js
src/components/views/create_room/Presets.js
src/components/views/create_room/RoomAlias.js
src/components/views/dialogs/ChatCreateOrReuseDialog.js
src/components/views/dialogs/DeactivateAccountDialog.js
src/components/views/dialogs/InteractiveAuthDialog.js
src/components/views/dialogs/UnknownDeviceDialog.js
src/components/views/elements/AccessibleButton.js
src/components/views/elements/ActionButton.js
src/components/views/elements/AddressSelector.js
src/components/views/elements/AddressTile.js
src/components/views/elements/CreateRoomButton.js
src/components/views/elements/DeviceVerifyButtons.js
src/components/views/elements/DirectorySearchBox.js
src/components/views/elements/Dropdown.js
src/components/views/elements/EditableText.js
src/components/views/elements/EditableTextContainer.js
src/components/views/elements/HomeButton.js
src/components/views/elements/LanguageDropdown.js
src/components/views/elements/MemberEventListSummary.js
src/components/views/elements/PowerSelector.js
src/components/views/elements/ProgressBar.js
src/components/views/elements/RoomDirectoryButton.js
src/components/views/elements/SettingsButton.js
src/components/views/elements/StartChatButton.js
src/components/views/elements/TintableSvg.js
src/components/views/elements/TruncatedList.js
src/components/views/elements/UserSelector.js
src/components/views/login/CaptchaForm.js
src/components/views/login/CasLogin.js
src/components/views/login/CountryDropdown.js
src/components/views/login/CustomServerDialog.js
src/components/views/login/InteractiveAuthEntryComponents.js
src/components/views/login/LoginHeader.js
src/components/views/login/PasswordLogin.js
src/components/views/login/RegistrationForm.js
src/components/views/login/ServerConfig.js
src/components/views/messages/MAudioBody.js
src/components/views/messages/MessageEvent.js
src/components/views/messages/MFileBody.js
src/components/views/messages/MImageBody.js
src/components/views/messages/MVideoBody.js
src/components/views/messages/RoomAvatarEvent.js
src/components/views/messages/TextualBody.js
src/components/views/messages/TextualEvent.js
src/components/views/room_settings/AliasSettings.js
src/components/views/room_settings/ColorSettings.js
src/components/views/room_settings/UrlPreviewSettings.js
@ -87,14 +62,11 @@ src/components/views/rooms/MemberList.js
src/components/views/rooms/MemberTile.js
src/components/views/rooms/MessageComposer.js
src/components/views/rooms/MessageComposerInput.js
src/components/views/rooms/PresenceLabel.js
src/components/views/rooms/ReadReceiptMarker.js
src/components/views/rooms/RoomList.js
src/components/views/rooms/RoomNameEditor.js
src/components/views/rooms/RoomPreviewBar.js
src/components/views/rooms/RoomSettings.js
src/components/views/rooms/RoomTile.js
src/components/views/rooms/RoomTopicEditor.js
src/components/views/rooms/SearchableEntityList.js
src/components/views/rooms/SearchResultTile.js
src/components/views/rooms/TopUnreadMessagesBar.js
@ -104,8 +76,6 @@ src/components/views/settings/ChangeAvatar.js
src/components/views/settings/ChangeDisplayName.js
src/components/views/settings/ChangePassword.js
src/components/views/settings/DevicesPanel.js
src/components/views/settings/DevicesPanelEntry.js
src/components/views/settings/EnableNotificationsButton.js
src/ContentMessages.js
src/HtmlUtils.js
src/ImageUtils.js
@ -123,8 +93,6 @@ src/RichText.js
src/Roles.js
src/Rooms.js
src/ScalarAuthClient.js
src/ScalarMessaging.js
src/TextForEvent.js
src/Tinter.js
src/UiEffects.js
src/Unread.js
@ -137,17 +105,13 @@ src/Velociraptor.js
src/VelocityBounce.js
src/WhoIsTyping.js
src/wrappers/withMatrixClient.js
test/all-tests.js
test/components/structures/login/Registration-test.js
test/components/structures/MessagePanel-test.js
test/components/structures/ScrollPanel-test.js
test/components/structures/TimelinePanel-test.js
test/components/stub-component.js
test/components/views/dialogs/InteractiveAuthDialog-test.js
test/components/views/elements/MemberEventListSummary-test.js
test/components/views/login/RegistrationForm-test.js
test/components/views/rooms/MessageComposerInput-test.js
test/mock-clock.js
test/skinned-sdk.js
test/stores/RoomViewStore-test.js
test/test-utils.js

View File

@ -40,6 +40,19 @@ module.exports = {
}],
"react/jsx-key": ["error"],
// Assert no spacing in JSX curly brackets
// <Element prop={ consideredError} prop={notConsideredError} />
//
// https://github.com/yannickcr/eslint-plugin-react/blob/HEAD/docs/rules/jsx-curly-spacing.md
"react/jsx-curly-spacing": ["error", {"when": "never", "children": {"when": "always"}}],
// Assert spacing before self-closing JSX tags, and no spacing before or
// after the closing slash, and no spacing after the opening bracket of
// the opening tag or closing tag.
//
// https://github.com/yannickcr/eslint-plugin-react/blob/HEAD/docs/rules/jsx-tag-spacing.md
"react/jsx-tag-spacing": ["error"],
/** flowtype **/
"flowtype/require-parameter-type": ["warn", {
"excludeArrowFunctions": true,

View File

@ -1,3 +1,106 @@
Changes in [0.10.6](https://github.com/matrix-org/matrix-react-sdk/releases/tag/v0.10.6) (2017-09-21)
=====================================================================================================
[Full Changelog](https://github.com/matrix-org/matrix-react-sdk/compare/v0.10.5...v0.10.6)
* New version of js-sdk with fixed build
Changes in [0.10.5](https://github.com/matrix-org/matrix-react-sdk/releases/tag/v0.10.5) (2017-09-21)
=====================================================================================================
[Full Changelog](https://github.com/matrix-org/matrix-react-sdk/compare/v0.10.4...v0.10.5)
* Fix build error (https://github.com/vector-im/riot-web/issues/5091)
Changes in [0.10.4](https://github.com/matrix-org/matrix-react-sdk/releases/tag/v0.10.4) (2017-09-20)
=====================================================================================================
[Full Changelog](https://github.com/matrix-org/matrix-react-sdk/compare/v0.10.4-rc.1...v0.10.4)
* No changes
Changes in [0.10.4-rc.1](https://github.com/matrix-org/matrix-react-sdk/releases/tag/v0.10.4-rc.1) (2017-09-19)
===============================================================================================================
[Full Changelog](https://github.com/matrix-org/matrix-react-sdk/compare/v0.10.3...v0.10.4-rc.1)
* Fix RoomView stuck in 'accept invite' state
[\#1396](https://github.com/matrix-org/matrix-react-sdk/pull/1396)
* Only show the integ management button if user is joined
[\#1398](https://github.com/matrix-org/matrix-react-sdk/pull/1398)
* suppressOnHover for member entity tiles which have no onClick
[\#1273](https://github.com/matrix-org/matrix-react-sdk/pull/1273)
* add /devtools command
[\#1268](https://github.com/matrix-org/matrix-react-sdk/pull/1268)
* Fix broken Link
[\#1359](https://github.com/matrix-org/matrix-react-sdk/pull/1359)
* Show who redacted an event on hover
[\#1387](https://github.com/matrix-org/matrix-react-sdk/pull/1387)
* start MELS expanded if it contains a highlighted/permalinked event.
[\#1388](https://github.com/matrix-org/matrix-react-sdk/pull/1388)
* Add ignore user API support
[\#1389](https://github.com/matrix-org/matrix-react-sdk/pull/1389)
* Add option to disable Emoji suggestions
[\#1392](https://github.com/matrix-org/matrix-react-sdk/pull/1392)
* sanitize the i18n for fn:textForHistoryVisibilityEvent
[\#1397](https://github.com/matrix-org/matrix-react-sdk/pull/1397)
* Don't check for only-emoji if there were none
[\#1394](https://github.com/matrix-org/matrix-react-sdk/pull/1394)
* Fix emojification of symbol characters
[\#1393](https://github.com/matrix-org/matrix-react-sdk/pull/1393)
* Update from Weblate.
[\#1395](https://github.com/matrix-org/matrix-react-sdk/pull/1395)
* Make /join join again
[\#1391](https://github.com/matrix-org/matrix-react-sdk/pull/1391)
* Display spinner not room preview after room create
[\#1390](https://github.com/matrix-org/matrix-react-sdk/pull/1390)
* Fix the avatar / room name in room preview
[\#1384](https://github.com/matrix-org/matrix-react-sdk/pull/1384)
* Remove spurious cancel button
[\#1381](https://github.com/matrix-org/matrix-react-sdk/pull/1381)
* Fix starting a chat by email address
[\#1386](https://github.com/matrix-org/matrix-react-sdk/pull/1386)
* respond on copy code block
[\#1363](https://github.com/matrix-org/matrix-react-sdk/pull/1363)
* fix DateUtils inconsistency with 12/24h
[\#1383](https://github.com/matrix-org/matrix-react-sdk/pull/1383)
* allow sending sub,sup and whitelist them on receive
[\#1382](https://github.com/matrix-org/matrix-react-sdk/pull/1382)
* Update roomlist when an event is decrypted
[\#1380](https://github.com/matrix-org/matrix-react-sdk/pull/1380)
* Update from Weblate.
[\#1379](https://github.com/matrix-org/matrix-react-sdk/pull/1379)
* fix radio for theme selection
[\#1368](https://github.com/matrix-org/matrix-react-sdk/pull/1368)
* fix some more zh_Hans - remove entirely broken lines
[\#1378](https://github.com/matrix-org/matrix-react-sdk/pull/1378)
* fix placeholder causing app to break when using zh
[\#1377](https://github.com/matrix-org/matrix-react-sdk/pull/1377)
* Avoid re-rendering RoomList on room switch
[\#1375](https://github.com/matrix-org/matrix-react-sdk/pull/1375)
* Fix 'Failed to load timeline position' regression
[\#1376](https://github.com/matrix-org/matrix-react-sdk/pull/1376)
* Fast path for emojifying strings
[\#1372](https://github.com/matrix-org/matrix-react-sdk/pull/1372)
* Consolidate the code copy button
[\#1374](https://github.com/matrix-org/matrix-react-sdk/pull/1374)
* Only add the code copy button for HTML messages
[\#1373](https://github.com/matrix-org/matrix-react-sdk/pull/1373)
* Don't re-render matrixchat unnecessarily
[\#1371](https://github.com/matrix-org/matrix-react-sdk/pull/1371)
* Don't wait for setState to run onHaveRoom
[\#1370](https://github.com/matrix-org/matrix-react-sdk/pull/1370)
* Introduce a RoomScrollStateStore
[\#1367](https://github.com/matrix-org/matrix-react-sdk/pull/1367)
* Don't always paginate when mounting a ScrollPanel
[\#1369](https://github.com/matrix-org/matrix-react-sdk/pull/1369)
* Remove unused scrollStateMap from LoggedinView
[\#1366](https://github.com/matrix-org/matrix-react-sdk/pull/1366)
* Revert "Implement sticky date separators"
[\#1365](https://github.com/matrix-org/matrix-react-sdk/pull/1365)
* Remove unused string "changing room on a RoomView is not supported"
[\#1361](https://github.com/matrix-org/matrix-react-sdk/pull/1361)
* Remove unused translation code translations
[\#1360](https://github.com/matrix-org/matrix-react-sdk/pull/1360)
* Implement sticky date separators
[\#1353](https://github.com/matrix-org/matrix-react-sdk/pull/1353)
Changes in [0.10.3](https://github.com/matrix-org/matrix-react-sdk/releases/tag/v0.10.3) (2017-09-06)
=====================================================================================================
[Full Changelog](https://github.com/matrix-org/matrix-react-sdk/compare/v0.10.3-rc.2...v0.10.3)

View File

@ -46,7 +46,7 @@ Please follow the standard Matrix contributor's guide:
https://github.com/matrix-org/synapse/tree/master/CONTRIBUTING.rst
Please follow the Matrix JS/React code style as per:
https://github.com/matrix-org/matrix-react-sdk/tree/master/code_style.rst
https://github.com/matrix-org/matrix-react-sdk/blob/master/code_style.md
Whilst the layering separation between matrix-react-sdk and Riot is broken
(as of July 2016), code should be committed as follows:

View File

@ -21,9 +21,7 @@ npm run test -- --no-colors
npm run lintall -- -f checkstyle -o eslint.xml || true
# re-run the linter, excluding any files known to have errors or warnings.
./node_modules/.bin/eslint --max-warnings 0 \
--ignore-path .eslintignore.errorfiles \
src test
npm run lintwithexclusions
# delete the old tarball, if it exists
rm -f matrix-react-sdk-*.tgz

View File

@ -1,6 +1,6 @@
{
"name": "matrix-react-sdk",
"version": "0.10.3",
"version": "0.10.6",
"description": "SDK for matrix.org using React",
"author": "matrix.org",
"repository": {
@ -39,8 +39,9 @@
"start": "parallelshell \"npm run build:watch\" \"npm run reskindex:watch\"",
"lint": "eslint src/",
"lintall": "eslint src/ test/",
"lintwithexclusions": "eslint --max-warnings 0 --ignore-path .eslintignore.errorfiles src test",
"clean": "rimraf lib",
"prepublish": "npm run build && git rev-parse HEAD > git-revision.txt",
"prepublish": "npm run clean && npm run build && git rev-parse HEAD > git-revision.txt",
"test": "karma start --single-run=true --browsers ChromeHeadless",
"test-multi": "karma start"
},
@ -66,7 +67,7 @@
"isomorphic-fetch": "^2.2.1",
"linkifyjs": "^2.1.3",
"lodash": "^4.13.1",
"matrix-js-sdk": "0.8.2",
"matrix-js-sdk": "0.8.4",
"optimist": "^0.6.1",
"prop-types": "^15.5.8",
"react": "^15.4.0",
@ -99,7 +100,7 @@
"eslint-config-google": "^0.7.1",
"eslint-plugin-babel": "^4.0.1",
"eslint-plugin-flowtype": "^2.30.0",
"eslint-plugin-react": "^6.9.0",
"eslint-plugin-react": "^7.4.0",
"expect": "^1.16.0",
"json-loader": "^0.5.3",
"karma": "^1.7.0",

View File

@ -6,6 +6,4 @@ npm run test
./.travis-test-riot.sh
# run the linter, but exclude any files known to have errors or warnings.
./node_modules/.bin/eslint --max-warnings 0 \
--ignore-path .eslintignore.errorfiles \
src test
npm run lintwithexclusions

View File

@ -107,6 +107,9 @@ export default class BasePlatform {
isElectron(): boolean { return false; }
setupScreenSharingForIframe() {
}
/**
* Restarts the application, without neccessarily reloading
* any application code

View File

@ -63,23 +63,22 @@ import dis from './dispatcher';
global.mxCalls = {
//room_id: MatrixCall
};
var calls = global.mxCalls;
var ConferenceHandler = null;
const calls = global.mxCalls;
let ConferenceHandler = null;
var audioPromises = {};
const audioPromises = {};
function play(audioId) {
// TODO: Attach an invisible element for this instead
// which listens?
var audio = document.getElementById(audioId);
const audio = document.getElementById(audioId);
if (audio) {
if (audioPromises[audioId]) {
audioPromises[audioId] = audioPromises[audioId].then(()=>{
audio.load();
return audio.play();
});
}
else {
} else {
audioPromises[audioId] = audio.play();
}
}
@ -88,12 +87,11 @@ function play(audioId) {
function pause(audioId) {
// TODO: Attach an invisible element for this instead
// which listens?
var audio = document.getElementById(audioId);
const audio = document.getElementById(audioId);
if (audio) {
if (audioPromises[audioId]) {
audioPromises[audioId] = audioPromises[audioId].then(()=>audio.pause());
}
else {
} else {
// pause doesn't actually return a promise, but might as well do this for symmetry with play();
audioPromises[audioId] = audio.pause();
}
@ -125,38 +123,32 @@ function _setCallListeners(call) {
if (newState === "ringing") {
_setCallState(call, call.roomId, "ringing");
pause("ringbackAudio");
}
else if (newState === "invite_sent") {
} else if (newState === "invite_sent") {
_setCallState(call, call.roomId, "ringback");
play("ringbackAudio");
}
else if (newState === "ended" && oldState === "connected") {
} else if (newState === "ended" && oldState === "connected") {
_setCallState(undefined, call.roomId, "ended");
pause("ringbackAudio");
play("callendAudio");
}
else if (newState === "ended" && oldState === "invite_sent" &&
} else if (newState === "ended" && oldState === "invite_sent" &&
(call.hangupParty === "remote" ||
(call.hangupParty === "local" && call.hangupReason === "invite_timeout")
)) {
_setCallState(call, call.roomId, "busy");
pause("ringbackAudio");
play("busyAudio");
var ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
Modal.createTrackedDialog('Call Handler', 'Call Timeout', ErrorDialog, {
title: _t('Call Timeout'),
description: _t('The remote side failed to pick up') + '.',
});
}
else if (oldState === "invite_sent") {
} else if (oldState === "invite_sent") {
_setCallState(call, call.roomId, "stop_ringback");
pause("ringbackAudio");
}
else if (oldState === "ringing") {
} else if (oldState === "ringing") {
_setCallState(call, call.roomId, "stop_ringing");
pause("ringbackAudio");
}
else if (newState === "connected") {
} else if (newState === "connected") {
_setCallState(call, call.roomId, "connected");
pause("ringbackAudio");
}
@ -165,14 +157,13 @@ function _setCallListeners(call) {
function _setCallState(call, roomId, status) {
console.log(
"Call state in %s changed to %s (%s)", roomId, status, (call ? call.call_state : "-")
"Call state in %s changed to %s (%s)", roomId, status, (call ? call.call_state : "-"),
);
calls[roomId] = call;
if (status === "ringing") {
play("ringAudio");
}
else if (call && call.call_state === "ringing") {
} else if (call && call.call_state === "ringing") {
pause("ringAudio");
}
@ -192,14 +183,12 @@ function _onAction(payload) {
_setCallState(newCall, newCall.roomId, "ringback");
if (payload.type === 'voice') {
newCall.placeVoiceCall();
}
else if (payload.type === 'video') {
} else if (payload.type === 'video') {
newCall.placeVideoCall(
payload.remote_element,
payload.local_element
payload.local_element,
);
}
else if (payload.type === 'screensharing') {
} else if (payload.type === 'screensharing') {
const screenCapErrorString = PlatformPeg.get().screenCaptureErrorString();
if (screenCapErrorString) {
_setCallState(undefined, newCall.roomId, "ended");
@ -213,10 +202,9 @@ function _onAction(payload) {
}
newCall.placeScreenSharingCall(
payload.remote_element,
payload.local_element
payload.local_element,
);
}
else {
} else {
console.error("Unknown conf call type: %s", payload.type);
}
}
@ -255,21 +243,19 @@ function _onAction(payload) {
description: _t('You cannot place a call with yourself.'),
});
return;
}
else if (members.length === 2) {
} else if (members.length === 2) {
console.log("Place %s call in %s", payload.type, payload.room_id);
const call = Matrix.createNewMatrixCall(MatrixClientPeg.get(), payload.room_id, {
forceTURN: UserSettingsStore.getLocalSetting('webRtcForceTURN', false),
});
placeCall(call);
}
else { // > 2
} else { // > 2
dis.dispatch({
action: "place_conference_call",
room_id: payload.room_id,
type: payload.type,
remote_element: payload.remote_element,
local_element: payload.local_element
local_element: payload.local_element,
});
}
break;
@ -280,15 +266,13 @@ function _onAction(payload) {
Modal.createTrackedDialog('Call Handler', 'Conference call unsupported client', ErrorDialog, {
description: _t('Conference calls are not supported in this client'),
});
}
else if (!MatrixClientPeg.get().supportsVoip()) {
} else if (!MatrixClientPeg.get().supportsVoip()) {
const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
Modal.createTrackedDialog('Call Handler', 'VoIP is unsupported', ErrorDialog, {
title: _t('VoIP is unsupported'),
description: _t('You cannot place VoIP calls in this browser.'),
});
}
else if (MatrixClientPeg.get().isRoomEncrypted(payload.room_id)) {
} else if (MatrixClientPeg.get().isRoomEncrypted(payload.room_id)) {
// Conference calls are implemented by sending the media to central
// server which combines the audio from all the participants together
// into a single stream. This is incompatible with end-to-end encryption
@ -299,16 +283,15 @@ function _onAction(payload) {
Modal.createTrackedDialog('Call Handler', 'Conference calls unsupported e2e', ErrorDialog, {
description: _t('Conference calls are not supported in encrypted rooms'),
});
}
else {
var QuestionDialog = sdk.getComponent("dialogs.QuestionDialog");
} else {
const QuestionDialog = sdk.getComponent("dialogs.QuestionDialog");
Modal.createTrackedDialog('Call Handler', 'Conference calling in development', QuestionDialog, {
title: _t('Warning!'),
description: _t('Conference calling is in development and may not be reliable.'),
onFinished: confirm=>{
onFinished: (confirm)=>{
if (confirm) {
ConferenceHandler.createNewMatrixCall(
MatrixClientPeg.get(), payload.room_id
MatrixClientPeg.get(), payload.room_id,
).done(function(call) {
placeCall(call);
}, function(err) {
@ -357,7 +340,7 @@ function _onAction(payload) {
_setCallState(calls[payload.room_id], payload.room_id, "connected");
dis.dispatch({
action: "view_room",
room_id: payload.room_id
room_id: payload.room_id,
});
break;
}
@ -368,9 +351,9 @@ if (!global.mxCallHandler) {
dis.register(_onAction);
}
var callHandler = {
const callHandler = {
getCallForRoom: function(roomId) {
var call = module.exports.getCall(roomId);
let call = module.exports.getCall(roomId);
if (call) return call;
if (ConferenceHandler) {
@ -386,8 +369,8 @@ var callHandler = {
},
getAnyActiveCall: function() {
var roomsWithCalls = Object.keys(calls);
for (var i = 0; i < roomsWithCalls.length; i++) {
const roomsWithCalls = Object.keys(calls);
for (let i = 0; i < roomsWithCalls.length; i++) {
if (calls[roomsWithCalls[i]] &&
calls[roomsWithCalls[i]].call_state !== "ended") {
return calls[roomsWithCalls[i]];
@ -402,7 +385,7 @@ var callHandler = {
getConferenceHandler: function() {
return ConferenceHandler;
}
},
};
// Only things in here which actually need to be global are the
// calls list (done separately) and making sure we only register

View File

@ -17,14 +17,14 @@ limitations under the License.
'use strict';
import Promise from 'bluebird';
var extend = require('./extend');
var dis = require('./dispatcher');
var MatrixClientPeg = require('./MatrixClientPeg');
var sdk = require('./index');
const extend = require('./extend');
const dis = require('./dispatcher');
const MatrixClientPeg = require('./MatrixClientPeg');
const sdk = require('./index');
import { _t } from './languageHandler';
var Modal = require('./Modal');
const Modal = require('./Modal');
var encrypt = require("browser-encrypt-attachment");
const encrypt = require("browser-encrypt-attachment");
// Polyfill for Canvas.toBlob API using Canvas.toDataURL
require("blueimp-canvas-to-blob");
@ -54,8 +54,8 @@ const MAX_HEIGHT = 600;
function createThumbnail(element, inputWidth, inputHeight, mimeType) {
const deferred = Promise.defer();
var targetWidth = inputWidth;
var targetHeight = inputHeight;
let targetWidth = inputWidth;
let targetHeight = inputHeight;
if (targetHeight > MAX_HEIGHT) {
targetWidth = Math.floor(targetWidth * (MAX_HEIGHT / targetHeight));
targetHeight = MAX_HEIGHT;
@ -81,7 +81,7 @@ function createThumbnail(element, inputWidth, inputHeight, mimeType) {
w: inputWidth,
h: inputHeight,
},
thumbnail: thumbnail
thumbnail: thumbnail,
});
}, mimeType);
@ -129,12 +129,12 @@ function loadImageElement(imageFile) {
* @return {Promise} A promise that resolves with the attachment info.
*/
function infoForImageFile(matrixClient, roomId, imageFile) {
var thumbnailType = "image/png";
let thumbnailType = "image/png";
if (imageFile.type == "image/jpeg") {
thumbnailType = "image/jpeg";
}
var imageInfo;
let imageInfo;
return loadImageElement(imageFile).then(function(img) {
return createThumbnail(img, img.width, img.height, thumbnailType);
}).then(function(result) {
@ -191,7 +191,7 @@ function loadVideoElement(videoFile) {
function infoForVideoFile(matrixClient, roomId, videoFile) {
const thumbnailType = "image/jpeg";
var videoInfo;
let videoInfo;
return loadVideoElement(videoFile).then(function(video) {
return createThumbnail(video, video.videoWidth, video.videoHeight, thumbnailType);
}).then(function(result) {
@ -286,7 +286,7 @@ class ContentMessages {
body: file.name || 'Attachment',
info: {
size: file.size,
}
},
};
// if we have a mime type for the file, add it to the message metadata
@ -297,10 +297,10 @@ class ContentMessages {
const def = Promise.defer();
if (file.type.indexOf('image/') == 0) {
content.msgtype = 'm.image';
infoForImageFile(matrixClient, roomId, file).then(imageInfo=>{
infoForImageFile(matrixClient, roomId, file).then((imageInfo)=>{
extend(content.info, imageInfo);
def.resolve();
}, error=>{
}, (error)=>{
console.error(error);
content.msgtype = 'm.file';
def.resolve();
@ -310,10 +310,10 @@ class ContentMessages {
def.resolve();
} else if (file.type.indexOf('video/') == 0) {
content.msgtype = 'm.video';
infoForVideoFile(matrixClient, roomId, file).then(videoInfo=>{
infoForVideoFile(matrixClient, roomId, file).then((videoInfo)=>{
extend(content.info, videoInfo);
def.resolve();
}, error=>{
}, (error)=>{
content.msgtype = 'm.file';
def.resolve();
});
@ -331,7 +331,7 @@ class ContentMessages {
this.inprogress.push(upload);
dis.dispatch({action: 'upload_started'});
var error;
let error;
function onProgress(ev) {
upload.total = ev.total;
@ -355,11 +355,11 @@ class ContentMessages {
}, function(err) {
error = err;
if (!upload.canceled) {
var desc = _t('The file \'%(fileName)s\' failed to upload', {fileName: upload.fileName}) + '.';
let desc = _t('The file \'%(fileName)s\' failed to upload', {fileName: upload.fileName}) + '.';
if (err.http_status == 413) {
desc = _t('The file \'%(fileName)s\' exceeds this home server\'s size limit for uploads', {fileName: upload.fileName});
}
var ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
Modal.createTrackedDialog('Upload failed', '', ErrorDialog, {
title: _t('Upload Failed'),
description: desc,
@ -367,8 +367,8 @@ class ContentMessages {
}
}).finally(() => {
const inprogressKeys = Object.keys(this.inprogress);
for (var i = 0; i < this.inprogress.length; ++i) {
var k = inprogressKeys[i];
for (let i = 0; i < this.inprogress.length; ++i) {
const k = inprogressKeys[i];
if (this.inprogress[k].promise === upload.promise) {
this.inprogress.splice(k, 1);
break;
@ -376,8 +376,7 @@ class ContentMessages {
}
if (error) {
dis.dispatch({action: 'upload_failed', upload: upload});
}
else {
} else {
dis.dispatch({action: 'upload_finished', upload: upload});
}
});
@ -389,9 +388,9 @@ class ContentMessages {
cancelUpload(promise) {
const inprogressKeys = Object.keys(this.inprogress);
var upload;
for (var i = 0; i < this.inprogress.length; ++i) {
var k = inprogressKeys[i];
let upload;
for (let i = 0; i < this.inprogress.length; ++i) {
const k = inprogressKeys[i];
if (this.inprogress[k].promise === promise) {
upload = this.inprogress[k];
break;

113
src/GroupAddressPicker.js Normal file
View File

@ -0,0 +1,113 @@
/*
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.
*/
import Modal from './Modal';
import sdk from './';
import MultiInviter from './utils/MultiInviter';
import { _t } from './languageHandler';
import MatrixClientPeg from './MatrixClientPeg';
import GroupStoreCache from './stores/GroupStoreCache';
export function showGroupInviteDialog(groupId) {
const AddressPickerDialog = sdk.getComponent("dialogs.AddressPickerDialog");
Modal.createTrackedDialog('Group Invite', '', AddressPickerDialog, {
title: _t("Invite new group members"),
description: _t("Who would you like to add to this group?"),
placeholder: _t("Name or matrix ID"),
button: _t("Invite to Group"),
validAddressTypes: ['mx-user-id'],
onFinished: (success, addrs) => {
if (!success) return;
_onGroupInviteFinished(groupId, addrs);
},
});
}
export function showGroupAddRoomDialog(groupId) {
return new Promise((resolve, reject) => {
const AddressPickerDialog = sdk.getComponent("dialogs.AddressPickerDialog");
Modal.createTrackedDialog('Add Rooms to Group', '', AddressPickerDialog, {
title: _t("Add rooms to the group"),
description: _t("Which rooms would you like to add to this group?"),
placeholder: _t("Room name or alias"),
button: _t("Add to group"),
pickerType: 'room',
validAddressTypes: ['mx-room-id'],
onFinished: (success, addrs) => {
if (!success) return;
_onGroupAddRoomFinished(groupId, addrs).then(resolve, reject);
},
});
});
}
function _onGroupInviteFinished(groupId, addrs) {
const multiInviter = new MultiInviter(groupId);
const addrTexts = addrs.map((addr) => addr.address);
multiInviter.invite(addrTexts).then((completionStates) => {
// Show user any errors
const errorList = [];
for (const addr of Object.keys(completionStates)) {
if (addrs[addr] === "error") {
errorList.push(addr);
}
}
if (errorList.length > 0) {
const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
Modal.createTrackedDialog('Failed to invite the following users to the group', '', ErrorDialog, {
title: _t("Failed to invite the following users to %(groupId)s:", {groupId: groupId}),
description: errorList.join(", "),
});
}
}).catch((err) => {
const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
Modal.createTrackedDialog('Failed to invite users to group', '', ErrorDialog, {
title: _t("Failed to invite users group"),
description: _t("Failed to invite users to %(groupId)s", {groupId: groupId}),
});
});
}
function _onGroupAddRoomFinished(groupId, addrs) {
const groupStore = GroupStoreCache.getGroupStore(MatrixClientPeg.get(), groupId);
const errorList = [];
return Promise.all(addrs.map((addr) => {
return groupStore
.addRoomToGroup(addr.address)
.catch(() => { errorList.push(addr.address); })
.reflect();
})).then(() => {
if (errorList.length === 0) {
return;
}
const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
Modal.createTrackedDialog(
'Failed to add the following room to the group',
'', ErrorDialog,
{
title: _t(
"Failed to add the following rooms to %(groupId)s:",
{groupId},
),
description: errorList.join(", "),
});
});
}

View File

@ -17,10 +17,10 @@ limitations under the License.
'use strict';
var React = require('react');
var sanitizeHtml = require('sanitize-html');
var highlight = require('highlight.js');
var linkifyMatrix = require('./linkify-matrix');
const React = require('react');
const sanitizeHtml = require('sanitize-html');
const highlight = require('highlight.js');
const linkifyMatrix = require('./linkify-matrix');
import escape from 'lodash/escape';
import emojione from 'emojione';
import classNames from 'classnames';
@ -66,8 +66,7 @@ function unicodeToImage(str) {
if ( (typeof unicodeChar === 'undefined') || (unicodeChar === '') || (!(unicodeChar in emojione.jsEscapeMap)) ) {
// if the unicodeChar doesnt exist just return the entire match
return unicodeChar;
}
else {
} else {
// get the unicode codepoint from the actual char
unicode = emojione.jsEscapeMap[unicodeChar];
@ -183,21 +182,19 @@ const sanitizeHtmlParams = {
if (attribs.href) {
attribs.target = '_blank'; // by default
var m;
let m;
// FIXME: horrible duplication with linkify-matrix
m = attribs.href.match(linkifyMatrix.VECTOR_URL_PATTERN);
if (m) {
attribs.href = m[1];
delete attribs.target;
}
else {
} else {
m = attribs.href.match(linkifyMatrix.MATRIXTO_URL_PATTERN);
if (m) {
var entity = m[1];
const entity = m[1];
if (entity[0] === '@') {
attribs.href = '#/user/' + entity;
}
else if (entity[0] === '#' || entity[0] === '!') {
} else if (entity[0] === '#' || entity[0] === '!') {
attribs.href = '#/room/' + entity;
}
delete attribs.target;
@ -205,7 +202,7 @@ const sanitizeHtmlParams = {
}
}
attribs.rel = 'noopener'; // https://mathiasbynens.github.io/rel-noopener/
return { tagName: tagName, attribs : attribs };
return { tagName: tagName, attribs: attribs };
},
'img': function(tagName, attribs) {
// Strip out imgs that aren't `mxc` here instead of using allowedSchemesByTag
@ -224,7 +221,7 @@ const sanitizeHtmlParams = {
'code': function(tagName, attribs) {
if (typeof attribs.class !== 'undefined') {
// Filter out all classes other than ones starting with language- for syntax highlighting.
let classes = attribs.class.split(/\s+/).filter(function(cl) {
const classes = attribs.class.split(/\s+/).filter(function(cl) {
return cl.startsWith('language-');
});
attribs.class = classes.join(' ');
@ -287,11 +284,11 @@ class BaseHighlighter {
* TextHighlighter).
*/
applyHighlights(safeSnippet, safeHighlights) {
var lastOffset = 0;
var offset;
var nodes = [];
let lastOffset = 0;
let offset;
let nodes = [];
var safeHighlight = safeHighlights[0];
const safeHighlight = safeHighlights[0];
while ((offset = safeSnippet.toLowerCase().indexOf(safeHighlight.toLowerCase(), lastOffset)) >= 0) {
// handle preamble
if (offset > lastOffset) {
@ -301,7 +298,7 @@ class BaseHighlighter {
// do highlight. use the original string rather than safeHighlight
// to preserve the original casing.
var endOffset = offset + safeHighlight.length;
const endOffset = offset + safeHighlight.length;
nodes.push(this._processSnippet(safeSnippet.substring(offset, endOffset), true));
lastOffset = endOffset;
@ -319,8 +316,7 @@ class BaseHighlighter {
if (safeHighlights[1]) {
// recurse into this range to check for the next set of highlight matches
return this.applyHighlights(safeSnippet, safeHighlights.slice(1));
}
else {
} else {
// no more highlights to be found, just return the unhighlighted string
return [this._processSnippet(safeSnippet, false)];
}
@ -341,7 +337,7 @@ class HtmlHighlighter extends BaseHighlighter {
return snippet;
}
var span = "<span class=\""+this.highlightClass+"\">"
let span = "<span class=\""+this.highlightClass+"\">"
+ snippet + "</span>";
if (this.highlightLink) {
@ -366,15 +362,15 @@ class TextHighlighter extends BaseHighlighter {
* returns a React node
*/
_processSnippet(snippet, highlight) {
var key = this._key++;
const key = this._key++;
var node =
<span key={key} className={highlight ? this.highlightClass : null }>
let node =
<span key={key} className={highlight ? this.highlightClass : null}>
{ snippet }
</span>;
if (highlight && this.highlightLink) {
node = <a key={key} href={this.highlightLink}>{node}</a>;
node = <a key={key} href={this.highlightLink}>{ node }</a>;
}
return node;
@ -393,20 +389,20 @@ class TextHighlighter extends BaseHighlighter {
export function bodyToHtml(content, highlights, opts) {
opts = opts || {};
var isHtml = (content.format === "org.matrix.custom.html");
let body = isHtml ? content.formatted_body : escape(content.body);
const isHtml = (content.format === "org.matrix.custom.html");
const body = isHtml ? content.formatted_body : escape(content.body);
let bodyHasEmoji = false;
var safeBody;
let safeBody;
// XXX: We sanitize the HTML whilst also highlighting its text nodes, to avoid accidentally trying
// to highlight HTML tags themselves. However, this does mean that we don't highlight textnodes which
// are interrupted by HTML tags (not that we did before) - e.g. foo<span/>bar won't get highlighted
// by an attempt to search for 'foobar'. Then again, the search query probably wouldn't work either
try {
if (highlights && highlights.length > 0) {
var highlighter = new HtmlHighlighter("mx_EventTile_searchHighlight", opts.highlightLink);
var safeHighlights = highlights.map(function(highlight) {
const highlighter = new HtmlHighlighter("mx_EventTile_searchHighlight", opts.highlightLink);
const safeHighlights = highlights.map(function(highlight) {
return sanitizeHtml(highlight, sanitizeHtmlParams);
});
// XXX: hacky bodge to temporarily apply a textFilter to the sanitizeHtmlParams structure.
@ -417,16 +413,15 @@ export function bodyToHtml(content, highlights, opts) {
safeBody = sanitizeHtml(body, sanitizeHtmlParams);
bodyHasEmoji = containsEmoji(body);
if (bodyHasEmoji) safeBody = unicodeToImage(safeBody);
}
finally {
} finally {
delete sanitizeHtmlParams.textFilter;
}
let emojiBody = false;
if (bodyHasEmoji) {
EMOJI_REGEX.lastIndex = 0;
let contentBodyTrimmed = content.body !== undefined ? content.body.trim() : '';
let match = EMOJI_REGEX.exec(contentBodyTrimmed);
const contentBodyTrimmed = content.body !== undefined ? content.body.trim() : '';
const match = EMOJI_REGEX.exec(contentBodyTrimmed);
emojiBody = match && match[0] && match[0].length === contentBodyTrimmed.length;
}

View File

@ -42,13 +42,12 @@ module.exports = {
// no scaling needs to be applied
return fullHeight;
}
var widthMulti = thumbWidth / fullWidth;
var heightMulti = thumbHeight / fullHeight;
const widthMulti = thumbWidth / fullWidth;
const heightMulti = thumbHeight / fullHeight;
if (widthMulti < heightMulti) {
// width is the dominant dimension so scaling will be fixed on that
return Math.floor(widthMulti * fullHeight);
}
else {
} else {
// height is the dominant dimension so scaling will be fixed on that
return Math.floor(heightMulti * fullHeight);
}

View File

@ -59,8 +59,8 @@ export default class Login {
}
getFlows() {
var self = this;
var client = this._createTemporaryClient();
const self = this;
const client = this._createTemporaryClient();
return client.loginFlows().then(function(result) {
self._flows = result.flows;
self._currentFlowIndex = 0;
@ -77,12 +77,12 @@ export default class Login {
getCurrentFlowStep() {
// technically the flow can have multiple steps, but no one does this
// for login so we can ignore it.
var flowStep = this._flows[this._currentFlowIndex];
const flowStep = this._flows[this._currentFlowIndex];
return flowStep ? flowStep.type : null;
}
loginAsGuest() {
var client = this._createTemporaryClient();
const client = this._createTemporaryClient();
return client.registerGuest({
body: {
initial_device_display_name: this._defaultDeviceDisplayName,
@ -94,7 +94,7 @@ export default class Login {
accessToken: creds.access_token,
homeserverUrl: this._hsUrl,
identityServerUrl: this._isUrl,
guest: true
guest: true,
};
}, (error) => {
throw error;
@ -149,12 +149,12 @@ export default class Login {
identityServerUrl: self._isUrl,
userId: data.user_id,
deviceId: data.device_id,
accessToken: data.access_token
accessToken: data.access_token,
});
}, function(error) {
if (error.httpStatus === 403) {
if (self._fallbackHsUrl) {
var fbClient = Matrix.createClient({
const fbClient = Matrix.createClient({
baseUrl: self._fallbackHsUrl,
idBaseUrl: this._isUrl,
});
@ -165,7 +165,7 @@ export default class Login {
identityServerUrl: self._isUrl,
userId: data.user_id,
deviceId: data.device_id,
accessToken: data.access_token
accessToken: data.access_token,
});
}, function(fallback_error) {
// throw the original error

View File

@ -48,7 +48,7 @@ function html_if_tag_allowed(node) {
* or false if it is only a single line.
*/
function is_multi_line(node) {
var par = node;
let par = node;
while (par.parent) {
par = par.parent;
}
@ -143,7 +143,7 @@ export default class Markdown {
if (isMultiLine) this.cr();
html_if_tag_allowed.call(this, node);
if (isMultiLine) this.cr();
}
};
return renderer.render(this.parsed);
}
@ -178,7 +178,7 @@ export default class Markdown {
renderer.html_block = function(node) {
this.lit(node.literal);
if (is_multi_line(node) && node.next) this.lit('\n\n');
}
};
return renderer.render(this.parsed);
}

View File

@ -95,7 +95,7 @@ class MatrixClientPeg {
opts.pendingEventOrdering = "detached";
try {
let promise = this.matrixClient.store.startup();
const promise = this.matrixClient.store.startup();
console.log(`MatrixClientPeg: waiting for MatrixClient store to initialise`);
await promise;
} catch(err) {
@ -136,7 +136,7 @@ class MatrixClientPeg {
}
_createClient(creds: MatrixClientCreds) {
var opts = {
const opts = {
baseUrl: creds.homeserverUrl,
idBaseUrl: creds.identityServerUrl,
accessToken: creds.accessToken,
@ -153,8 +153,8 @@ class MatrixClientPeg {
this.matrixClient.setGuest(Boolean(creds.guest));
var notifTimelineSet = new EventTimelineSet(null, {
timelineSupport: true
const notifTimelineSet = new EventTimelineSet(null, {
timelineSupport: true,
});
// XXX: what is our initial pagination token?! it somehow needs to be synchronised with /sync.
notifTimelineSet.getLiveTimeline().setPaginationToken("", EventTimeline.BACKWARDS);

View File

@ -17,8 +17,8 @@ limitations under the License.
'use strict';
var React = require('react');
var ReactDOM = require('react-dom');
const React = require('react');
const ReactDOM = require('react-dom');
import Analytics from './Analytics';
import sdk from './index';
@ -137,15 +137,15 @@ class ModalManager {
* @param {String} className CSS class to apply to the modal wrapper
*/
createDialogAsync(loader, props, className) {
var self = this;
const self = this;
const modal = {};
// never call this from onFinished() otherwise it will loop
//
// nb explicit function() rather than arrow function, to get `arguments`
var closeDialog = function() {
const closeDialog = function() {
if (props && props.onFinished) props.onFinished.apply(null, arguments);
var i = self._modals.indexOf(modal);
const i = self._modals.indexOf(modal);
if (i >= 0) {
self._modals.splice(i, 1);
}
@ -160,7 +160,7 @@ class ModalManager {
// property set here so you can't close the dialog from a button click!
modal.elem = (
<AsyncWrapper key={modalCount} loader={loader} {...props}
onFinished={closeDialog}/>
onFinished={closeDialog} />
);
modal.onFinished = props ? props.onFinished : null;
modal.className = className;
@ -191,13 +191,13 @@ class ModalManager {
return;
}
var modal = this._modals[0];
var dialog = (
<div className={"mx_Dialog_wrapper " + (modal.className ? modal.className : '') }>
const modal = this._modals[0];
const dialog = (
<div className={"mx_Dialog_wrapper " + (modal.className ? modal.className : '')}>
<div className="mx_Dialog">
{modal.elem}
{ modal.elem }
</div>
<div className="mx_Dialog_background" onClick={ this.closeAll }></div>
<div className="mx_Dialog_background" onClick={this.closeAll}></div>
</div>
);

View File

@ -81,7 +81,7 @@ const Notifier = {
}
const avatarUrl = ev.sender ? Avatar.avatarUrlForMember(
ev.sender, 40, 40, 'crop'
ev.sender, 40, 40, 'crop',
) : null;
const notif = plaf.displayNotification(title, msg, avatarUrl, room);
@ -303,7 +303,7 @@ const Notifier = {
this._playAudioNotification(ev, room);
}
}
}
},
};
if (!global.mxNotifier) {

View File

@ -14,12 +14,12 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
var MatrixClientPeg = require("./MatrixClientPeg");
var dis = require("./dispatcher");
const MatrixClientPeg = require("./MatrixClientPeg");
const dis = require("./dispatcher");
// Time in ms after that a user is considered as unavailable/away
var UNAVAILABLE_TIME_MS = 3 * 60 * 1000; // 3 mins
var PRESENCE_STATES = ["online", "offline", "unavailable"];
const UNAVAILABLE_TIME_MS = 3 * 60 * 1000; // 3 mins
const PRESENCE_STATES = ["online", "offline", "unavailable"];
class Presence {
@ -71,14 +71,14 @@ class Presence {
if (!this.running) {
return;
}
var old_state = this.state;
const old_state = this.state;
this.state = newState;
if (MatrixClientPeg.get().isGuest()) {
return; // don't try to set presence when a guest; it won't work.
}
var self = this;
const self = this;
MatrixClientPeg.get().setPresence(this.state).done(function() {
console.log("Presence: %s", newState);
}, function(err) {
@ -104,7 +104,7 @@ class Presence {
* @private
*/
_resetTimer() {
var self = this;
const self = this;
this.setState("online");
// Re-arm the timer
clearTimeout(this.timer);

View File

@ -44,9 +44,9 @@ export const contentStateToHTML = (contentState: ContentState) => {
return stateToHTML(contentState, {
inlineStyles: {
UNDERLINE: {
element: 'u'
}
}
element: 'u',
},
},
});
};
@ -59,7 +59,7 @@ function unicodeToEmojiUri(str) {
let replaceWith, unicode, alt;
if ((!emojione.unicodeAlt) || (emojione.sprites)) {
// if we are using the shortname as the alt tag then we need a reversed array to map unicode code point to shortnames
let mappedUnicode = emojione.mapUnicodeToShort();
const mappedUnicode = emojione.mapUnicodeToShort();
}
str = str.replace(emojione.regUnicode, function(unicodeChar) {
@ -67,8 +67,14 @@ function unicodeToEmojiUri(str) {
// if the unicodeChar doesnt exist just return the entire match
return unicodeChar;
} else {
// Remove variant selector VS16 (explicitly emoji) as it is unnecessary and leads to an incorrect URL below
if(unicodeChar.length == 2 && unicodeChar[1] == '\ufe0f') {
unicodeChar = unicodeChar[0];
}
// get the unicode codepoint from the actual char
unicode = emojione.jsEscapeMap[unicodeChar];
return emojione.imagePathSVG+unicode+'.svg'+emojione.cacheBustParam;
}
});
@ -90,14 +96,14 @@ function findWithRegex(regex, contentBlock: ContentBlock, callback: (start: numb
}
// Workaround for https://github.com/facebook/draft-js/issues/414
let emojiDecorator = {
const emojiDecorator = {
strategy: (contentState, contentBlock, callback) => {
findWithRegex(EMOJI_REGEX, contentBlock, callback);
},
component: (props) => {
let uri = unicodeToEmojiUri(props.children[0].props.text);
let shortname = emojione.toShort(props.children[0].props.text);
let style = {
const uri = unicodeToEmojiUri(props.children[0].props.text);
const shortname = emojione.toShort(props.children[0].props.text);
const style = {
display: 'inline-block',
width: '1em',
maxHeight: '1em',
@ -106,7 +112,7 @@ let emojiDecorator = {
backgroundPosition: 'center center',
overflow: 'hidden',
};
return (<span title={shortname} style={style}><span style={{opacity: 0}}>{props.children}</span></span>);
return (<span title={shortname} style={style}><span style={{opacity: 0}}>{ props.children }</span></span>);
},
};
@ -118,16 +124,16 @@ export function getScopedRTDecorators(scope: any): CompositeDecorator {
}
export function getScopedMDDecorators(scope: any): CompositeDecorator {
let markdownDecorators = ['HR', 'BOLD', 'ITALIC', 'CODE', 'STRIKETHROUGH'].map(
const markdownDecorators = ['HR', 'BOLD', 'ITALIC', 'CODE', 'STRIKETHROUGH'].map(
(style) => ({
strategy: (contentState, contentBlock, callback) => {
return findWithRegex(MARKDOWN_REGEX[style], contentBlock, callback);
},
component: (props) => (
<span className={"mx_MarkdownElement mx_Markdown_" + style}>
{props.children}
{ props.children }
</span>
)
),
}));
markdownDecorators.push({
@ -136,9 +142,9 @@ export function getScopedMDDecorators(scope: any): CompositeDecorator {
},
component: (props) => (
<a href="#" className="mx_MarkdownElement mx_Markdown_LINK">
{props.children}
{ props.children }
</a>
)
),
});
// markdownDecorators.push(emojiDecorator);
// TODO Consider renabling "syntax highlighting" when we can do it properly
@ -161,7 +167,7 @@ export function modifyText(contentState: ContentState, rangeToReplace: Selection
for (let currentKey = startKey;
currentKey && currentKey !== endKey;
currentKey = contentState.getKeyAfter(currentKey)) {
let blockText = getText(currentKey);
const blockText = getText(currentKey);
text += blockText.substring(startOffset, blockText.length);
// from now on, we'll take whole blocks
@ -182,7 +188,7 @@ export function modifyText(contentState: ContentState, rangeToReplace: Selection
export function selectionStateToTextOffsets(selectionState: SelectionState,
contentBlocks: Array<ContentBlock>): {start: number, end: number} {
let offset = 0, start = 0, end = 0;
for (let block of contentBlocks) {
for (const block of contentBlocks) {
if (selectionState.getStartKey() === block.getKey()) {
start = offset + selectionState.getStartOffset();
}
@ -259,7 +265,7 @@ export function attachImmutableEntitiesToEmoji(editorState: EditorState): Editor
.set('focusOffset', end);
const emojiText = plainText.substring(start, end);
newContentState = newContentState.createEntity(
'emoji', 'IMMUTABLE', { emojiUnicode: emojiText }
'emoji', 'IMMUTABLE', { emojiUnicode: emojiText },
);
const entityKey = newContentState.getLastCreatedEntityKey();
newContentState = Modifier.replaceText(

View File

@ -28,7 +28,7 @@ export function inviteToRoom(roomId, addr) {
if (addrType == 'email') {
return MatrixClientPeg.get().inviteByEmail(roomId, addr);
} else if (addrType == 'mx') {
} else if (addrType == 'mx-user-id') {
return MatrixClientPeg.get().invite(roomId, addr);
} else {
throw new Error('Unsupported address');
@ -50,8 +50,8 @@ export function inviteMultipleToRoom(roomId, addrs) {
}
export function showStartChatInviteDialog() {
const UserPickerDialog = sdk.getComponent("dialogs.UserPickerDialog");
Modal.createTrackedDialog('Start a chat', '', UserPickerDialog, {
const AddressPickerDialog = sdk.getComponent("dialogs.AddressPickerDialog");
Modal.createTrackedDialog('Start a chat', '', AddressPickerDialog, {
title: _t('Start a chat'),
description: _t("Who would you like to communicate with?"),
placeholder: _t("Email, name or matrix ID"),
@ -61,8 +61,8 @@ export function showStartChatInviteDialog() {
}
export function showRoomInviteDialog(roomId) {
const UserPickerDialog = sdk.getComponent("dialogs.UserPickerDialog");
Modal.createTrackedDialog('Chat Invite', '', UserPickerDialog, {
const AddressPickerDialog = sdk.getComponent("dialogs.AddressPickerDialog");
Modal.createTrackedDialog('Chat Invite', '', AddressPickerDialog, {
title: _t('Invite new room members'),
description: _t('Who would you like to add to this room?'),
button: _t('Send Invites'),

View File

@ -62,8 +62,7 @@ export function isConfCallRoom(room, me, conferenceHandler) {
export function looksLikeDirectMessageRoom(room, me) {
if (me.membership == "join" || me.membership === "ban" ||
(me.membership === "leave" && me.events.member.getSender() !== me.events.member.getStateKey()))
{
(me.membership === "leave" && me.events.member.getSender() !== me.events.member.getStateKey())) {
// Used to split rooms via tags
const tagNames = Object.keys(room.tags);
// Used for 1:1 direct chats

View File

@ -15,10 +15,10 @@ limitations under the License.
*/
import Promise from 'bluebird';
var request = require('browser-request');
const request = require('browser-request');
var SdkConfig = require('./SdkConfig');
var MatrixClientPeg = require('./MatrixClientPeg');
const SdkConfig = require('./SdkConfig');
const MatrixClientPeg = require('./MatrixClientPeg');
class ScalarAuthClient {
@ -38,7 +38,7 @@ class ScalarAuthClient {
// Returns a scalar_token string
getScalarToken() {
var tok = window.localStorage.getItem("mx_scalar_token");
const tok = window.localStorage.getItem("mx_scalar_token");
if (tok) return Promise.resolve(tok);
// No saved token, so do the dance to get one. First, we
@ -53,9 +53,9 @@ class ScalarAuthClient {
}
exchangeForScalarToken(openid_token_object) {
var defer = Promise.defer();
const defer = Promise.defer();
var scalar_rest_url = SdkConfig.get().integrations_rest_url;
const scalar_rest_url = SdkConfig.get().integrations_rest_url;
request({
method: 'POST',
uri: scalar_rest_url+'/register',
@ -77,7 +77,7 @@ class ScalarAuthClient {
}
getScalarInterfaceUrlForRoom(roomId, screen, id) {
var url = SdkConfig.get().integrations_ui_url;
let url = SdkConfig.get().integrations_ui_url;
url += "?scalar_token=" + encodeURIComponent(this.scalarToken);
url += "&room_id=" + encodeURIComponent(roomId);
if (id) {

View File

@ -356,12 +356,12 @@ function getWidgets(event, roomId) {
}
const stateEvents = room.currentState.getStateEvents("im.vector.modular.widgets");
// Only return widgets which have required fields
let widgetStateEvents = [];
const widgetStateEvents = [];
stateEvents.forEach((ev) => {
if (ev.getContent().type && ev.getContent().url) {
widgetStateEvents.push(ev.event); // return the raw event
}
})
});
sendResponse(event, widgetStateEvents);
}
@ -376,7 +376,7 @@ function setPlumbingState(event, roomId, status) {
sendError(event, _t('You need to be logged in.'));
return;
}
client.sendStateEvent(roomId, "m.room.plumbing", { status : status }).done(() => {
client.sendStateEvent(roomId, "m.room.plumbing", { status: status }).done(() => {
sendResponse(event, {
success: true,
});
@ -415,11 +415,11 @@ function setBotPower(event, roomId, userId, level) {
}
client.getStateEvent(roomId, "m.room.power_levels", "").then((powerLevels) => {
let powerEvent = new MatrixEvent(
const powerEvent = new MatrixEvent(
{
type: "m.room.power_levels",
content: powerLevels,
}
},
);
client.setPowerLevel(roomId, userId, level, powerEvent).done(() => {
@ -485,8 +485,7 @@ function canSendEvent(event, roomId) {
let canSend = false;
if (isState) {
canSend = room.currentState.maySendStateEvent(evType, me);
}
else {
} else {
canSend = room.currentState.maySendEvent(evType, me);
}
@ -517,8 +516,8 @@ function returnStateEvent(event, roomId, eventType, stateKey) {
sendResponse(event, stateEvent.getContent());
}
var currentRoomId = null;
var currentRoomAlias = null;
let currentRoomId = null;
let currentRoomAlias = null;
// Listen for when a room is viewed
dis.register(onAction);
@ -542,7 +541,7 @@ const onMessage = function(event) {
//
// All strings start with the empty string, so for sanity return if the length
// of the event origin is 0.
let url = SdkConfig.get().integrations_ui_url;
const url = SdkConfig.get().integrations_ui_url;
if (event.origin.length === 0 || !url.startsWith(event.origin) || !event.data.action) {
return; // don't log this - debugging APIs like to spam postMessage which floods the log otherwise
}
@ -647,7 +646,7 @@ module.exports = {
// Make an error so we get a stack trace
const e = new Error(
"ScalarMessaging: mismatched startListening / stopListening detected." +
" Negative count"
" Negative count",
);
console.error(e);
}

View File

@ -240,6 +240,59 @@ const commands = {
return reject(this.getUsage());
}),
ignore: new Command("ignore", "<userId>", function(roomId, args) {
if (args) {
const matches = args.match(/^(\S+)$/);
if (matches) {
const userId = matches[1];
const ignoredUsers = MatrixClientPeg.get().getIgnoredUsers();
ignoredUsers.push(userId); // de-duped internally in the js-sdk
return success(
MatrixClientPeg.get().setIgnoredUsers(ignoredUsers).then(() => {
const QuestionDialog = sdk.getComponent("dialogs.QuestionDialog");
Modal.createTrackedDialog('Slash Commands', 'User ignored', QuestionDialog, {
title: _t("Ignored user"),
description: (
<div>
<p>{ _t("You are now ignoring %(userId)s", {userId: userId}) }</p>
</div>
),
hasCancelButton: false,
});
}),
);
}
}
return reject(this.getUsage());
}),
unignore: new Command("unignore", "<userId>", function(roomId, args) {
if (args) {
const matches = args.match(/^(\S+)$/);
if (matches) {
const userId = matches[1];
const ignoredUsers = MatrixClientPeg.get().getIgnoredUsers();
const index = ignoredUsers.indexOf(userId);
if (index !== -1) ignoredUsers.splice(index, 1);
return success(
MatrixClientPeg.get().setIgnoredUsers(ignoredUsers).then(() => {
const QuestionDialog = sdk.getComponent("dialogs.QuestionDialog");
Modal.createTrackedDialog('Slash Commands', 'User unignored', QuestionDialog, {
title: _t("Unignored user"),
description: (
<div>
<p>{ _t("You are no longer ignoring %(userId)s", {userId: userId}) }</p>
</div>
),
hasCancelButton: false,
});
}),
);
}
}
return reject(this.getUsage());
}),
// Define the power level of a user
op: new Command("op", "<userId> [<power level>]", function(roomId, args) {
if (args) {
@ -292,6 +345,13 @@ const commands = {
return reject(this.getUsage());
}),
// Open developer tools
devtools: new Command("devtools", "", function(roomId) {
const DevtoolsDialog = sdk.getComponent("dialogs.DevtoolsDialog");
Modal.createDialog(DevtoolsDialog, { roomId });
return success();
}),
// Verify a user, device, and pubkey tuple
verify: new Command("verify", "<userId> <deviceId> <deviceSigningKey>", function(roomId, args) {
if (args) {

View File

@ -13,56 +13,67 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
import MatrixClientPeg from "./MatrixClientPeg";
import CallHandler from "./CallHandler";
import MatrixClientPeg from './MatrixClientPeg';
import CallHandler from './CallHandler';
import { _t } from './languageHandler';
import * as Roles from './Roles';
function textForMemberEvent(ev) {
// XXX: SYJS-16 "sender is sometimes null for join messages"
var senderName = ev.sender ? ev.sender.name : ev.getSender();
var targetName = ev.target ? ev.target.name : ev.getStateKey();
var ConferenceHandler = CallHandler.getConferenceHandler();
var reason = ev.getContent().reason ? (
_t('Reason') + ': ' + ev.getContent().reason
) : "";
switch (ev.getContent().membership) {
case 'invite':
var threePidContent = ev.getContent().third_party_invite;
const senderName = ev.sender ? ev.sender.name : ev.getSender();
const targetName = ev.target ? ev.target.name : ev.getStateKey();
const prevContent = ev.getPrevContent();
const content = ev.getContent();
const ConferenceHandler = CallHandler.getConferenceHandler();
const reason = content.reason ? (_t('Reason') + ': ' + content.reason) : '';
switch (content.membership) {
case 'invite': {
const threePidContent = content.third_party_invite;
if (threePidContent) {
if (threePidContent.display_name) {
return _t('%(targetName)s accepted the invitation for %(displayName)s.', {targetName: targetName, displayName: threePidContent.display_name});
return _t('%(targetName)s accepted the invitation for %(displayName)s.', {
targetName,
displayName: threePidContent.display_name,
});
} else {
return _t('%(targetName)s accepted an invitation.', {targetName: targetName});
return _t('%(targetName)s accepted an invitation.', {targetName});
}
}
else {
} else {
if (ConferenceHandler && ConferenceHandler.isConferenceUser(ev.getStateKey())) {
return _t('%(senderName)s requested a VoIP conference.', {senderName: senderName});
}
else {
return _t('%(senderName)s invited %(targetName)s.', {senderName: senderName, targetName: targetName});
return _t('%(senderName)s requested a VoIP conference.', {senderName});
} else {
return _t('%(senderName)s invited %(targetName)s.', {senderName, targetName});
}
}
}
case 'ban':
return _t(
'%(senderName)s banned %(targetName)s.',
{senderName: senderName, targetName: targetName}
) + ' ' + reason;
return _t('%(senderName)s banned %(targetName)s.', {senderName, targetName}) + ' ' + reason;
case 'join':
if (ev.getPrevContent() && ev.getPrevContent().membership == 'join') {
if (ev.getPrevContent().displayname && ev.getContent().displayname && ev.getPrevContent().displayname != ev.getContent().displayname) {
return _t('%(senderName)s changed their display name from %(oldDisplayName)s to %(displayName)s.', {senderName: ev.getSender(), oldDisplayName: ev.getPrevContent().displayname, displayName: ev.getContent().displayname});
} else if (!ev.getPrevContent().displayname && ev.getContent().displayname) {
return _t('%(senderName)s set their display name to %(displayName)s.', {senderName: ev.getSender(), displayName: ev.getContent().displayname});
} else if (ev.getPrevContent().displayname && !ev.getContent().displayname) {
return _t('%(senderName)s removed their display name (%(oldDisplayName)s).', {senderName: ev.getSender(), oldDisplayName: ev.getPrevContent().displayname});
} else if (ev.getPrevContent().avatar_url && !ev.getContent().avatar_url) {
return _t('%(senderName)s removed their profile picture.', {senderName: senderName});
} else if (ev.getPrevContent().avatar_url && ev.getContent().avatar_url && ev.getPrevContent().avatar_url != ev.getContent().avatar_url) {
return _t('%(senderName)s changed their profile picture.', {senderName: senderName});
} else if (!ev.getPrevContent().avatar_url && ev.getContent().avatar_url) {
return _t('%(senderName)s set a profile picture.', {senderName: senderName});
if (prevContent && prevContent.membership === 'join') {
if (prevContent.displayname && content.displayname && prevContent.displayname !== content.displayname) {
return _t('%(senderName)s changed their display name from %(oldDisplayName)s to %(displayName)s.', {
senderName,
oldDisplayName: prevContent.displayname,
displayName: content.displayname,
});
} else if (!prevContent.displayname && content.displayname) {
return _t('%(senderName)s set their display name to %(displayName)s.', {
senderName,
displayName: content.displayname,
});
} else if (prevContent.displayname && !content.displayname) {
return _t('%(senderName)s removed their display name (%(oldDisplayName)s).', {
senderName,
oldDisplayName: prevContent.displayname,
});
} else if (prevContent.avatar_url && !content.avatar_url) {
return _t('%(senderName)s removed their profile picture.', {senderName});
} else if (prevContent.avatar_url && content.avatar_url &&
prevContent.avatar_url !== content.avatar_url) {
return _t('%(senderName)s changed their profile picture.', {senderName});
} else if (!prevContent.avatar_url && content.avatar_url) {
return _t('%(senderName)s set a profile picture.', {senderName});
} else {
// suppress null rejoins
return '';
@ -71,73 +82,69 @@ function textForMemberEvent(ev) {
if (!ev.target) console.warn("Join message has no target! -- " + ev.getContent().state_key);
if (ConferenceHandler && ConferenceHandler.isConferenceUser(ev.getStateKey())) {
return _t('VoIP conference started.');
}
else {
return _t('%(targetName)s joined the room.', {targetName: targetName});
} else {
return _t('%(targetName)s joined the room.', {targetName});
}
}
case 'leave':
if (ev.getSender() === ev.getStateKey()) {
if (ConferenceHandler && ConferenceHandler.isConferenceUser(ev.getStateKey())) {
return _t('VoIP conference finished.');
} else if (prevContent.membership === "invite") {
return _t('%(targetName)s rejected the invitation.', {targetName});
} else {
return _t('%(targetName)s left the room.', {targetName});
}
else if (ev.getPrevContent().membership === "invite") {
return _t('%(targetName)s rejected the invitation.', {targetName: targetName});
}
else {
return _t('%(targetName)s left the room.', {targetName: targetName});
}
}
else if (ev.getPrevContent().membership === "ban") {
return _t('%(senderName)s unbanned %(targetName)s.', {senderName: senderName, targetName: targetName});
}
else if (ev.getPrevContent().membership === "join") {
return _t(
'%(senderName)s kicked %(targetName)s.',
{senderName: senderName, targetName: targetName}
) + ' ' + reason;
}
else if (ev.getPrevContent().membership === "invite") {
return _t(
'%(senderName)s withdrew %(targetName)s\'s invitation.',
{senderName: senderName, targetName: targetName}
) + ' ' + reason;
}
else {
return _t('%(targetName)s left the room.', {targetName: targetName});
} else if (prevContent.membership === "ban") {
return _t('%(senderName)s unbanned %(targetName)s.', {senderName, targetName});
} else if (prevContent.membership === "join") {
return _t('%(senderName)s kicked %(targetName)s.', {senderName, targetName}) + ' ' + reason;
} else if (prevContent.membership === "invite") {
return _t('%(senderName)s withdrew %(targetName)s\'s invitation.', {
senderName,
targetName,
}) + ' ' + reason;
} else {
return _t('%(targetName)s left the room.', {targetName});
}
}
}
function textForTopicEvent(ev) {
var senderDisplayName = ev.sender && ev.sender.name ? ev.sender.name : ev.getSender();
return _t('%(senderDisplayName)s changed the topic to "%(topic)s".', {senderDisplayName: senderDisplayName, topic: ev.getContent().topic});
const senderDisplayName = ev.sender && ev.sender.name ? ev.sender.name : ev.getSender();
return _t('%(senderDisplayName)s changed the topic to "%(topic)s".', {
senderDisplayName,
topic: ev.getContent().topic,
});
}
function textForRoomNameEvent(ev) {
var senderDisplayName = ev.sender && ev.sender.name ? ev.sender.name : ev.getSender();
const senderDisplayName = ev.sender && ev.sender.name ? ev.sender.name : ev.getSender();
if (!ev.getContent().name || ev.getContent().name.trim().length === 0) {
return _t('%(senderDisplayName)s removed the room name.', {senderDisplayName: senderDisplayName});
return _t('%(senderDisplayName)s removed the room name.', {senderDisplayName});
}
return _t('%(senderDisplayName)s changed the room name to %(roomName)s.', {senderDisplayName: senderDisplayName, roomName: ev.getContent().name});
return _t('%(senderDisplayName)s changed the room name to %(roomName)s.', {
senderDisplayName,
roomName: ev.getContent().name,
});
}
function textForMessageEvent(ev) {
var senderDisplayName = ev.sender && ev.sender.name ? ev.sender.name : ev.getSender();
var message = senderDisplayName + ': ' + ev.getContent().body;
const senderDisplayName = ev.sender && ev.sender.name ? ev.sender.name : ev.getSender();
let message = senderDisplayName + ': ' + ev.getContent().body;
if (ev.getContent().msgtype === "m.emote") {
message = "* " + senderDisplayName + " " + message;
} else if (ev.getContent().msgtype === "m.image") {
message = _t('%(senderDisplayName)s sent an image.', {senderDisplayName: senderDisplayName});
message = _t('%(senderDisplayName)s sent an image.', {senderDisplayName});
}
return message;
}
function textForCallAnswerEvent(event) {
var senderName = event.sender ? event.sender.name : _t('Someone');
var supported = MatrixClientPeg.get().supportsVoip() ? "" : _t('(not supported by this browser)');
return _t('%(senderName)s answered the call.', {senderName: senderName}) + ' ' + supported;
const senderName = event.sender ? event.sender.name : _t('Someone');
const supported = MatrixClientPeg.get().supportsVoip() ? '' : _t('(not supported by this browser)');
return _t('%(senderName)s answered the call.', {senderName}) + ' ' + supported;
}
function textForCallHangupEvent(event) {
@ -159,48 +166,52 @@ function textForCallHangupEvent(event) {
}
function textForCallInviteEvent(event) {
var senderName = event.sender ? event.sender.name : _t('Someone');
const senderName = event.sender ? event.sender.name : _t('Someone');
// FIXME: Find a better way to determine this from the event?
var type = "voice";
let callType = "voice";
if (event.getContent().offer && event.getContent().offer.sdp &&
event.getContent().offer.sdp.indexOf('m=video') !== -1) {
type = "video";
callType = "video";
}
var supported = MatrixClientPeg.get().supportsVoip() ? "" : _t('(not supported by this browser)');
return _t('%(senderName)s placed a %(callType)s call.', {senderName: senderName, callType: type}) + ' ' + supported;
const supported = MatrixClientPeg.get().supportsVoip() ? "" : _t('(not supported by this browser)');
return _t('%(senderName)s placed a %(callType)s call.', {senderName, callType}) + ' ' + supported;
}
function textForThreePidInviteEvent(event) {
var senderName = event.sender ? event.sender.name : event.getSender();
return _t('%(senderName)s sent an invitation to %(targetDisplayName)s to join the room.', {senderName: senderName, targetDisplayName: event.getContent().display_name});
const senderName = event.sender ? event.sender.name : event.getSender();
return _t('%(senderName)s sent an invitation to %(targetDisplayName)s to join the room.', {
senderName,
targetDisplayName: event.getContent().display_name,
});
}
function textForHistoryVisibilityEvent(event) {
var senderName = event.sender ? event.sender.name : event.getSender();
var vis = event.getContent().history_visibility;
// XXX: This i18n just isn't going to work for languages with different sentence structure.
var text = _t('%(senderName)s made future room history visible to', {senderName: senderName}) + ' ';
if (vis === "invited") {
text += _t('all room members, from the point they are invited') + '.';
const senderName = event.sender ? event.sender.name : event.getSender();
switch (event.getContent().history_visibility) {
case 'invited':
return _t('%(senderName)s made future room history visible to all room members, '
+ 'from the point they are invited.', {senderName});
case 'joined':
return _t('%(senderName)s made future room history visible to all room members, '
+ 'from the point they joined.', {senderName});
case 'shared':
return _t('%(senderName)s made future room history visible to all room members.', {senderName});
case 'world_readable':
return _t('%(senderName)s made future room history visible to anyone.', {senderName});
default:
return _t('%(senderName)s made future room history visible to unknown (%(visibility)s).', {
senderName,
visibility: event.getContent().history_visibility,
});
}
else if (vis === "joined") {
text += _t('all room members, from the point they joined') + '.';
}
else if (vis === "shared") {
text += _t('all room members') + '.';
}
else if (vis === "world_readable") {
text += _t('anyone') + '.';
}
else {
text += ' ' + _t('unknown') + ' (' + vis + ').';
}
return text;
}
function textForEncryptionEvent(event) {
var senderName = event.sender ? event.sender.name : event.getSender();
return _t('%(senderName)s turned on end-to-end encryption (algorithm %(algorithm)s).', {senderName: senderName, algorithm: event.getContent().algorithm});
const senderName = event.sender ? event.sender.name : event.getSender();
return _t('%(senderName)s turned on end-to-end encryption (algorithm %(algorithm)s).', {
senderName,
algorithm: event.getContent().algorithm,
});
}
// Currently will only display a change if a user's power level is changed
@ -211,18 +222,18 @@ function textForPowerEvent(event) {
}
const userDefault = event.getContent().users_default || 0;
// Construct set of userIds
let users = [];
const users = [];
Object.keys(event.getContent().users).forEach(
(userId) => {
if (users.indexOf(userId) === -1) users.push(userId);
}
},
);
Object.keys(event.getPrevContent().users).forEach(
(userId) => {
if (users.indexOf(userId) === -1) users.push(userId);
}
},
);
let diff = [];
const diff = [];
// XXX: This is also surely broken for i18n
users.forEach((userId) => {
// Previous power level
@ -231,11 +242,11 @@ function textForPowerEvent(event) {
const to = event.getContent().users[userId];
if (to !== from) {
diff.push(
_t('%(userId)s from %(fromPowerLevel)s to %(toPowerLevel)s', {
userId: userId,
_t('%(userId)s from %(fromPowerLevel)s to %(toPowerLevel)s', {
userId,
fromPowerLevel: Roles.textualPowerLevel(from, userDefault),
toPowerLevel: Roles.textualPowerLevel(to, userDefault)
})
toPowerLevel: Roles.textualPowerLevel(to, userDefault),
}),
);
}
});
@ -243,7 +254,7 @@ function textForPowerEvent(event) {
return '';
}
return _t('%(senderName)s changed the power level of %(powerLevelDiffText)s.', {
senderName: senderName,
senderName,
powerLevelDiffText: diff.join(", "),
});
}
@ -278,14 +289,17 @@ function textForWidgetEvent(event) {
}
}
var handlers = {
const handlers = {
'm.room.message': textForMessageEvent,
'm.room.name': textForRoomNameEvent,
'm.room.topic': textForTopicEvent,
'm.room.member': textForMemberEvent,
'm.call.invite': textForCallInviteEvent,
'm.call.answer': textForCallAnswerEvent,
'm.call.hangup': textForCallHangupEvent,
'm.call.invite': textForCallInviteEvent,
'm.call.answer': textForCallAnswerEvent,
'm.call.hangup': textForCallHangupEvent,
};
const stateHandlers = {
'm.room.name': textForRoomNameEvent,
'm.room.topic': textForTopicEvent,
'm.room.member': textForMemberEvent,
'm.room.third_party_invite': textForThreePidInviteEvent,
'm.room.history_visibility': textForHistoryVisibilityEvent,
'm.room.encryption': textForEncryptionEvent,
@ -296,8 +310,8 @@ var handlers = {
module.exports = {
textForEvent: function(ev) {
var hdlr = handlers[ev.getType()];
if (!hdlr) return "";
return hdlr(ev);
}
const handler = (ev.isState() ? stateHandlers : handlers)[ev.getType()];
if (handler) return handler(ev);
return '';
},
};

View File

@ -18,10 +18,10 @@ limitations under the License.
// module.exports otherwise this will break when included by both
// react-sdk and apps layered on top.
var DEBUG = 0;
const DEBUG = 0;
// The colour keys to be replaced as referred to in CSS
var keyRgb = [
const keyRgb = [
"rgb(118, 207, 166)", // Vector Green
"rgb(234, 245, 240)", // Vector Light Green
"rgb(211, 239, 225)", // BottomLeftMenu overlay (20% Vector Green)
@ -35,7 +35,7 @@ var keyRgb = [
// x = (255 - 234) / (255 - 118) = 0.16
// The colour keys to be replaced as referred to in SVGs
var keyHex = [
const keyHex = [
"#76CFA6", // Vector Green
"#EAF5F0", // Vector Light Green
"#D3EFE1", // BottomLeftMenu overlay (20% Vector Green overlaid on Vector Light Green)
@ -44,14 +44,14 @@ var keyHex = [
// cache of our replacement colours
// defaults to our keys.
var colors = [
const colors = [
keyHex[0],
keyHex[1],
keyHex[2],
keyHex[3],
];
var cssFixups = [
const cssFixups = [
// {
// style: a style object that should be fixed up taken from a stylesheet
// attr: name of the attribute to be clobbered, e.g. 'color'
@ -60,7 +60,7 @@ var cssFixups = [
];
// CSS attributes to be fixed up
var cssAttrs = [
const cssAttrs = [
"color",
"backgroundColor",
"borderColor",
@ -69,17 +69,17 @@ var cssAttrs = [
"borderLeftColor",
];
var svgAttrs = [
const svgAttrs = [
"fill",
"stroke",
];
var cached = false;
let cached = false;
function calcCssFixups() {
if (DEBUG) console.log("calcSvgFixups start");
for (var i = 0; i < document.styleSheets.length; i++) {
var ss = document.styleSheets[i];
for (let i = 0; i < document.styleSheets.length; i++) {
const ss = document.styleSheets[i];
if (!ss) continue; // well done safari >:(
// Chromium apparently sometimes returns null here; unsure why.
// see $14534907369972FRXBx:matrix.org in HQ
@ -104,12 +104,12 @@ function calcCssFixups() {
if (ss.href && !ss.href.match(/\/bundle.*\.css$/)) continue;
if (!ss.cssRules) continue;
for (var j = 0; j < ss.cssRules.length; j++) {
var rule = ss.cssRules[j];
for (let j = 0; j < ss.cssRules.length; j++) {
const rule = ss.cssRules[j];
if (!rule.style) continue;
for (var k = 0; k < cssAttrs.length; k++) {
var attr = cssAttrs[k];
for (var l = 0; l < keyRgb.length; l++) {
for (let k = 0; k < cssAttrs.length; k++) {
const attr = cssAttrs[k];
for (let l = 0; l < keyRgb.length; l++) {
if (rule.style[attr] === keyRgb[l]) {
cssFixups.push({
style: rule.style,
@ -126,8 +126,8 @@ function calcCssFixups() {
function applyCssFixups() {
if (DEBUG) console.log("applyCssFixups start");
for (var i = 0; i < cssFixups.length; i++) {
var cssFixup = cssFixups[i];
for (let i = 0; i < cssFixups.length; i++) {
const cssFixup = cssFixups[i];
cssFixup.style[cssFixup.attr] = colors[cssFixup.index];
}
if (DEBUG) console.log("applyCssFixups end");
@ -140,15 +140,15 @@ function hexToRgb(color) {
color[1] + color[1] +
color[2] + color[2];
}
var val = parseInt(color, 16);
var r = (val >> 16) & 255;
var g = (val >> 8) & 255;
var b = val & 255;
const val = parseInt(color, 16);
const r = (val >> 16) & 255;
const g = (val >> 8) & 255;
const b = val & 255;
return [r, g, b];
}
function rgbToHex(rgb) {
var val = (rgb[0] << 16) | (rgb[1] << 8) | rgb[2];
const val = (rgb[0] << 16) | (rgb[1] << 8) | rgb[2];
return '#' + (0x1000000 + val).toString(16).slice(1);
}
@ -167,12 +167,11 @@ module.exports = {
*
* @param {Function} tintable Function to call when the tint changes.
*/
registerTintable : function(tintable) {
registerTintable: function(tintable) {
tintables.push(tintable);
},
tint: function(primaryColor, secondaryColor, tertiaryColor) {
if (!cached) {
calcCssFixups();
cached = true;
@ -185,7 +184,7 @@ module.exports = {
if (!secondaryColor) {
const x = 0.16; // average weighting factor calculated from vector green & light green
var rgb = hexToRgb(primaryColor);
const rgb = hexToRgb(primaryColor);
rgb[0] = x * rgb[0] + (1 - x) * 255;
rgb[1] = x * rgb[1] + (1 - x) * 255;
rgb[2] = x * rgb[2] + (1 - x) * 255;
@ -194,8 +193,8 @@ module.exports = {
if (!tertiaryColor) {
const x = 0.19;
var rgb1 = hexToRgb(primaryColor);
var rgb2 = hexToRgb(secondaryColor);
const rgb1 = hexToRgb(primaryColor);
const rgb2 = hexToRgb(secondaryColor);
rgb1[0] = x * rgb1[0] + (1 - x) * rgb2[0];
rgb1[1] = x * rgb1[1] + (1 - x) * rgb2[1];
rgb1[2] = x * rgb1[2] + (1 - x) * rgb2[2];
@ -204,8 +203,7 @@ module.exports = {
if (colors[0] === primaryColor &&
colors[1] === secondaryColor &&
colors[2] === tertiaryColor)
{
colors[2] === tertiaryColor) {
return;
}
@ -248,14 +246,13 @@ module.exports = {
// key colour; cache the element and apply.
if (DEBUG) console.log("calcSvgFixups start for " + svgs);
var fixups = [];
for (var i = 0; i < svgs.length; i++) {
const fixups = [];
for (let i = 0; i < svgs.length; i++) {
var svgDoc;
try {
svgDoc = svgs[i].contentDocument;
}
catch(e) {
var msg = 'Failed to get svg.contentDocument of ' + svgs[i].toString();
} catch(e) {
let msg = 'Failed to get svg.contentDocument of ' + svgs[i].toString();
if (e.message) {
msg += e.message;
}
@ -265,12 +262,12 @@ module.exports = {
console.error(e);
}
if (!svgDoc) continue;
var tags = svgDoc.getElementsByTagName("*");
for (var j = 0; j < tags.length; j++) {
var tag = tags[j];
for (var k = 0; k < svgAttrs.length; k++) {
var attr = svgAttrs[k];
for (var l = 0; l < keyHex.length; l++) {
const tags = svgDoc.getElementsByTagName("*");
for (let j = 0; j < tags.length; j++) {
const tag = tags[j];
for (let k = 0; k < svgAttrs.length; k++) {
const attr = svgAttrs[k];
for (let l = 0; l < keyHex.length; l++) {
if (tag.getAttribute(attr) && tag.getAttribute(attr).toUpperCase() === keyHex[l]) {
fixups.push({
node: tag,
@ -289,10 +286,10 @@ module.exports = {
applySvgFixups: function(fixups) {
if (DEBUG) console.log("applySvgFixups start for " + fixups);
for (var i = 0; i < fixups.length; i++) {
var svgFixup = fixups[i];
for (let i = 0; i < fixups.length; i++) {
const svgFixup = fixups[i];
svgFixup.node.setAttribute(svgFixup.attr, colors[svgFixup.index]);
}
if (DEBUG) console.log("applySvgFixups end");
}
},
};

View File

@ -14,10 +14,10 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
var MatrixClientPeg = require('./MatrixClientPeg');
const MatrixClientPeg = require('./MatrixClientPeg');
import UserSettingsStore from './UserSettingsStore';
import shouldHideEvent from './shouldHideEvent';
var sdk = require('./index');
const sdk = require('./index');
module.exports = {
/**
@ -34,17 +34,17 @@ module.exports = {
} else if (ev.getType == 'm.room.message' && ev.getContent().msgtype == 'm.notify') {
return false;
}
var EventTile = sdk.getComponent('rooms.EventTile');
const EventTile = sdk.getComponent('rooms.EventTile');
return EventTile.haveTileForEvent(ev);
},
doesRoomHaveUnreadMessages: function(room) {
var myUserId = MatrixClientPeg.get().credentials.userId;
const myUserId = MatrixClientPeg.get().credentials.userId;
// get the most recent read receipt sent by our account.
// N.B. this is NOT a read marker (RM, aka "read up to marker"),
// despite the name of the method :((
var readUpToId = room.getEventReadUpTo(myUserId);
const readUpToId = room.getEventReadUpTo(myUserId);
// as we don't send RRs for our own messages, make sure we special case that
// if *we* sent the last message into the room, we consider it not unread!
@ -54,8 +54,7 @@ module.exports = {
// https://github.com/vector-im/riot-web/issues/3363
if (room.timeline.length &&
room.timeline[room.timeline.length - 1].sender &&
room.timeline[room.timeline.length - 1].sender.userId === myUserId)
{
room.timeline[room.timeline.length - 1].sender.userId === myUserId) {
return false;
}
@ -67,8 +66,8 @@ module.exports = {
const syncedSettings = UserSettingsStore.getSyncedSettings();
// Loop through messages, starting with the most recent...
for (var i = room.timeline.length - 1; i >= 0; --i) {
var ev = room.timeline[i];
for (let i = room.timeline.length - 1; i >= 0; --i) {
const ev = room.timeline[i];
if (ev.getId() == readUpToId) {
// If we've read up to this event, there's nothing more recents
@ -86,5 +85,5 @@ module.exports = {
// is unread on the theory that false positives are better than
// false negatives here.
return true;
}
},
};

View File

@ -16,11 +16,12 @@ limitations under the License.
const emailRegex = /^\S+@\S+\.\S+$/;
const mxidRegex = /^@\S+:\S+$/;
const mxUserIdRegex = /^@\S+:\S+$/;
const mxRoomIdRegex = /^!\S+:\S+$/;
import PropTypes from 'prop-types';
export const addressTypes = [
'mx', 'email',
'mx-user-id', 'mx-room-id', 'email',
];
// PropType definition for an object describing
@ -41,13 +42,16 @@ export const UserAddressType = PropTypes.shape({
export function getAddressType(inputText) {
const isEmailAddress = emailRegex.test(inputText);
const isMatrixId = mxidRegex.test(inputText);
const isUserId = mxUserIdRegex.test(inputText);
const isRoomId = mxRoomIdRegex.test(inputText);
// sanity check the input for user IDs
if (isEmailAddress) {
return 'email';
} else if (isMatrixId) {
return 'mx';
} else if (isUserId) {
return 'mx-user-id';
} else if (isRoomId) {
return 'mx-room-id';
} else {
return null;
}

View File

@ -33,11 +33,17 @@ export default {
// XXX: Always use default, ignore localStorage and remove from labs
override: true,
},
{
name: "-",
id: 'feature_groups',
default: false,
},
],
// horrible but it works. The locality makes this somewhat more palatable.
doTranslations: function() {
this.LABS_FEATURES[0].name = _t("Matrix Apps");
this.LABS_FEATURES[1].name = _t("Groups");
},
loadProfileInfo: function() {

View File

@ -1,6 +1,6 @@
var React = require('react');
var ReactDom = require('react-dom');
var Velocity = require('velocity-vector');
const React = require('react');
const ReactDom = require('react-dom');
const Velocity = require('velocity-vector');
/**
* The Velociraptor contains components and animates transitions with velocity.
@ -46,13 +46,13 @@ module.exports = React.createClass({
* update `this.children` according to the new list of children given
*/
_updateChildren: function(newChildren) {
var self = this;
var oldChildren = this.children || {};
const self = this;
const oldChildren = this.children || {};
this.children = {};
React.Children.toArray(newChildren).forEach(function(c) {
if (oldChildren[c.key]) {
var old = oldChildren[c.key];
var oldNode = ReactDom.findDOMNode(self.nodes[old.key]);
const old = oldChildren[c.key];
const oldNode = ReactDom.findDOMNode(self.nodes[old.key]);
if (oldNode && oldNode.style.left != c.props.style.left) {
Velocity(oldNode, { left: c.props.style.left }, self.props.transition).then(function() {
@ -71,18 +71,18 @@ module.exports = React.createClass({
} else {
// new element. If we have a startStyle, use that as the style and go through
// the enter animations
var newProps = {};
var restingStyle = c.props.style;
const newProps = {};
const restingStyle = c.props.style;
var startStyles = self.props.startStyles;
const startStyles = self.props.startStyles;
if (startStyles.length > 0) {
var startStyle = startStyles[0];
const startStyle = startStyles[0];
newProps.style = startStyle;
// console.log("mounted@startstyle0: "+JSON.stringify(startStyle));
}
newProps.ref = (n => self._collectNode(
c.key, n, restingStyle
newProps.ref = ((n) => self._collectNode(
c.key, n, restingStyle,
));
self.children[c.key] = React.cloneElement(c, newProps);
@ -103,8 +103,8 @@ module.exports = React.createClass({
this.nodes[k] === undefined &&
this.props.startStyles.length > 0
) {
var startStyles = this.props.startStyles;
var transitionOpts = this.props.enterTransitionOpts;
const startStyles = this.props.startStyles;
const transitionOpts = this.props.enterTransitionOpts;
const domNode = ReactDom.findDOMNode(node);
// start from startStyle 1: 0 is the one we gave it
// to start with, so now we animate 1 etc.
@ -154,7 +154,7 @@ module.exports = React.createClass({
render: function() {
return (
<span>
{Object.values(this.children)}
{ Object.values(this.children) }
</span>
);
},

View File

@ -1,9 +1,9 @@
var Velocity = require('velocity-vector');
const Velocity = require('velocity-vector');
// courtesy of https://github.com/julianshapiro/velocity/issues/283
// We only use easeOutBounce (easeInBounce is just sort of nonsensical)
function bounce( p ) {
var pow2,
let pow2,
bounce = 4;
while ( p < ( ( pow2 = Math.pow( 2, --bounce ) ) - 1 ) / 11 ) {

View File

@ -14,13 +14,19 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
var MatrixClientPeg = require("./MatrixClientPeg");
const MatrixClientPeg = require("./MatrixClientPeg");
import { _t } from './languageHandler';
module.exports = {
usersTypingApartFromMeAndIgnored: function(room) {
return this.usersTyping(
room, [MatrixClientPeg.get().credentials.userId].concat(MatrixClientPeg.get().getIgnoredUsers()),
);
},
usersTypingApartFromMe: function(room) {
return this.usersTyping(
room, [MatrixClientPeg.get().credentials.userId]
room, [MatrixClientPeg.get().credentials.userId],
);
},
@ -29,15 +35,15 @@ module.exports = {
* to exclude, return a list of user objects who are typing.
*/
usersTyping: function(room, exclude) {
var whoIsTyping = [];
const whoIsTyping = [];
if (exclude === undefined) {
exclude = [];
}
var memberKeys = Object.keys(room.currentState.members);
for (var i = 0; i < memberKeys.length; ++i) {
var userId = memberKeys[i];
const memberKeys = Object.keys(room.currentState.members);
for (let i = 0; i < memberKeys.length; ++i) {
const userId = memberKeys[i];
if (room.currentState.members[userId].typing) {
if (exclude.indexOf(userId) == -1) {
@ -70,5 +76,5 @@ module.exports = {
const lastPerson = names.pop();
return _t('%(names)s and %(lastPerson)s are typing', {names: names.join(', '), lastPerson: lastPerson});
}
}
},
};

View File

@ -14,10 +14,10 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
var React = require("react");
const React = require("react");
import { _t } from '../../../languageHandler';
var sdk = require('../../../index');
var MatrixClientPeg = require("../../../MatrixClientPeg");
const sdk = require('../../../index');
const MatrixClientPeg = require("../../../MatrixClientPeg");
module.exports = React.createClass({
displayName: 'EncryptedEventDialog',
@ -33,7 +33,7 @@ module.exports = React.createClass({
componentWillMount: function() {
this._unmounted = false;
var client = MatrixClientPeg.get();
const client = MatrixClientPeg.get();
// first try to load the device from our store.
//
@ -60,7 +60,7 @@ module.exports = React.createClass({
componentWillUnmount: function() {
this._unmounted = true;
var client = MatrixClientPeg.get();
const client = MatrixClientPeg.get();
if (client) {
client.removeListener("deviceVerificationChanged", this.onDeviceVerificationChanged);
}
@ -89,12 +89,12 @@ module.exports = React.createClass({
},
_renderDeviceInfo: function() {
var device = this.state.device;
const device = this.state.device;
if (!device) {
return (<i>{ _t('unknown device') }</i>);
}
var verificationStatus = (<b>{ _t('NOT verified') }</b>);
let verificationStatus = (<b>{ _t('NOT verified') }</b>);
if (device.isBlocked()) {
verificationStatus = (<b>{ _t('Blacklisted') }</b>);
} else if (device.isVerified()) {
@ -118,7 +118,7 @@ module.exports = React.createClass({
</tr>
<tr>
<td>{ _t('Ed25519 fingerprint') }</td>
<td><code>{device.getFingerprint()}</code></td>
<td><code>{ device.getFingerprint() }</code></td>
</tr>
</tbody>
</table>
@ -126,7 +126,7 @@ module.exports = React.createClass({
},
_renderEventInfo: function() {
var event = this.props.event;
const event = this.props.event;
return (
<table>
@ -165,36 +165,36 @@ module.exports = React.createClass({
},
render: function() {
var DeviceVerifyButtons = sdk.getComponent('elements.DeviceVerifyButtons');
const DeviceVerifyButtons = sdk.getComponent('elements.DeviceVerifyButtons');
var buttons = null;
let buttons = null;
if (this.state.device) {
buttons = (
<DeviceVerifyButtons device={ this.state.device }
userId={ this.props.event.getSender() }
<DeviceVerifyButtons device={this.state.device}
userId={this.props.event.getSender()}
/>
);
}
return (
<div className="mx_EncryptedEventDialog" onKeyDown={ this.onKeyDown }>
<div className="mx_EncryptedEventDialog" onKeyDown={this.onKeyDown}>
<div className="mx_Dialog_title">
{ _t('End-to-end encryption information') }
</div>
<div className="mx_Dialog_content">
<h4>{ _t('Event information') }</h4>
{this._renderEventInfo()}
{ this._renderEventInfo() }
<h4>{ _t('Sender device information') }</h4>
{this._renderDeviceInfo()}
{ this._renderDeviceInfo() }
</div>
<div className="mx_Dialog_buttons">
<button className="mx_Dialog_primary" onClick={ this.props.onFinished } autoFocus={ true }>
<button className="mx_Dialog_primary" onClick={this.props.onFinished} autoFocus={true}>
{ _t('OK') }
</button>
{buttons}
{ buttons }
</div>
</div>
);
}
},
});

View File

@ -136,13 +136,13 @@ export default React.createClass({
) }
</p>
<div className='error'>
{this.state.errStr}
{ this.state.errStr }
</div>
<div className='mx_E2eKeysDialog_inputTable'>
<div className='mx_E2eKeysDialog_inputRow'>
<div className='mx_E2eKeysDialog_inputLabel'>
<label htmlFor='passphrase1'>
{_t("Enter passphrase")}
{ _t("Enter passphrase") }
</label>
</div>
<div className='mx_E2eKeysDialog_inputCell'>
@ -155,7 +155,7 @@ export default React.createClass({
<div className='mx_E2eKeysDialog_inputRow'>
<div className='mx_E2eKeysDialog_inputLabel'>
<label htmlFor='passphrase2'>
{_t("Confirm passphrase")}
{ _t("Confirm passphrase") }
</label>
</div>
<div className='mx_E2eKeysDialog_inputCell'>
@ -172,7 +172,7 @@ export default React.createClass({
disabled={disableForm}
/>
<button onClick={this._onCancelClick} disabled={disableForm}>
{_t("Cancel")}
{ _t("Cancel") }
</button>
</div>
</form>

View File

@ -134,13 +134,13 @@ export default React.createClass({
) }
</p>
<div className='error'>
{this.state.errStr}
{ this.state.errStr }
</div>
<div className='mx_E2eKeysDialog_inputTable'>
<div className='mx_E2eKeysDialog_inputRow'>
<div className='mx_E2eKeysDialog_inputLabel'>
<label htmlFor='importFile'>
{_t("File to import")}
{ _t("File to import") }
</label>
</div>
<div className='mx_E2eKeysDialog_inputCell'>
@ -153,14 +153,14 @@ export default React.createClass({
<div className='mx_E2eKeysDialog_inputRow'>
<div className='mx_E2eKeysDialog_inputLabel'>
<label htmlFor='passphrase'>
{_t("Enter passphrase")}
{ _t("Enter passphrase") }
</label>
</div>
<div className='mx_E2eKeysDialog_inputCell'>
<input ref='passphrase' id='passphrase'
size='64' type='password'
onChange={this._onFormChange}
disabled={disableForm}/>
disabled={disableForm} />
</div>
</div>
</div>
@ -170,7 +170,7 @@ export default React.createClass({
disabled={!this.state.enableSubmit || disableForm}
/>
<button onClick={this._onCancelClick} disabled={disableForm}>
{_t("Cancel")}
{ _t("Cancel") }
</button>
</div>
</form>

View File

@ -45,7 +45,7 @@ const PROVIDERS = [
EmojiProvider,
CommandProvider,
DuckDuckGoProvider,
].map(completer => completer.getInstance());
].map((completer) => completer.getInstance());
// Providers will get rejected if they take longer than this.
const PROVIDER_COMPLETION_TIMEOUT = 3000;

View File

@ -16,7 +16,7 @@ limitations under the License.
*/
import React from 'react';
import { _t } from '../languageHandler';
import { _t, _td } from '../languageHandler';
import AutocompleteProvider from './AutocompleteProvider';
import FuzzyMatcher from './FuzzyMatcher';
import {TextualCompletion} from './Components';
@ -27,72 +27,82 @@ const COMMANDS = [
{
command: '/me',
args: '<message>',
description: 'Displays action',
description: _td('Displays action'),
},
{
command: '/ban',
args: '<user-id> [reason]',
description: 'Bans user with given id',
description: _td('Bans user with given id'),
},
{
command: '/unban',
args: '<user-id>',
description: 'Unbans user with given id',
description: _td('Unbans user with given id'),
},
{
command: '/op',
args: '<user-id> [<power-level>]',
description: 'Define the power level of a user',
description: _td('Define the power level of a user'),
},
{
command: '/deop',
args: '<user-id>',
description: 'Deops user with given id',
description: _td('Deops user with given id'),
},
{
command: '/invite',
args: '<user-id>',
description: 'Invites user with given id to current room',
description: _td('Invites user with given id to current room'),
},
{
command: '/join',
args: '<room-alias>',
description: 'Joins room with given alias',
description: _td('Joins room with given alias'),
},
{
command: '/part',
args: '[<room-alias>]',
description: 'Leave room',
description: _td('Leave room'),
},
{
command: '/topic',
args: '<topic>',
description: 'Sets the room topic',
description: _td('Sets the room topic'),
},
{
command: '/kick',
args: '<user-id> [reason]',
description: 'Kicks user with given id',
description: _td('Kicks user with given id'),
},
{
command: '/nick',
args: '<display-name>',
description: 'Changes your display nickname',
description: _td('Changes your display nickname'),
},
{
command: '/ddg',
args: '<query>',
description: 'Searches DuckDuckGo for results',
description: _td('Searches DuckDuckGo for results'),
},
{
command: '/tint',
args: '<color1> [<color2>]',
description: 'Changes colour scheme of current room',
description: _td('Changes colour scheme of current room'),
},
{
command: '/verify',
args: '<user-id> <device-id> <device-signing-key>',
description: 'Verifies a user, device, and pubkey tuple',
description: _td('Verifies a user, device, and pubkey tuple'),
},
{
command: '/ignore',
args: '<user-id>',
description: _td('Ignores a user, hiding their messages from you'),
},
{
command: '/unignore',
args: '<user-id>',
description: _td('Stops ignoring a user, showing their messages going forward'),
},
// Omitting `/markdown` as it only seems to apply to OldComposer
];
@ -119,7 +129,7 @@ export default class CommandProvider extends AutocompleteProvider {
component: (<TextualCompletion
title={result.command}
subtitle={result.args}
description={ _t(result.description) }
description={_t(result.description)}
/>),
range,
};
@ -140,7 +150,7 @@ export default class CommandProvider extends AutocompleteProvider {
renderCompletions(completions: [React.Component]): ?React.Component {
return <div className="mx_Autocomplete_Completion_container_block">
{completions}
{ completions }
</div>;
}
}

View File

@ -30,13 +30,13 @@ export class TextualCompletion extends React.Component {
subtitle,
description,
className,
...restProps,
...restProps
} = this.props;
return (
<div className={classNames('mx_Autocomplete_Completion_block', className)} {...restProps}>
<span className="mx_Autocomplete_Completion_title">{title}</span>
<span className="mx_Autocomplete_Completion_subtitle">{subtitle}</span>
<span className="mx_Autocomplete_Completion_description">{description}</span>
<span className="mx_Autocomplete_Completion_title">{ title }</span>
<span className="mx_Autocomplete_Completion_subtitle">{ subtitle }</span>
<span className="mx_Autocomplete_Completion_description">{ description }</span>
</div>
);
}
@ -56,14 +56,14 @@ export class PillCompletion extends React.Component {
description,
initialComponent,
className,
...restProps,
...restProps
} = this.props;
return (
<div className={classNames('mx_Autocomplete_Completion_pill', className)} {...restProps}>
{initialComponent}
<span className="mx_Autocomplete_Completion_title">{title}</span>
<span className="mx_Autocomplete_Completion_subtitle">{subtitle}</span>
<span className="mx_Autocomplete_Completion_description">{description}</span>
{ initialComponent }
<span className="mx_Autocomplete_Completion_title">{ title }</span>
<span className="mx_Autocomplete_Completion_subtitle">{ subtitle }</span>
<span className="mx_Autocomplete_Completion_description">{ description }</span>
</div>
);
}

View File

@ -38,7 +38,7 @@ export default class DuckDuckGoProvider extends AutocompleteProvider {
}
async getCompletions(query: string, selection: {start: number, end: number}) {
let {command, range} = this.getCurrentCommand(query, selection);
const {command, range} = this.getCurrentCommand(query, selection);
if (!query || !command) {
return [];
}
@ -47,7 +47,7 @@ export default class DuckDuckGoProvider extends AutocompleteProvider {
method: 'GET',
});
const json = await response.json();
let results = json.Results.map(result => {
const results = json.Results.map((result) => {
return {
completion: result.Text,
component: (
@ -105,7 +105,7 @@ export default class DuckDuckGoProvider extends AutocompleteProvider {
renderCompletions(completions: [React.Component]): ?React.Component {
return <div className="mx_Autocomplete_Completion_container_block">
{completions}
{ completions }
</div>;
}
}

View File

@ -25,6 +25,7 @@ import {PillCompletion} from './Components';
import type {SelectionRange, Completion} from './Autocompleter';
import _uniq from 'lodash/uniq';
import _sortBy from 'lodash/sortBy';
import UserSettingsStore from '../UserSettingsStore';
import EmojiData from '../stripped-emoji.json';
@ -96,6 +97,10 @@ export default class EmojiProvider extends AutocompleteProvider {
}
async getCompletions(query: string, selection: SelectionRange) {
if (UserSettingsStore.getSyncedSetting("MessageComposerInput.dontSuggestEmoji")) {
return []; // don't give any suggestions if the user doesn't want them
}
const EmojiText = sdk.getComponent('views.elements.EmojiText');
let completions = [];
@ -133,7 +138,7 @@ export default class EmojiProvider extends AutocompleteProvider {
return {
completion: unicode,
component: (
<PillCompletion title={shortname} initialComponent={<EmojiText style={{maxWidth: '1em'}}>{unicode}</EmojiText>} />
<PillCompletion title={shortname} initialComponent={<EmojiText style={{maxWidth: '1em'}}>{ unicode }</EmojiText>} />
),
range,
};
@ -147,14 +152,13 @@ export default class EmojiProvider extends AutocompleteProvider {
}
static getInstance() {
if (instance == null)
{instance = new EmojiProvider();}
if (instance == null) {instance = new EmojiProvider();}
return instance;
}
renderCompletions(completions: [React.Component]): ?React.Component {
return <div className="mx_Autocomplete_Completion_container_pill">
{completions}
{ completions }
</div>;
}
}

View File

@ -106,7 +106,7 @@ export default class RoomProvider extends AutocompleteProvider {
renderCompletions(completions: [React.Component]): ?React.Component {
return <div className="mx_Autocomplete_Completion_container_pill mx_Autocomplete_Completion_container_truncate">
{completions}
{ completions }
</div>;
}
}

View File

@ -33,7 +33,8 @@ const USER_REGEX = /@\S*/g;
let instance = null;
export default class UserProvider extends AutocompleteProvider {
users: Array<RoomMember> = [];
users: Array<RoomMember> = null;
room: Room = null;
constructor() {
super(USER_REGEX, {
@ -54,8 +55,11 @@ export default class UserProvider extends AutocompleteProvider {
return [];
}
// lazy-load user list into matcher
if (this.users === null) this._makeUsers();
let completions = [];
let {command, range} = this.getCurrentCommand(query, selection, force);
const {command, range} = this.getCurrentCommand(query, selection, force);
if (command) {
completions = this.matcher.match(command[0]).map((user) => {
const displayName = (user.name || user.userId || '').replace(' (IRC)', ''); // FIXME when groups are done
@ -67,7 +71,7 @@ export default class UserProvider extends AutocompleteProvider {
href: 'https://matrix.to/#/' + user.userId,
component: (
<PillCompletion
initialComponent={<MemberAvatar member={user} width={24} height={24}/>}
initialComponent={<MemberAvatar member={user} width={24} height={24} />}
title={displayName}
description={user.userId} />
),
@ -83,7 +87,12 @@ export default class UserProvider extends AutocompleteProvider {
}
setUserListFromRoom(room: Room) {
const events = room.getLiveTimeline().getEvents();
this.room = room;
this.users = null;
}
_makeUsers() {
const events = this.room.getLiveTimeline().getEvents();
const lastSpoken = {};
for(const event of events) {
@ -91,7 +100,7 @@ export default class UserProvider extends AutocompleteProvider {
}
const currentUserId = MatrixClientPeg.get().credentials.userId;
this.users = room.getJoinedMembers().filter((member) => {
this.users = this.room.getJoinedMembers().filter((member) => {
if (member.userId !== currentUserId) return true;
});
@ -103,7 +112,8 @@ export default class UserProvider extends AutocompleteProvider {
}
onUserSpoke(user: RoomMember) {
if(user.userId === MatrixClientPeg.get().credentials.userId) return;
if (this.users === null) return;
if (user.userId === MatrixClientPeg.get().credentials.userId) return;
// Move the user that spoke to the front of the array
this.users.splice(
@ -122,7 +132,7 @@ export default class UserProvider extends AutocompleteProvider {
renderCompletions(completions: [React.Component]): ?React.Component {
return <div className="mx_Autocomplete_Completion_container_pill mx_Autocomplete_Completion_container_truncate">
{completions}
{ completions }
</div>;
}

View File

@ -17,9 +17,9 @@ limitations under the License.
'use strict';
var classNames = require('classnames');
var React = require('react');
var ReactDOM = require('react-dom');
const classNames = require('classnames');
const React = require('react');
const ReactDOM = require('react-dom');
// Shamelessly ripped off Modal.js. There's probably a better way
// of doing reusable widgets like dialog boxes & menus where we go and
@ -36,7 +36,7 @@ module.exports = {
},
getOrCreateContainer: function() {
var container = document.getElementById(this.ContextualMenuContainerId);
let container = document.getElementById(this.ContextualMenuContainerId);
if (!container) {
container = document.createElement("div");
@ -48,9 +48,9 @@ module.exports = {
},
createMenu: function(Element, props) {
var self = this;
const self = this;
var closeMenu = function() {
const closeMenu = function() {
ReactDOM.unmountComponentAtNode(self.getOrCreateContainer());
if (props && props.onFinished) {
@ -58,17 +58,17 @@ module.exports = {
}
};
var position = {
const position = {
top: props.top,
};
var chevronOffset = {};
const chevronOffset = {};
if (props.chevronOffset) {
chevronOffset.top = props.chevronOffset;
}
// To override the default chevron colour, if it's been set
var chevronCSS = "";
let chevronCSS = "";
if (props.menuColour) {
chevronCSS = `
.mx_ContextualMenu_chevron_left:after {
@ -81,7 +81,7 @@ module.exports = {
`;
}
var chevron = null;
let chevron = null;
if (props.left) {
chevron = <div style={chevronOffset} className="mx_ContextualMenu_chevron_left"></div>;
position.left = props.left;
@ -90,15 +90,15 @@ module.exports = {
position.right = props.right;
}
var className = 'mx_ContextualMenu_wrapper';
const className = 'mx_ContextualMenu_wrapper';
var menuClasses = classNames({
const menuClasses = classNames({
'mx_ContextualMenu': true,
'mx_ContextualMenu_left': props.left,
'mx_ContextualMenu_right': !props.left,
});
var menuStyle = {};
const menuStyle = {};
if (props.menuWidth) {
menuStyle.width = props.menuWidth;
}
@ -113,14 +113,14 @@ module.exports = {
// FIXME: If a menu uses getDefaultProps it clobbers the onFinished
// property set here so you can't close the menu from a button click!
var menu = (
const menu = (
<div className={className} style={position}>
<div className={menuClasses} style={menuStyle}>
{chevron}
<Element {...props} onFinished={closeMenu}/>
{ chevron }
<Element {...props} onFinished={closeMenu} />
</div>
<div className="mx_ContextualMenu_background" onClick={closeMenu}></div>
<style>{chevronCSS}</style>
<style>{ chevronCSS }</style>
</div>
);

View File

@ -61,7 +61,7 @@ module.exports = React.createClass({
},
onCreateRoom: function() {
var options = {};
const options = {};
if (this.state.room_name) {
options.name = this.state.room_name;
@ -79,14 +79,14 @@ module.exports = React.createClass({
{
type: "m.room.join_rules",
content: {
"join_rule": this.state.is_private ? "invite" : "public"
}
"join_rule": this.state.is_private ? "invite" : "public",
},
},
{
type: "m.room.history_visibility",
content: {
"history_visibility": this.state.share_history ? "shared" : "invited"
}
"history_visibility": this.state.share_history ? "shared" : "invited",
},
},
];
}
@ -94,19 +94,19 @@ module.exports = React.createClass({
options.invite = this.state.invited_users;
var alias = this.getAliasLocalpart();
const alias = this.getAliasLocalpart();
if (alias) {
options.room_alias_name = alias;
}
var cli = MatrixClientPeg.get();
const cli = MatrixClientPeg.get();
if (!cli) {
// TODO: Error.
console.error("Cannot create room: No matrix client.");
return;
}
var deferred = cli.createRoom(options);
const deferred = cli.createRoom(options);
if (this.state.encrypt) {
// TODO
@ -116,7 +116,7 @@ module.exports = React.createClass({
phase: this.phases.CREATING,
});
var self = this;
const self = this;
deferred.then(function(resp) {
self.setState({
@ -209,7 +209,7 @@ module.exports = React.createClass({
onAliasChanged: function(alias) {
this.setState({
alias: alias
alias: alias,
});
},
@ -220,64 +220,64 @@ module.exports = React.createClass({
},
render: function() {
var curr_phase = this.state.phase;
const curr_phase = this.state.phase;
if (curr_phase == this.phases.CREATING) {
var Loader = sdk.getComponent("elements.Spinner");
const Loader = sdk.getComponent("elements.Spinner");
return (
<Loader/>
<Loader />
);
} else {
var error_box = "";
let error_box = "";
if (curr_phase == this.phases.ERROR) {
error_box = (
<div className="mx_Error">
{_t('An error occurred: %(error_string)s', {error_string: this.state.error_string})}
{ _t('An error occurred: %(error_string)s', {error_string: this.state.error_string}) }
</div>
);
}
var CreateRoomButton = sdk.getComponent("create_room.CreateRoomButton");
var RoomAlias = sdk.getComponent("create_room.RoomAlias");
var Presets = sdk.getComponent("create_room.Presets");
var UserSelector = sdk.getComponent("elements.UserSelector");
var SimpleRoomHeader = sdk.getComponent("rooms.SimpleRoomHeader");
const CreateRoomButton = sdk.getComponent("create_room.CreateRoomButton");
const RoomAlias = sdk.getComponent("create_room.RoomAlias");
const Presets = sdk.getComponent("create_room.Presets");
const UserSelector = sdk.getComponent("elements.UserSelector");
const SimpleRoomHeader = sdk.getComponent("rooms.SimpleRoomHeader");
var domain = MatrixClientPeg.get().getDomain();
const domain = MatrixClientPeg.get().getDomain();
return (
<div className="mx_CreateRoom">
<SimpleRoomHeader title={_t("Create Room")} collapsedRhs={ this.props.collapsedRhs }/>
<SimpleRoomHeader title={_t("Create Room")} collapsedRhs={this.props.collapsedRhs} />
<div className="mx_CreateRoom_body">
<input type="text" ref="room_name" value={this.state.room_name} onChange={this.onNameChange} placeholder={_t('Name')}/> <br />
<textarea className="mx_CreateRoom_description" ref="topic" value={this.state.topic} onChange={this.onTopicChange} placeholder={_t('Topic')}/> <br />
<RoomAlias ref="alias" alias={this.state.alias} homeserver={ domain } onChange={this.onAliasChanged}/> <br />
<UserSelector ref="user_selector" selected_users={this.state.invited_users} onChange={this.onInviteChanged}/> <br />
<Presets ref="presets" onChange={this.onPresetChanged} preset={this.state.preset}/> <br />
<input type="text" ref="room_name" value={this.state.room_name} onChange={this.onNameChange} placeholder={_t('Name')} /> <br />
<textarea className="mx_CreateRoom_description" ref="topic" value={this.state.topic} onChange={this.onTopicChange} placeholder={_t('Topic')} /> <br />
<RoomAlias ref="alias" alias={this.state.alias} homeserver={domain} onChange={this.onAliasChanged} /> <br />
<UserSelector ref="user_selector" selected_users={this.state.invited_users} onChange={this.onInviteChanged} /> <br />
<Presets ref="presets" onChange={this.onPresetChanged} preset={this.state.preset} /> <br />
<div>
<label>
<input type="checkbox" ref="is_private" checked={this.state.is_private} onChange={this.onPrivateChanged}/>
{_t('Make this room private')}
<input type="checkbox" ref="is_private" checked={this.state.is_private} onChange={this.onPrivateChanged} />
{ _t('Make this room private') }
</label>
</div>
<div>
<label>
<input type="checkbox" ref="share_history" checked={this.state.share_history} onChange={this.onShareHistoryChanged}/>
{_t('Share message history with new users')}
<input type="checkbox" ref="share_history" checked={this.state.share_history} onChange={this.onShareHistoryChanged} />
{ _t('Share message history with new users') }
</label>
</div>
<div className="mx_CreateRoom_encrypt">
<label>
<input type="checkbox" ref="encrypt" checked={this.state.encrypt} onChange={this.onEncryptChanged}/>
{_t('Encrypt room')}
<input type="checkbox" ref="encrypt" checked={this.state.encrypt} onChange={this.onEncryptChanged} />
{ _t('Encrypt room') }
</label>
</div>
<div>
<CreateRoomButton onCreateRoom={this.onCreateRoom} /> <br />
</div>
{error_box}
{ error_box }
</div>
</div>
);
}
}
},
});

View File

@ -24,7 +24,7 @@ import { _t, _tJsx } from '../../languageHandler';
/*
* Component which shows the filtered file using a TimelinePanel
*/
var FilePanel = React.createClass({
const FilePanel = React.createClass({
displayName: 'FilePanel',
propTypes: {
@ -55,33 +55,33 @@ var FilePanel = React.createClass({
},
updateTimelineSet: function(roomId) {
var client = MatrixClientPeg.get();
var room = client.getRoom(roomId);
const client = MatrixClientPeg.get();
const room = client.getRoom(roomId);
this.noRoom = !room;
if (room) {
var filter = new Matrix.Filter(client.credentials.userId);
const filter = new Matrix.Filter(client.credentials.userId);
filter.setDefinition(
{
"room": {
"timeline": {
"contains_url": true
"contains_url": true,
},
}
}
},
},
);
// FIXME: we shouldn't be doing this every time we change room - see comment above.
client.getOrCreateFilter("FILTER_FILES_" + client.credentials.userId, filter).then(
(filterId)=>{
filter.filterId = filterId;
var timelineSet = room.getOrCreateFilteredTimelineSet(filter);
const timelineSet = room.getOrCreateFilteredTimelineSet(filter);
this.setState({ timelineSet: timelineSet });
},
(error)=>{
console.error("Failed to get or create file panel filter", error);
}
},
);
} else {
console.error("Failed to add filtered timelineSet for FilePanel as no room!");
@ -92,18 +92,18 @@ var FilePanel = React.createClass({
if (MatrixClientPeg.get().isGuest()) {
return <div className="mx_FilePanel mx_RoomView_messageListWrapper">
<div className="mx_RoomView_empty">
{_tJsx("You must <a>register</a> to use this functionality", /<a>(.*?)<\/a>/, (sub) => <a href="#/register" key="sub">{sub}</a>)}
{ _tJsx("You must <a>register</a> to use this functionality", /<a>(.*?)<\/a>/, (sub) => <a href="#/register" key="sub">{ sub }</a>) }
</div>
</div>;
} else if (this.noRoom) {
return <div className="mx_FilePanel mx_RoomView_messageListWrapper">
<div className="mx_RoomView_empty">{_t("You must join the room to see its files")}</div>
<div className="mx_RoomView_empty">{ _t("You must join the room to see its files") }</div>
</div>;
}
// wrap a TimelinePanel with the jump-to-event bits turned off.
var TimelinePanel = sdk.getComponent("structures.TimelinePanel");
var Loader = sdk.getComponent("elements.Spinner");
const TimelinePanel = sdk.getComponent("structures.TimelinePanel");
const Loader = sdk.getComponent("elements.Spinner");
if (this.state.timelineSet) {
// console.log("rendering TimelinePanel for timelineSet " + this.state.timelineSet.room.roomId + " " +
@ -114,17 +114,16 @@ var FilePanel = React.createClass({
manageReadReceipts={false}
manageReadMarkers={false}
timelineSet={this.state.timelineSet}
showUrlPreview = { false }
showUrlPreview = {false}
tileShape="file_grid"
opacity={ this.props.opacity }
opacity={this.props.opacity}
empty={_t('There are no visible files in this room')}
/>
);
}
else {
} else {
return (
<div className="mx_FilePanel">
<Loader/>
<Loader />
</div>
);
}

View File

@ -1,5 +1,6 @@
/*
Copyright 2017 Vector Creations Ltd.
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.
@ -16,6 +17,7 @@ limitations under the License.
import React from 'react';
import PropTypes from 'prop-types';
import Promise from 'bluebird';
import MatrixClientPeg from '../../MatrixClientPeg';
import sdk from '../../index';
import dis from '../../dispatcher';
@ -25,6 +27,9 @@ import AccessibleButton from '../views/elements/AccessibleButton';
import Modal from '../../Modal';
import classnames from 'classnames';
import GroupStoreCache from '../../stores/GroupStoreCache';
import GroupStore from '../../stores/GroupStore';
const RoomSummaryType = PropTypes.shape({
room_id: PropTypes.string.isRequired,
profile: PropTypes.shape({
@ -37,6 +42,9 @@ const RoomSummaryType = PropTypes.shape({
const UserSummaryType = PropTypes.shape({
summaryInfo: PropTypes.shape({
user_id: PropTypes.string.isRequired,
role_id: PropTypes.string,
avatar_url: PropTypes.string,
displayname: PropTypes.string,
}).isRequired,
});
@ -50,19 +58,79 @@ const CategoryRoomList = React.createClass({
name: PropTypes.string,
}).isRequired,
}),
groupId: PropTypes.string.isRequired,
// Whether the list should be editable
editing: PropTypes.bool.isRequired,
},
onAddRoomsClicked: function(ev) {
ev.preventDefault();
const AddressPickerDialog = sdk.getComponent("dialogs.AddressPickerDialog");
Modal.createTrackedDialog('Add Rooms to Group Summary', '', AddressPickerDialog, {
title: _t('Add rooms to the group summary'),
description: _t("Which rooms would you like to add to this summary?"),
placeholder: _t("Room name or alias"),
button: _t("Add to summary"),
pickerType: 'room',
validAddressTypes: ['mx-room-id'],
groupId: this.props.groupId,
onFinished: (success, addrs) => {
if (!success) return;
const errorList = [];
Promise.all(addrs.map((addr) => {
return this.context.groupStore
.addRoomToGroupSummary(addr.address)
.catch(() => { errorList.push(addr.address); })
.reflect();
})).then(() => {
if (errorList.length === 0) {
return;
}
const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
Modal.createTrackedDialog(
'Failed to add the following room to the group summary',
'', ErrorDialog,
{
title: _t(
"Failed to add the following rooms to the summary of %(groupId)s:",
{groupId: this.props.groupId},
),
description: errorList.join(", "),
});
});
},
});
},
render: function() {
const TintableSvg = sdk.getComponent("elements.TintableSvg");
const addButton = this.props.editing ?
(<AccessibleButton className="mx_GroupView_featuredThings_addButton" onClick={this.onAddRoomsClicked}>
<TintableSvg src="img/icons-create-room.svg" width="64" height="64" />
<div className="mx_GroupView_featuredThings_addButton_label">
{ _t('Add a Room') }
</div>
</AccessibleButton>) : <div />;
const roomNodes = this.props.rooms.map((r) => {
return <FeaturedRoom key={r.room_id} summaryInfo={r} />;
return <FeaturedRoom
key={r.room_id}
groupId={this.props.groupId}
editing={this.props.editing}
summaryInfo={r} />;
});
let catHeader = null;
let catHeader = <div />;
if (this.props.category && this.props.category.profile) {
catHeader = <div className="mx_GroupView_featuredThings_category">{this.props.category.profile.name}</div>;
catHeader = <div className="mx_GroupView_featuredThings_category">
{ this.props.category.profile.name }
</div>;
}
return <div>
{catHeader}
{roomNodes}
return <div className="mx_GroupView_featuredThings_container">
{ catHeader }
{ roomNodes }
{ addButton }
</div>;
},
});
@ -72,6 +140,8 @@ const FeaturedRoom = React.createClass({
props: {
summaryInfo: RoomSummaryType.isRequired,
editing: PropTypes.bool.isRequired,
groupId: PropTypes.string.isRequired,
},
onClick: function(e) {
@ -85,28 +155,69 @@ const FeaturedRoom = React.createClass({
});
},
onDeleteClicked: function(e) {
e.preventDefault();
e.stopPropagation();
this.context.groupStore.removeRoomFromGroupSummary(
this.props.summaryInfo.room_id,
).catch((err) => {
console.error('Error whilst removing room from group summary', err);
const roomName = this.props.summaryInfo.name ||
this.props.summaryInfo.canonical_alias ||
this.props.summaryInfo.room_id;
const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
Modal.createTrackedDialog(
'Failed to remove room from group summary',
'', ErrorDialog,
{
title: _t(
"Failed to remove the room from the summary of %(groupId)s",
{groupId: this.props.groupId},
),
description: _t("The room '%(roomName)s' could not be removed from the summary.", {roomName}),
});
});
},
render: function() {
const RoomAvatar = sdk.getComponent("avatars.RoomAvatar");
const roomName = this.props.summaryInfo.profile.name ||
this.props.summaryInfo.profile.canonical_alias ||
_t("Unnamed Room");
const oobData = {
roomId: this.props.summaryInfo.room_id,
avatarUrl: this.props.summaryInfo.profile.avatar_url,
name: this.props.summaryInfo.profile.name,
name: roomName,
};
let permalink = null;
if (this.props.summaryInfo.profile && this.props.summaryInfo.profile.canonical_alias) {
permalink = 'https://matrix.to/#/' + this.props.summaryInfo.profile.canonical_alias;
}
let roomNameNode = null;
if (permalink) {
roomNameNode = <a href={permalink} onClick={this.onClick} >{this.props.summaryInfo.profile.name}</a>;
roomNameNode = <a href={permalink} onClick={this.onClick} >{ roomName }</a>;
} else {
roomNameNode = <span>{this.props.summaryInfo.profile.name}</span>;
roomNameNode = <span>{ roomName }</span>;
}
const deleteButton = this.props.editing ?
<img
className="mx_GroupView_featuredThing_deleteButton"
src="img/cancel-small.svg"
width="14"
height="14"
alt="Delete"
onClick={this.onDeleteClicked} />
: <div />;
return <AccessibleButton className="mx_GroupView_featuredThing" onClick={this.onClick}>
<RoomAvatar oobData={oobData} width={64} height={64} />
<div className="mx_GroupView_featuredThing_name">{roomNameNode}</div>
<div className="mx_GroupView_featuredThing_name">{ roomNameNode }</div>
{ deleteButton }
</AccessibleButton>;
},
});
@ -121,19 +232,75 @@ const RoleUserList = React.createClass({
name: PropTypes.string,
}).isRequired,
}),
groupId: PropTypes.string.isRequired,
// Whether the list should be editable
editing: PropTypes.bool.isRequired,
},
onAddUsersClicked: function(ev) {
ev.preventDefault();
const AddressPickerDialog = sdk.getComponent("dialogs.AddressPickerDialog");
Modal.createTrackedDialog('Add Users to Group Summary', '', AddressPickerDialog, {
title: _t('Add users to the group summary'),
description: _t("Who would you like to add to this summary?"),
placeholder: _t("Name or matrix ID"),
button: _t("Add to summary"),
validAddressTypes: ['mx-user-id'],
groupId: this.props.groupId,
shouldOmitSelf: false,
onFinished: (success, addrs) => {
if (!success) return;
const errorList = [];
Promise.all(addrs.map((addr) => {
return this.context.groupStore
.addUserToGroupSummary(addr.address)
.catch(() => { errorList.push(addr.address); })
.reflect();
})).then(() => {
if (errorList.length === 0) {
return;
}
const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
Modal.createTrackedDialog(
'Failed to add the following users to the group summary',
'', ErrorDialog,
{
title: _t(
"Failed to add the following users to the summary of %(groupId)s:",
{groupId: this.props.groupId},
),
description: errorList.join(", "),
});
});
},
});
},
render: function() {
const TintableSvg = sdk.getComponent("elements.TintableSvg");
const addButton = this.props.editing ?
(<AccessibleButton className="mx_GroupView_featuredThings_addButton" onClick={this.onAddUsersClicked}>
<TintableSvg src="img/icons-create-room.svg" width="64" height="64" />
<div className="mx_GroupView_featuredThings_addButton_label">
{ _t('Add a User') }
</div>
</AccessibleButton>) : <div />;
const userNodes = this.props.users.map((u) => {
return <FeaturedUser key={u.user_id} summaryInfo={u} />;
return <FeaturedUser
key={u.user_id}
summaryInfo={u}
editing={this.props.editing}
groupId={this.props.groupId} />;
});
let roleHeader = null;
let roleHeader = <div />;
if (this.props.role && this.props.role.profile) {
roleHeader = <div className="mx_GroupView_featuredThings_category">{this.props.role.profile.name}</div>;
roleHeader = <div className="mx_GroupView_featuredThings_category">{ this.props.role.profile.name }</div>;
}
return <div>
{roleHeader}
{userNodes}
return <div className="mx_GroupView_featuredThings_container">
{ roleHeader }
{ userNodes }
{ addButton }
</div>;
},
});
@ -143,6 +310,8 @@ const FeaturedUser = React.createClass({
props: {
summaryInfo: UserSummaryType.isRequired,
editing: PropTypes.bool.isRequired,
groupId: PropTypes.string.isRequired,
},
onClick: function(e) {
@ -156,19 +325,64 @@ const FeaturedUser = React.createClass({
});
},
onDeleteClicked: function(e) {
e.preventDefault();
e.stopPropagation();
this.context.groupStore.removeUserFromGroupSummary(
this.props.summaryInfo.user_id,
).catch((err) => {
console.error('Error whilst removing user from group summary', err);
const displayName = this.props.summaryInfo.displayname || this.props.summaryInfo.user_id;
const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
Modal.createTrackedDialog(
'Failed to remove user from group summary',
'', ErrorDialog,
{
title: _t(
"Failed to remove a user from the summary of %(groupId)s",
{groupId: this.props.groupId},
),
description: _t("The user '%(displayName)s' could not be removed from the summary.", {displayName}),
});
});
},
render: function() {
// Add avatar once we get profile info inline in the summary response
//const BaseAvatar = sdk.getComponent("avatars.BaseAvatar");
const BaseAvatar = sdk.getComponent("avatars.BaseAvatar");
const name = this.props.summaryInfo.displayname || this.props.summaryInfo.user_id;
const permalink = 'https://matrix.to/#/' + this.props.summaryInfo.user_id;
const userNameNode = <a href={permalink} onClick={this.onClick} >{this.props.summaryInfo.user_id}</a>;
const userNameNode = <a href={permalink} onClick={this.onClick}>{ name }</a>;
const httpUrl = MatrixClientPeg.get()
.mxcUrlToHttp(this.props.summaryInfo.avatar_url, 64, 64);
const deleteButton = this.props.editing ?
<img
className="mx_GroupView_featuredThing_deleteButton"
src="img/cancel-small.svg"
width="14"
height="14"
alt="Delete"
onClick={this.onDeleteClicked} />
: <div />;
return <AccessibleButton className="mx_GroupView_featuredThing" onClick={this.onClick}>
<div className="mx_GroupView_featuredThing_name">{userNameNode}</div>
<BaseAvatar name={name} url={httpUrl} width={64} height={64} />
<div className="mx_GroupView_featuredThing_name">{ userNameNode }</div>
{ deleteButton }
</AccessibleButton>;
},
});
const GroupContext = {
groupStore: React.PropTypes.instanceOf(GroupStore).isRequired,
};
CategoryRoomList.contextTypes = GroupContext;
FeaturedRoom.contextTypes = GroupContext;
RoleUserList.contextTypes = GroupContext;
FeaturedUser.contextTypes = GroupContext;
export default React.createClass({
displayName: 'GroupView',
@ -176,6 +390,16 @@ export default React.createClass({
groupId: PropTypes.string.isRequired,
},
childContextTypes: {
groupStore: React.PropTypes.instanceOf(GroupStore),
},
getChildContext: function() {
return {
groupStore: this._groupStore,
};
},
getInitialState: function() {
return {
summary: null,
@ -183,12 +407,21 @@ export default React.createClass({
editing: false,
saving: false,
uploadingAvatar: false,
membershipBusy: false,
publicityBusy: false,
};
},
componentWillMount: function() {
this._changeAvatarComponent = null;
this._loadGroupFromServer(this.props.groupId);
this._initGroupStore(this.props.groupId);
MatrixClientPeg.get().on("Group.myMembership", this._onGroupMyMembership);
},
componentWillUnmount: function() {
MatrixClientPeg.get().removeListener("Group.myMembership", this._onGroupMyMembership);
this._groupStore.removeAllListeners();
},
componentWillReceiveProps: function(newProps) {
@ -197,18 +430,26 @@ export default React.createClass({
summary: null,
error: null,
}, () => {
this._loadGroupFromServer(newProps.groupId);
this._initGroupStore(newProps.groupId);
});
}
},
_loadGroupFromServer: function(groupId) {
MatrixClientPeg.get().getGroupSummary(groupId).done((res) => {
_onGroupMyMembership: function(group) {
if (group.groupId !== this.props.groupId) return;
this.setState({membershipBusy: false});
},
_initGroupStore: function(groupId) {
this._groupStore = GroupStoreCache.getGroupStore(MatrixClientPeg.get(), groupId);
this._groupStore.on('update', () => {
this.setState({
summary: res,
summary: this._groupStore.getSummary(),
error: null,
});
}, (err) => {
});
this._groupStore.on('error', (err) => {
this.setState({
summary: null,
error: err,
@ -216,6 +457,10 @@ export default React.createClass({
});
},
_onShowRhsClick: function(ev) {
dis.dispatch({ action: 'show_right_panel' });
},
_onEditClick: function() {
this.setState({
editing: true,
@ -281,7 +526,7 @@ export default React.createClass({
editing: false,
summary: null,
});
this._loadGroupFromServer(this.props.groupId);
this._initGroupStore(this.props.groupId);
}).catch((e) => {
this.setState({
saving: false,
@ -295,10 +540,80 @@ export default React.createClass({
}).done();
},
_getFeaturedRoomsNode() {
const summary = this.state.summary;
_onAcceptInviteClick: function() {
this.setState({membershipBusy: true});
MatrixClientPeg.get().acceptGroupInvite(this.props.groupId).then(() => {
// don't reset membershipBusy here: wait for the membership change to come down the sync
}).catch((e) => {
this.setState({membershipBusy: false});
const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
Modal.createTrackedDialog('Error accepting invite', '', ErrorDialog, {
title: _t("Error"),
description: _t("Unable to accept invite"),
});
});
},
if (summary.rooms_section.rooms.length == 0) return null;
_onRejectInviteClick: function() {
this.setState({membershipBusy: true});
MatrixClientPeg.get().leaveGroup(this.props.groupId).then(() => {
// don't reset membershipBusy here: wait for the membership change to come down the sync
}).catch((e) => {
this.setState({membershipBusy: false});
const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
Modal.createTrackedDialog('Error rejecting invite', '', ErrorDialog, {
title: _t("Error"),
description: _t("Unable to reject invite"),
});
});
},
_onLeaveClick: function() {
const QuestionDialog = sdk.getComponent("dialogs.QuestionDialog");
Modal.createTrackedDialog('Leave Group', '', QuestionDialog, {
title: _t("Leave Group"),
description: _t("Leave %(groupName)s?", {groupName: this.props.groupId}),
button: _t("Leave"),
danger: true,
onFinished: (confirmed) => {
if (!confirmed) return;
this.setState({membershipBusy: true});
MatrixClientPeg.get().leaveGroup(this.props.groupId).then(() => {
// don't reset membershipBusy here: wait for the membership change to come down the sync
}).catch((e) => {
this.setState({membershipBusy: false});
const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
Modal.createTrackedDialog('Error leaving room', '', ErrorDialog, {
title: _t("Error"),
description: _t("Unable to leave room"),
});
});
},
});
},
_onPubliciseOffClick: function() {
this._setPublicity(false);
},
_onPubliciseOnClick: function() {
this._setPublicity(true);
},
_setPublicity: function(publicity) {
this.setState({
publicityBusy: true,
});
this._groupStore.setGroupPublicity(publicity).then(() => {
this.setState({
publicityBusy: false,
});
});
},
_getFeaturedRoomsNode: function() {
const summary = this.state.summary;
const defaultCategoryRooms = [];
const categoryRooms = {};
@ -315,29 +630,32 @@ export default React.createClass({
}
});
let defaultCategoryNode = null;
if (defaultCategoryRooms.length > 0) {
defaultCategoryNode = <CategoryRoomList rooms={defaultCategoryRooms} />;
}
const defaultCategoryNode = <CategoryRoomList
rooms={defaultCategoryRooms}
groupId={this.props.groupId}
editing={this.state.editing} />;
const categoryRoomNodes = Object.keys(categoryRooms).map((catId) => {
const cat = summary.rooms_section.categories[catId];
return <CategoryRoomList key={catId} rooms={categoryRooms[catId]} category={cat} />;
return <CategoryRoomList
key={catId}
rooms={categoryRooms[catId]}
category={cat}
groupId={this.props.groupId}
editing={this.state.editing} />;
});
return <div className="mx_GroupView_featuredThings">
<div className="mx_GroupView_featuredThings_header">
{_t('Featured Rooms:')}
{ _t('Featured Rooms:') }
</div>
{defaultCategoryNode}
{categoryRoomNodes}
{ defaultCategoryNode }
{ categoryRoomNodes }
</div>;
},
_getFeaturedUsersNode() {
_getFeaturedUsersNode: function() {
const summary = this.state.summary;
if (summary.users_section.users.length == 0) return null;
const noRoleUsers = [];
const roleUsers = {};
summary.users_section.users.forEach((u) => {
@ -353,24 +671,121 @@ export default React.createClass({
}
});
let noRoleNode = null;
if (noRoleUsers.length > 0) {
noRoleNode = <RoleUserList users={noRoleUsers} />;
}
const noRoleNode = <RoleUserList
users={noRoleUsers}
groupId={this.props.groupId}
editing={this.state.editing} />;
const roleUserNodes = Object.keys(roleUsers).map((roleId) => {
const role = summary.users_section.roles[roleId];
return <RoleUserList key={roleId} users={roleUsers[roleId]} role={role} />;
return <RoleUserList
key={roleId}
users={roleUsers[roleId]}
role={role}
groupId={this.props.groupId}
editing={this.state.editing} />;
});
return <div className="mx_GroupView_featuredThings">
<div className="mx_GroupView_featuredThings_header">
{_t('Featured Users:')}
{ _t('Featured Users:') }
</div>
{noRoleNode}
{roleUserNodes}
{ noRoleNode }
{ roleUserNodes }
</div>;
},
_getMembershipSection: function() {
const Spinner = sdk.getComponent("elements.Spinner");
const group = MatrixClientPeg.get().getGroup(this.props.groupId);
if (!group) return null;
if (group.myMembership === 'invite') {
if (this.state.membershipBusy) {
return <div className="mx_GroupView_membershipSection">
<Spinner />
</div>;
}
return <div className="mx_GroupView_membershipSection mx_GroupView_membershipSection_invited">
<div className="mx_GroupView_membershipSection_description">
{ _t("%(inviter)s has invited you to join this group", {inviter: group.inviter.userId}) }
</div>
<div className="mx_GroupView_membership_buttonContainer">
<AccessibleButton className="mx_GroupView_textButton mx_RoomHeader_textButton"
onClick={this._onAcceptInviteClick}
>
{ _t("Accept") }
</AccessibleButton>
<AccessibleButton className="mx_GroupView_textButton mx_RoomHeader_textButton"
onClick={this._onRejectInviteClick}
>
{ _t("Decline") }
</AccessibleButton>
</div>
</div>;
} else if (group.myMembership === 'join') {
let youAreAMemberText = _t("You are a member of this group");
if (this.state.summary.user && this.state.summary.user.is_privileged) {
youAreAMemberText = _t("You are an administrator of this group");
}
let publicisedButton;
if (this.state.publicityBusy) {
publicisedButton = <Spinner />;
}
let publicisedSection;
if (this.state.summary.user && this.state.summary.user.is_publicised) {
if (!this.state.publicityBusy) {
publicisedButton = <AccessibleButton className="mx_GroupView_textButton mx_RoomHeader_textButton"
onClick={this._onPubliciseOffClick}
>
{ _t("Unpublish") }
</AccessibleButton>;
}
publicisedSection = <div className="mx_GroupView_membershipSubSection">
{ _t("This group is published on your profile") }
<div className="mx_GroupView_membership_buttonContainer">
{ publicisedButton }
</div>
</div>;
} else {
if (!this.state.publicityBusy) {
publicisedButton = <AccessibleButton className="mx_GroupView_textButton mx_RoomHeader_textButton"
onClick={this._onPubliciseOnClick}
>
{ _t("Publish") }
</AccessibleButton>;
}
publicisedSection = <div className="mx_GroupView_membershipSubSection">
{ _t("This group is not published on your profile") }
<div className="mx_GroupView_membership_buttonContainer">
{ publicisedButton }
</div>
</div>;
}
return <div className="mx_GroupView_membershipSection mx_GroupView_membershipSection_joined">
<div className="mx_GroupView_membershipSubSection">
<div className="mx_GroupView_membershipSection_description">
{ youAreAMemberText }
</div>
<div className="mx_GroupView_membership_buttonContainer">
<AccessibleButton className="mx_GroupView_textButton mx_RoomHeader_textButton"
onClick={this._onLeaveClick}
>
{ _t("Leave") }
</AccessibleButton>
</div>
</div>
{ publicisedSection }
</div>;
}
return null;
},
render: function() {
const GroupAvatar = sdk.getComponent("avatars.GroupAvatar");
const Loader = sdk.getComponent("elements.Spinner");
@ -384,8 +799,8 @@ export default React.createClass({
let avatarNode;
let nameNode;
let shortDescNode;
let rightButtons;
let roomBody;
const rightButtons = [];
const headerClasses = {
mx_GroupView_header: true,
};
@ -404,15 +819,15 @@ export default React.createClass({
avatarNode = (
<div className="mx_GroupView_avatarPicker">
<label htmlFor="avatarInput" className="mx_GroupView_avatarPicker_label">
{avatarImage}
{ avatarImage }
</label>
<div className="mx_GroupView_avatarPicker_edit">
<label htmlFor="avatarInput" className="mx_GroupView_avatarPicker_label">
<img src="img/camera.svg"
alt={ _t("Upload avatar") } title={ _t("Upload avatar") }
alt={_t("Upload avatar")} title={_t("Upload avatar")}
width="17" height="15" />
</label>
<input id="avatarInput" className="mx_GroupView_uploadInput" type="file" onChange={this._onAvatarSelected}/>
<input id="avatarInput" className="mx_GroupView_uploadInput" type="file" onChange={this._onAvatarSelected} />
</div>
</div>
);
@ -428,20 +843,26 @@ export default React.createClass({
placeholder={_t('Description')}
tabIndex="2"
/>;
rightButtons = <span>
<AccessibleButton className="mx_GroupView_saveButton mx_RoomHeader_textButton" onClick={this._onSaveClick}>
{_t('Save')}
</AccessibleButton>
<AccessibleButton className='mx_GroupView_cancelButton' onClick={this._onCancelClick}>
<img src="img/cancel.svg" className='mx_filterFlipColor'
width="18" height="18" alt={_t("Cancel")}/>
</AccessibleButton>
</span>;
rightButtons.push(
<AccessibleButton className="mx_GroupView_textButton mx_RoomHeader_textButton"
onClick={this._onSaveClick} key="_saveButton"
>
{ _t('Save') }
</AccessibleButton>,
);
rightButtons.push(
<AccessibleButton className="mx_RoomHeader_cancelButton" onClick={this._onCancelClick} key="_cancelButton">
<img src="img/cancel.svg" className="mx_filterFlipColor"
width="18" height="18" alt={_t("Cancel")} />
</AccessibleButton>,
);
roomBody = <div>
<textarea className="mx_GroupView_editLongDesc" value={this.state.profileForm.long_description}
onChange={this._onLongDescChange}
tabIndex="3"
/>
{ this._getFeaturedRoomsNode() }
{ this._getFeaturedUsersNode() }
</div>;
} else {
const groupAvatarUrl = summary.profile ? summary.profile.avatar_url : null;
@ -452,31 +873,44 @@ export default React.createClass({
/>;
if (summary.profile && summary.profile.name) {
nameNode = <div>
<span>{summary.profile.name}</span>
<span>{ summary.profile.name }</span>
<span className="mx_GroupView_header_groupid">
({this.props.groupId})
({ this.props.groupId })
</span>
</div>;
} else {
nameNode = <span>{this.props.groupId}</span>;
nameNode = <span>{ this.props.groupId }</span>;
}
shortDescNode = <span>{summary.profile.short_description}</span>;
shortDescNode = <span>{ summary.profile.short_description }</span>;
let description = null;
if (summary.profile && summary.profile.long_description) {
description = sanitizedHtmlNode(summary.profile.long_description);
}
roomBody = <div>
<div className="mx_GroupView_groupDesc">{description}</div>
{this._getFeaturedRoomsNode()}
{this._getFeaturedUsersNode()}
{ this._getMembershipSection() }
<div className="mx_GroupView_groupDesc">{ description }</div>
{ this._getFeaturedRoomsNode() }
{ this._getFeaturedUsersNode() }
</div>;
// disabled until editing works
rightButtons = <AccessibleButton className="mx_GroupHeader_button"
onClick={this._onEditClick} title={_t("Edit Group")}
>
<TintableSvg src="img/icons-settings-room.svg" width="16" height="16"/>
</AccessibleButton>;
if (summary.user && summary.user.is_privileged) {
rightButtons.push(
<AccessibleButton className="mx_GroupHeader_button"
onClick={this._onEditClick} title={_t("Edit Group")} key="_editButton"
>
<TintableSvg src="img/icons-settings-room.svg" width="16" height="16" />
</AccessibleButton>,
);
}
if (this.props.collapsedRhs) {
rightButtons.push(
<AccessibleButton className="mx_GroupHeader_button"
onClick={this._onShowRhsClick} title={_t('Show panel')} key="_maximiseButton"
>
<TintableSvg src="img/maximise.svg" width="10" height="16" />
</AccessibleButton>,
);
}
headerClasses.mx_GroupView_header_view = true;
}
@ -486,40 +920,40 @@ export default React.createClass({
<div className={classnames(headerClasses)}>
<div className="mx_GroupView_header_leftCol">
<div className="mx_GroupView_header_avatar">
{avatarNode}
{ avatarNode }
</div>
<div className="mx_GroupView_header_info">
<div className="mx_GroupView_header_name">
{nameNode}
{ nameNode }
</div>
<div className="mx_GroupView_header_shortDesc">
{shortDescNode}
{ shortDescNode }
</div>
</div>
</div>
<div className="mx_GroupView_header_rightCol">
{rightButtons}
{ rightButtons }
</div>
</div>
{roomBody}
{ roomBody }
</div>
);
} else if (this.state.error) {
if (this.state.error.httpStatus === 404) {
return (
<div className="mx_GroupView_error">
Group {this.props.groupId} not found
Group { this.props.groupId } not found
</div>
);
} else {
let extraText;
if (this.state.error.errcode === 'M_UNRECOGNIZED') {
extraText = <div>{_t('This Home server does not support groups')}</div>;
extraText = <div>{ _t('This Home server does not support groups') }</div>;
}
return (
<div className="mx_GroupView_error">
Failed to load {this.props.groupId}
{extraText}
Failed to load { this.props.groupId }
{ extraText }
</div>
);
}

View File

@ -107,7 +107,7 @@ export default React.createClass({
const msg = error.message || error.toString();
this.setState({
errorText: msg
errorText: msg,
});
}).done();
@ -207,7 +207,7 @@ export default React.createClass({
if (this.state.errorText) {
error = (
<div className="error">
{this.state.errorText}
{ this.state.errorText }
</div>
);
}
@ -215,8 +215,8 @@ export default React.createClass({
return (
<div>
<div>
{this._renderCurrentStage()}
{error}
{ this._renderCurrentStage() }
{ error }
</div>
</div>
);

View File

@ -131,6 +131,9 @@ export default React.createClass({
useCompactLayout: event.getContent().useCompactLayout,
});
}
if (event.getType() === "m.ignored_user_list") {
dis.dispatch({action: "ignore_state_changed"});
}
},
_onKeyDown: function(ev) {
@ -161,7 +164,7 @@ export default React.createClass({
case KeyCode.UP:
case KeyCode.DOWN:
if (ev.altKey && !ev.shiftKey && !ev.ctrlKey && !ev.metaKey) {
let action = ev.keyCode == KeyCode.UP ?
const action = ev.keyCode == KeyCode.UP ?
'view_prev_room' : 'view_next_room';
dis.dispatch({action: action});
handled = true;
@ -203,8 +206,7 @@ export default React.createClass({
_onScrollKeyPressed: function(ev) {
if (this.refs.roomView) {
this.refs.roomView.handleScrollKey(ev);
}
else if (this.refs.roomDirectory) {
} else if (this.refs.roomDirectory) {
this.refs.roomDirectory.handleScrollKey(ev);
}
},
@ -238,10 +240,10 @@ export default React.createClass({
eventPixelOffset={this.props.initialEventPixelOffset}
key={this.props.currentRoomId || 'roomview'}
opacity={this.props.middleOpacity}
collapsedRhs={this.props.collapse_rhs}
collapsedRhs={this.props.collapseRhs}
ConferenceHandler={this.props.ConferenceHandler}
/>;
if (!this.props.collapse_rhs) right_panel = <RightPanel roomId={this.props.currentRoomId} opacity={this.props.rightOpacity} />;
if (!this.props.collapseRhs) right_panel = <RightPanel roomId={this.props.currentRoomId} opacity={this.props.rightOpacity} />;
break;
case PageTypes.UserSettings:
@ -252,7 +254,7 @@ export default React.createClass({
referralBaseUrl={this.props.config.referralBaseUrl}
teamToken={this.props.teamToken}
/>;
if (!this.props.collapse_rhs) right_panel = <RightPanel opacity={this.props.rightOpacity}/>;
if (!this.props.collapseRhs) right_panel = <RightPanel opacity={this.props.rightOpacity} />;
break;
case PageTypes.MyGroups:
@ -262,9 +264,9 @@ export default React.createClass({
case PageTypes.CreateRoom:
page_element = <CreateRoom
onRoomCreated={this.props.onRoomCreated}
collapsedRhs={this.props.collapse_rhs}
collapsedRhs={this.props.collapseRhs}
/>;
if (!this.props.collapse_rhs) right_panel = <RightPanel opacity={this.props.rightOpacity}/>;
if (!this.props.collapseRhs) right_panel = <RightPanel opacity={this.props.rightOpacity} />;
break;
case PageTypes.RoomDirectory:
@ -297,8 +299,9 @@ export default React.createClass({
case PageTypes.GroupView:
page_element = <GroupView
groupId={this.props.currentGroupId}
collapsedRhs={this.props.collapseRhs}
/>;
//right_panel = <RightPanel opacity={this.props.rightOpacity} />;
if (!this.props.collapseRhs) right_panel = <RightPanel groupId={this.props.currentGroupId} opacity={this.props.rightOpacity} />;
break;
}
@ -316,7 +319,7 @@ export default React.createClass({
topBar = <MatrixToolbar />;
}
var bodyClasses = 'mx_MatrixChat';
let bodyClasses = 'mx_MatrixChat';
if (topBar) {
bodyClasses += ' mx_MatrixChat_toolbarShowing';
}
@ -326,17 +329,17 @@ export default React.createClass({
return (
<div className='mx_MatrixChat_wrapper'>
{topBar}
{ topBar }
<div className={bodyClasses}>
<LeftPanel
selectedRoom={this.props.currentRoomId}
collapsed={this.props.collapse_lhs || false}
collapsed={this.props.collapseLhs || false}
opacity={this.props.leftOpacity}
/>
<main className='mx_MatrixChat_middlePanel'>
{page_element}
{ page_element }
</main>
{right_panel}
{ right_panel }
</div>
</div>
);

View File

@ -32,7 +32,7 @@ import dis from "../../dispatcher";
import Modal from "../../Modal";
import Tinter from "../../Tinter";
import sdk from '../../index';
import { showStartChatInviteDialog, showRoomInviteDialog } from '../../Invite';
import { showStartChatInviteDialog, showRoomInviteDialog } from '../../RoomInvite';
import * as Rooms from '../../Rooms';
import linkifyMatrix from "../../linkify-matrix";
import * as Lifecycle from '../../Lifecycle';
@ -143,8 +143,8 @@ module.exports = React.createClass({
// If we're trying to just view a user ID (i.e. /user URL), this is it
viewUserId: null,
collapse_lhs: false,
collapse_rhs: false,
collapseLhs: false,
collapseRhs: false,
leftOpacity: 1.0,
middleOpacity: 1.0,
rightOpacity: 1.0,
@ -434,7 +434,7 @@ module.exports = React.createClass({
break;
case 'view_user':
// FIXME: ugly hack to expand the RightPanel and then re-dispatch.
if (this.state.collapse_rhs) {
if (this.state.collapseRhs) {
setTimeout(()=>{
dis.dispatch({
action: 'show_right_panel',
@ -516,22 +516,22 @@ module.exports = React.createClass({
break;
case 'hide_left_panel':
this.setState({
collapse_lhs: true,
collapseLhs: true,
});
break;
case 'show_left_panel':
this.setState({
collapse_lhs: false,
collapseLhs: false,
});
break;
case 'hide_right_panel':
this.setState({
collapse_rhs: true,
collapseRhs: true,
});
break;
case 'show_right_panel':
this.setState({
collapse_rhs: false,
collapseRhs: false,
});
break;
case 'ui_opacity': {
@ -773,15 +773,13 @@ module.exports = React.createClass({
dis.dispatch({action: 'view_set_mxid'});
return;
}
const TextInputDialog = sdk.getComponent("dialogs.TextInputDialog");
Modal.createTrackedDialog('Create Room', '', TextInputDialog, {
title: _t('Create Room'),
description: _t('Room name (optional)'),
button: _t('Create Room'),
onFinished: (shouldCreate, name) => {
const CreateRoomDialog = sdk.getComponent('dialogs.CreateRoomDialog');
Modal.createTrackedDialog('Create Room', '', CreateRoomDialog, {
onFinished: (shouldCreate, name, noFederate) => {
if (shouldCreate) {
const createOpts = {};
if (name) createOpts.name = name;
if (noFederate) createOpts.creation_content = {'m.federate': false};
createRoom({createOpts}).done();
}
},
@ -853,7 +851,7 @@ module.exports = React.createClass({
title: _t("Leave room"),
description: (
<span>
{_t("Are you sure you want to leave the room '%(roomName)s'?", {roomName: roomToLeave.name})}
{ _t("Are you sure you want to leave the room '%(roomName)s'?", {roomName: roomToLeave.name}) }
</span>
),
onFinished: (shouldLeave) => {
@ -993,8 +991,8 @@ module.exports = React.createClass({
this.setStateForNewView({
view: VIEWS.LOGIN,
ready: false,
collapse_lhs: false,
collapse_rhs: false,
collapseLhs: false,
collapseRhs: false,
currentRoomId: null,
page_type: PageTypes.RoomDirectory,
});
@ -1450,7 +1448,7 @@ module.exports = React.createClass({
return (
<div className="mx_MatrixChat_splash">
<Spinner />
<a href="#" className="mx_MatrixChat_splashButtons" onClick={ this.onLogoutClick }>
<a href="#" className="mx_MatrixChat_splashButtons" onClick={this.onLogoutClick}>
{ _t('Logout') }
</a>
</div>

View File

@ -154,15 +154,15 @@ module.exports = React.createClass({
// 0: read marker is within the window
// +1: read marker is below the window
getReadMarkerPosition: function() {
var readMarker = this.refs.readMarkerNode;
var messageWrapper = this.refs.scrollPanel;
const readMarker = this.refs.readMarkerNode;
const messageWrapper = this.refs.scrollPanel;
if (!readMarker || !messageWrapper) {
return null;
}
var wrapperRect = ReactDOM.findDOMNode(messageWrapper).getBoundingClientRect();
var readMarkerRect = readMarker.getBoundingClientRect();
const wrapperRect = ReactDOM.findDOMNode(messageWrapper).getBoundingClientRect();
const readMarkerRect = readMarker.getBoundingClientRect();
// the read-marker pretends to have zero height when it is actually
// two pixels high; +2 here to account for that.
@ -241,6 +241,10 @@ module.exports = React.createClass({
// TODO: Implement granular (per-room) hide options
_shouldShowEvent: function(mxEv) {
if (mxEv.sender && MatrixClientPeg.get().isUserIgnored(mxEv.sender.userId)) {
return false; // ignored = no show (only happens if the ignore happens after an event was received)
}
const EventTile = sdk.getComponent('rooms.EventTile');
if (!EventTile.haveTileForEvent(mxEv)) {
return false; // no tile = no show
@ -258,7 +262,7 @@ module.exports = React.createClass({
this.eventNodes = {};
var i;
let i;
// first figure out which is the last event in the list which we're
// actually going to show; this allows us to behave slightly
@ -268,9 +272,9 @@ module.exports = React.createClass({
// a local echo, to manage the read-marker.
let lastShownEvent;
var lastShownNonLocalEchoIndex = -1;
let lastShownNonLocalEchoIndex = -1;
for (i = this.props.events.length-1; i >= 0; i--) {
var mxEv = this.props.events[i];
const mxEv = this.props.events[i];
if (!this._shouldShowEvent(mxEv)) {
continue;
}
@ -288,12 +292,12 @@ module.exports = React.createClass({
break;
}
var ret = [];
const ret = [];
var prevEvent = null; // the last event we showed
let prevEvent = null; // the last event we showed
// assume there is no read marker until proven otherwise
var readMarkerVisible = false;
let readMarkerVisible = false;
// if the readmarker has moved, cancel any active ghost.
if (this.currentReadMarkerEventId && this.props.readMarkerEventId &&
@ -305,16 +309,16 @@ module.exports = React.createClass({
const isMembershipChange = (e) => e.getType() === 'm.room.member';
for (i = 0; i < this.props.events.length; i++) {
let mxEv = this.props.events[i];
let eventId = mxEv.getId();
let last = (mxEv === lastShownEvent);
const mxEv = this.props.events[i];
const eventId = mxEv.getId();
const last = (mxEv === lastShownEvent);
const wantTile = this._shouldShowEvent(mxEv);
// Wrap consecutive member events in a ListSummary, ignore if redacted
if (isMembershipChange(mxEv) && wantTile) {
let readMarkerInMels = false;
let ts1 = mxEv.getTs();
const ts1 = mxEv.getTs();
// Ensure that the key of the MemberEventListSummary does not change with new
// member events. This will prevent it from being re-created unnecessarily, and
// instead will allow new props to be provided. In turn, the shouldComponentUpdate
@ -326,7 +330,7 @@ module.exports = React.createClass({
const key = "membereventlistsummary-" + (prevEvent ? mxEv.getId() : "initial");
if (this._wantsDateSeparator(prevEvent, mxEv.getDate())) {
let dateSeparator = <li key={ts1+'~'}><DateSeparator key={ts1+'~'} ts={ts1} showTwelveHour={this.props.isTwelveHour}/></li>;
const dateSeparator = <li key={ts1+'~'}><DateSeparator key={ts1+'~'} ts={ts1} showTwelveHour={this.props.isTwelveHour} /></li>;
ret.push(dateSeparator);
}
@ -335,7 +339,7 @@ module.exports = React.createClass({
readMarkerInMels = true;
}
let summarisedEvents = [mxEv];
const summarisedEvents = [mxEv];
for (;i + 1 < this.props.events.length; i++) {
const collapsedMxEv = this.props.events[i + 1];
@ -361,8 +365,13 @@ module.exports = React.createClass({
summarisedEvents.push(collapsedMxEv);
}
let highlightInMels = false;
// At this point, i = the index of the last event in the summary sequence
let eventTiles = summarisedEvents.map((e) => {
if (e.getId() === this.props.highlightedEventId) {
highlightInMels = true;
}
// In order to prevent DateSeparators from appearing in the expanded form
// of MemberEventListSummary, render each member event as if the previous
// one was itself. This way, the timestamp of the previous event === the
@ -376,15 +385,13 @@ module.exports = React.createClass({
eventTiles = null;
}
ret.push(
<MemberEventListSummary
key={key}
events={summarisedEvents}
onToggle={this._onWidgetLoad} // Update scroll state
>
{eventTiles}
</MemberEventListSummary>
);
ret.push(<MemberEventListSummary key={key}
events={summarisedEvents}
onToggle={this._onWidgetLoad} // Update scroll state
startExpanded={highlightInMels}
>
{ eventTiles }
</MemberEventListSummary>);
if (readMarkerInMels) {
ret.push(this._getReadMarkerTile(visible));
@ -401,7 +408,7 @@ module.exports = React.createClass({
prevEvent = mxEv;
}
var isVisibleReadMarker = false;
let isVisibleReadMarker = false;
if (eventId == this.props.readMarkerEventId) {
var visible = this.props.readMarkerVisible;
@ -441,10 +448,10 @@ module.exports = React.createClass({
_getTilesForEvent: function(prevEvent, mxEv, last) {
const EventTile = sdk.getComponent('rooms.EventTile');
const DateSeparator = sdk.getComponent('messages.DateSeparator');
var ret = [];
const ret = [];
// is this a continuation of the previous message?
var continuation = false;
let continuation = false;
if (prevEvent !== null
&& prevEvent.sender && mxEv.sender
@ -469,8 +476,8 @@ module.exports = React.createClass({
// local echoes have a fake date, which could even be yesterday. Treat them
// as 'today' for the date separators.
var ts1 = mxEv.getTs();
var eventDate = mxEv.getDate();
let ts1 = mxEv.getTs();
let eventDate = mxEv.getDate();
if (mxEv.status) {
eventDate = new Date();
ts1 = eventDate.getTime();
@ -478,19 +485,19 @@ module.exports = React.createClass({
// do we need a date separator since the last event?
if (this._wantsDateSeparator(prevEvent, eventDate)) {
var dateSeparator = <li key={ts1}><DateSeparator key={ts1} ts={ts1} showTwelveHour={this.props.isTwelveHour}/></li>;
const dateSeparator = <li key={ts1}><DateSeparator key={ts1} ts={ts1} showTwelveHour={this.props.isTwelveHour} /></li>;
ret.push(dateSeparator);
continuation = false;
}
var eventId = mxEv.getId();
var highlight = (eventId == this.props.highlightedEventId);
const eventId = mxEv.getId();
const highlight = (eventId == this.props.highlightedEventId);
// we can't use local echoes as scroll tokens, because their event IDs change.
// Local echos have a send "status".
var scrollToken = mxEv.status ? undefined : eventId;
const scrollToken = mxEv.status ? undefined : eventId;
var readReceipts;
let readReceipts;
if (this.props.showReadReceipts) {
readReceipts = this._getReadReceiptsForEvent(mxEv);
}
@ -508,8 +515,8 @@ module.exports = React.createClass({
eventSendStatus={mxEv.status}
tileShape={this.props.tileShape}
isTwelveHour={this.props.isTwelveHour}
last={last} isSelectedEvent={highlight}/>
</li>
last={last} isSelectedEvent={highlight} />
</li>,
);
return ret;
@ -544,12 +551,15 @@ module.exports = React.createClass({
if (!room) {
return null;
}
let receipts = [];
const receipts = [];
room.getReceiptsForEvent(event).forEach((r) => {
if (!r.userId || r.type !== "m.read" || r.userId === myUserId) {
return; // ignore non-read receipts and receipts from self.
}
let member = room.getMember(r.userId);
if (MatrixClientPeg.get().isUserIgnored(r.userId)) {
return; // ignore ignored users
}
const member = room.getMember(r.userId);
if (!member) {
return; // ignore unknown user IDs
}
@ -565,7 +575,7 @@ module.exports = React.createClass({
},
_getReadMarkerTile: function(visible) {
var hr;
let hr;
if (visible) {
hr = <hr className="mx_RoomView_myReadMarker"
style={{opacity: 1, width: '99%'}}
@ -575,7 +585,7 @@ module.exports = React.createClass({
return (
<li key="_readupto" ref="readMarkerNode"
className="mx_RoomView_myReadMarker_container">
{hr}
{ hr }
</li>
);
},
@ -594,7 +604,7 @@ module.exports = React.createClass({
},
_getReadMarkerGhostTile: function() {
var hr = <hr className="mx_RoomView_myReadMarker"
const hr = <hr className="mx_RoomView_myReadMarker"
style={{opacity: 1, width: '99%'}}
ref={this._startAnimation}
/>;
@ -605,7 +615,7 @@ module.exports = React.createClass({
return (
<li key={"_readuptoghost_"+this.currentGhostEventId}
className="mx_RoomView_myReadMarker_container">
{hr}
{ hr }
</li>
);
},
@ -617,7 +627,7 @@ module.exports = React.createClass({
// once dynamic content in the events load, make the scrollPanel check the
// scroll offsets.
_onWidgetLoad: function() {
var scrollPanel = this.refs.scrollPanel;
const scrollPanel = this.refs.scrollPanel;
if (scrollPanel) {
scrollPanel.forceUpdate();
}
@ -628,9 +638,9 @@ module.exports = React.createClass({
},
render: function() {
var ScrollPanel = sdk.getComponent("structures.ScrollPanel");
var Spinner = sdk.getComponent("elements.Spinner");
var topSpinner, bottomSpinner;
const ScrollPanel = sdk.getComponent("structures.ScrollPanel");
const Spinner = sdk.getComponent("elements.Spinner");
let topSpinner, bottomSpinner;
if (this.props.backPaginating) {
topSpinner = <li key="_topSpinner"><Spinner /></li>;
}
@ -638,25 +648,25 @@ module.exports = React.createClass({
bottomSpinner = <li key="_bottomSpinner"><Spinner /></li>;
}
var style = this.props.hidden ? { display: 'none' } : {};
const style = this.props.hidden ? { display: 'none' } : {};
style.opacity = this.props.opacity;
var className = this.props.className + " mx_fadable";
let className = this.props.className + " mx_fadable";
if (this.props.alwaysShowTimestamps) {
className += " mx_MessagePanel_alwaysShowTimestamps";
}
return (
<ScrollPanel ref="scrollPanel" className={ className }
onScroll={ this.props.onScroll }
onResize={ this.onResize }
onFillRequest={ this.props.onFillRequest }
onUnfillRequest={ this.props.onUnfillRequest }
style={ style }
stickyBottom={ this.props.stickyBottom }>
{topSpinner}
{this._getEventTiles()}
{bottomSpinner}
<ScrollPanel ref="scrollPanel" className={className}
onScroll={this.props.onScroll}
onResize={this.onResize}
onFillRequest={this.props.onFillRequest}
onUnfillRequest={this.props.onUnfillRequest}
style={style}
stickyBottom={this.props.stickyBottom}>
{ topSpinner }
{ this._getEventTiles() }
{ bottomSpinner }
</ScrollPanel>
);
},

View File

@ -39,7 +39,7 @@ const GroupTile = React.createClass({
},
render: function() {
return <a onClick={this.onClick} href="#">{this.props.groupId}</a>;
return <a onClick={this.onClick} href="#">{ this.props.groupId }</a>;
},
});
@ -90,51 +90,51 @@ export default withMatrixClient(React.createClass({
);
});
content = <div>
<div>{_t('You are a member of these groups:')}</div>
{groupNodes}
<div>{ _t('You are a member of these groups:') }</div>
{ groupNodes }
</div>;
} else if (this.state.error) {
content = <div className="mx_MyGroups_error">
{_t('Error whilst fetching joined groups')}
{ _t('Error whilst fetching joined groups') }
</div>;
} else {
content = <Loader />;
}
return <div className="mx_MyGroups">
<SimpleRoomHeader title={ _t("Groups") } />
<SimpleRoomHeader title={_t("Groups")} icon="img/icons-groups.svg" />
<div className='mx_MyGroups_joinCreateBox'>
<div className="mx_MyGroups_createBox">
<div className="mx_MyGroups_joinCreateHeader">
{_t('Create a new group')}
{ _t('Create a new group') }
</div>
<AccessibleButton className='mx_MyGroups_joinCreateButton' onClick={this._onCreateGroupClick}>
<TintableSvg src="img/icons-create-room.svg" width="50" height="50" />
</AccessibleButton>
{_t(
{ _t(
'Create a group to represent your community! '+
'Define a set of rooms and your own custom homepage '+
'to mark out your space in the Matrix universe.',
)}
) }
</div>
<div className="mx_MyGroups_joinBox">
<div className="mx_MyGroups_joinCreateHeader">
{_t('Join an existing group')}
{ _t('Join an existing group') }
</div>
<AccessibleButton className='mx_MyGroups_joinCreateButton' onClick={this._onJoinGroupClick}>
<TintableSvg src="img/icons-create-room.svg" width="50" height="50" />
</AccessibleButton>
{_tJsx(
{ _tJsx(
'To join an existing group you\'ll have to '+
'know its group identifier; this will look '+
'something like <i>+example:matrix.org</i>.',
/<i>(.*)<\/i>/,
(sub) => <i>{sub}</i>,
)}
(sub) => <i>{ sub }</i>,
) }
</div>
</div>
<div className="mx_MyGroups_content">
{content}
{ content }
</div>
</div>;
},

View File

@ -14,18 +14,18 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
var React = require('react');
var ReactDOM = require("react-dom");
const React = require('react');
const ReactDOM = require("react-dom");
import { _t } from '../../languageHandler';
var Matrix = require("matrix-js-sdk");
var sdk = require('../../index');
var MatrixClientPeg = require("../../MatrixClientPeg");
var dis = require("../../dispatcher");
const Matrix = require("matrix-js-sdk");
const sdk = require('../../index');
const MatrixClientPeg = require("../../MatrixClientPeg");
const dis = require("../../dispatcher");
/*
* Component which shows the global notification list using a TimelinePanel
*/
var NotificationPanel = React.createClass({
const NotificationPanel = React.createClass({
displayName: 'NotificationPanel',
propTypes: {
@ -33,10 +33,10 @@ var NotificationPanel = React.createClass({
render: function() {
// wrap a TimelinePanel with the jump-to-event bits turned off.
var TimelinePanel = sdk.getComponent("structures.TimelinePanel");
var Loader = sdk.getComponent("elements.Spinner");
const TimelinePanel = sdk.getComponent("structures.TimelinePanel");
const Loader = sdk.getComponent("elements.Spinner");
var timelineSet = MatrixClientPeg.get().getNotifTimelineSet();
const timelineSet = MatrixClientPeg.get().getNotifTimelineSet();
if (timelineSet) {
return (
<TimelinePanel key={"NotificationPanel_" + this.props.roomId}
@ -44,18 +44,17 @@ var NotificationPanel = React.createClass({
manageReadReceipts={false}
manageReadMarkers={false}
timelineSet={timelineSet}
showUrlPreview = { false }
opacity={ this.props.opacity }
showUrlPreview = {false}
opacity={this.props.opacity}
tileShape="notif"
empty={ _t('You have no visible notifications') }
empty={_t('You have no visible notifications')}
/>
);
}
else {
} else {
console.error("No notifTimelineSet available!");
return (
<div className="mx_NotificationPanel">
<Loader/>
<Loader />
</div>
);
}

View File

@ -103,7 +103,7 @@ module.exports = React.createClass({
componentWillUnmount: function() {
// we may have entirely lost our client as we're logging out before clicking login on the guest bar...
var client = MatrixClientPeg.get();
const client = MatrixClientPeg.get();
if (client) {
client.removeListener("sync", this.onSyncStateChange);
client.removeListener("RoomMember.typing", this.onRoomMemberTyping);
@ -115,18 +115,18 @@ module.exports = React.createClass({
return;
}
this.setState({
syncState: state
syncState: state,
});
},
onRoomMemberTyping: function(ev, member) {
this.setState({
usersTyping: WhoIsTyping.usersTypingApartFromMe(this.props.room),
usersTyping: WhoIsTyping.usersTypingApartFromMeAndIgnored(this.props.room),
});
},
// Check whether current size is greater than 0, if yes call props.onVisible
_checkSize: function () {
_checkSize: function() {
if (this.props.onVisible && this._getSize()) {
this.props.onVisible();
}
@ -157,9 +157,9 @@ module.exports = React.createClass({
if (this.props.numUnreadMessages) {
return (
<div className="mx_RoomStatusBar_scrollDownIndicator"
onClick={ this.props.onScrollToBottomClick }>
onClick={this.props.onScrollToBottomClick}>
<img src="img/newmessages.svg" width="24" height="24"
alt=""/>
alt="" />
</div>
);
}
@ -167,18 +167,18 @@ module.exports = React.createClass({
if (!this.props.atEndOfLiveTimeline) {
return (
<div className="mx_RoomStatusBar_scrollDownIndicator"
onClick={ this.props.onScrollToBottomClick }>
onClick={this.props.onScrollToBottomClick}>
<img src="img/scrolldown.svg" width="24" height="24"
alt={ _t("Scroll to bottom of page") }
title={ _t("Scroll to bottom of page") }/>
alt={_t("Scroll to bottom of page")}
title={_t("Scroll to bottom of page")} />
</div>
);
}
if (this.props.hasActiveCall) {
var TintableSvg = sdk.getComponent("elements.TintableSvg");
const TintableSvg = sdk.getComponent("elements.TintableSvg");
return (
<TintableSvg src="img/sound-indicator.svg" width="23" height="20"/>
<TintableSvg src="img/sound-indicator.svg" width="23" height="20" />
);
}
@ -189,7 +189,7 @@ module.exports = React.createClass({
if (wantPlaceholder) {
return (
<div className="mx_RoomStatusBar_typingIndicatorAvatars">
{this._renderTypingIndicatorAvatars(this.props.whoIsTypingLimit)}
{ this._renderTypingIndicatorAvatars(this.props.whoIsTypingLimit) }
</div>
);
}
@ -221,8 +221,8 @@ module.exports = React.createClass({
if (othersCount > 0) {
avatars.push(
<span className="mx_RoomStatusBar_typingIndicatorRemaining" key="others">
+{othersCount}
</span>
+{ othersCount }
</span>,
);
}
@ -240,12 +240,12 @@ module.exports = React.createClass({
if (this.state.syncState === "ERROR") {
return (
<div className="mx_RoomStatusBar_connectionLostBar">
<img src="img/warning.svg" width="24" height="23" title="/!\ " alt="/!\ "/>
<img src="img/warning.svg" width="24" height="23" title="/!\ " alt="/!\ " />
<div className="mx_RoomStatusBar_connectionLostBar_title">
{_t('Connectivity to the server has been lost.')}
{ _t('Connectivity to the server has been lost.') }
</div>
<div className="mx_RoomStatusBar_connectionLostBar_desc">
{_t('Sent messages will be stored until your connection has returned.')}
{ _t('Sent messages will be stored until your connection has returned.') }
</div>
</div>
);
@ -254,18 +254,18 @@ module.exports = React.createClass({
if (this.props.unsentMessageError) {
return (
<div className="mx_RoomStatusBar_connectionLostBar">
<img src="img/warning.svg" width="24" height="23" title="/!\ " alt="/!\ "/>
<img src="img/warning.svg" width="24" height="23" title="/!\ " alt="/!\ " />
<div className="mx_RoomStatusBar_connectionLostBar_title">
{ this.props.unsentMessageError }
</div>
<div className="mx_RoomStatusBar_connectionLostBar_desc">
{_tJsx("<a>Resend all</a> or <a>cancel all</a> now. You can also select individual messages to resend or cancel.",
{ _tJsx("<a>Resend all</a> or <a>cancel all</a> now. You can also select individual messages to resend or cancel.",
[/<a>(.*?)<\/a>/, /<a>(.*?)<\/a>/],
[
(sub) => <a className="mx_RoomStatusBar_resend_link" key="resend" onClick={ this.props.onResendAllClick }>{sub}</a>,
(sub) => <a className="mx_RoomStatusBar_resend_link" key="cancel" onClick={ this.props.onCancelAllClick }>{sub}</a>,
]
)}
(sub) => <a className="mx_RoomStatusBar_resend_link" key="resend" onClick={this.props.onResendAllClick}>{ sub }</a>,
(sub) => <a className="mx_RoomStatusBar_resend_link" key="cancel" onClick={this.props.onCancelAllClick}>{ sub }</a>,
],
) }
</div>
</div>
);
@ -275,24 +275,24 @@ module.exports = React.createClass({
// set when you've scrolled up
if (this.props.numUnreadMessages) {
// MUST use var name "count" for pluralization to kick in
var unreadMsgs = _t("%(count)s new messages", {count: this.props.numUnreadMessages});
const unreadMsgs = _t("%(count)s new messages", {count: this.props.numUnreadMessages});
return (
<div className="mx_RoomStatusBar_unreadMessagesBar"
onClick={ this.props.onScrollToBottomClick }>
{unreadMsgs}
onClick={this.props.onScrollToBottomClick}>
{ unreadMsgs }
</div>
);
}
const typingString = WhoIsTyping.whoIsTypingString(
this.state.usersTyping,
this.props.whoIsTypingLimit
this.props.whoIsTypingLimit,
);
if (typingString) {
return (
<div className="mx_RoomStatusBar_typingBar">
<EmojiText>{typingString}</EmojiText>
<EmojiText>{ typingString }</EmojiText>
</div>
);
}
@ -300,7 +300,7 @@ module.exports = React.createClass({
if (this.props.hasActiveCall) {
return (
<div className="mx_RoomStatusBar_callBar">
<b>{_t('Active call')}</b>
<b>{ _t('Active call') }</b>
</div>
);
}
@ -310,15 +310,15 @@ module.exports = React.createClass({
render: function() {
var content = this._getContent();
var indicator = this._getIndicator(this.state.usersTyping.length > 0);
const content = this._getContent();
const indicator = this._getIndicator(this.state.usersTyping.length > 0);
return (
<div className="mx_RoomStatusBar">
<div className="mx_RoomStatusBar_indicator">
{indicator}
{ indicator }
</div>
{content}
{ content }
</div>
);
},

View File

@ -22,25 +22,25 @@ limitations under the License.
import shouldHideEvent from "../../shouldHideEvent";
var React = require("react");
var ReactDOM = require("react-dom");
const React = require("react");
const ReactDOM = require("react-dom");
import Promise from 'bluebird';
var classNames = require("classnames");
var Matrix = require("matrix-js-sdk");
const classNames = require("classnames");
const Matrix = require("matrix-js-sdk");
import { _t } from '../../languageHandler';
var UserSettingsStore = require('../../UserSettingsStore');
var MatrixClientPeg = require("../../MatrixClientPeg");
var ContentMessages = require("../../ContentMessages");
var Modal = require("../../Modal");
var sdk = require('../../index');
var CallHandler = require('../../CallHandler');
var Resend = require("../../Resend");
var dis = require("../../dispatcher");
var Tinter = require("../../Tinter");
var rate_limited_func = require('../../ratelimitedfunc');
var ObjectUtils = require('../../ObjectUtils');
var Rooms = require('../../Rooms');
const UserSettingsStore = require('../../UserSettingsStore');
const MatrixClientPeg = require("../../MatrixClientPeg");
const ContentMessages = require("../../ContentMessages");
const Modal = require("../../Modal");
const sdk = require('../../index');
const CallHandler = require('../../CallHandler');
const Resend = require("../../Resend");
const dis = require("../../dispatcher");
const Tinter = require("../../Tinter");
const rate_limited_func = require('../../ratelimitedfunc');
const ObjectUtils = require('../../ObjectUtils');
const Rooms = require('../../Rooms');
import KeyCode from '../../KeyCode';
@ -49,7 +49,7 @@ import UserProvider from '../../autocomplete/UserProvider';
import RoomViewStore from '../../stores/RoomViewStore';
import RoomScrollStateStore from '../../stores/RoomScrollStateStore';
let DEBUG = false;
const DEBUG = false;
let debuglog = function() {};
const BROWSER_SUPPORTS_SANDBOX = 'sandbox' in document.createElement('iframe');
@ -117,12 +117,16 @@ module.exports = React.createClass({
guestsCanJoin: false,
canPeek: false,
showApps: false,
isPeeking: false,
// error object, as from the matrix client/server API
// If we failed to load information about the room,
// store the error here.
roomLoadError: null,
// Have we sent a request to join the room that we're waiting to complete?
joining: false,
// this is true if we are fully scrolled-down, and are looking at
// the end of the live timeline. It has the effect of hiding the
// 'scroll to bottom' knob, among a couple of other things.
@ -185,10 +189,6 @@ module.exports = React.createClass({
shouldPeek: RoomViewStore.shouldPeek(),
};
// finished joining, start waiting for a room and show a spinner. See onRoom.
newState.waitingForRoom = this.state.joining && !newState.joining &&
!RoomViewStore.getJoinError();
// Temporary logging to diagnose https://github.com/vector-im/riot-web/issues/4307
console.log(
'RVS update:',
@ -197,7 +197,6 @@ module.exports = React.createClass({
'loading?', newState.roomLoading,
'joining?', newState.joining,
'initial?', initial,
'waiting?', newState.waitingForRoom,
'shouldPeek?', newState.shouldPeek,
);
@ -268,6 +267,7 @@ module.exports = React.createClass({
console.log("Attempting to peek into room %s", roomId);
this.setState({
peekLoading: true,
isPeeking: true, // this will change to false if peeking fails
});
MatrixClientPeg.get().peekInRoom(roomId).then((room) => {
this.setState({
@ -276,6 +276,11 @@ module.exports = React.createClass({
});
this._onRoomLoaded(room);
}, (err) => {
// Stop peeking if anything went wrong
this.setState({
isPeeking: false,
});
// This won't necessarily be a MatrixError, but we duck-type
// here and say if it's got an 'errcode' key with the right value,
// it means we can't peek.
@ -292,6 +297,7 @@ module.exports = React.createClass({
} else if (room) {
// Stop peeking because we have joined this room previously
MatrixClientPeg.get().stopPeeking();
this.setState({isPeeking: false});
}
},
@ -309,10 +315,10 @@ module.exports = React.createClass({
},
componentDidMount: function() {
var call = this._getCallForRoom();
var callState = call ? call.call_state : "ended";
const call = this._getCallForRoom();
const callState = call ? call.call_state : "ended";
this.setState({
callState: callState
callState: callState,
});
this._updateConfCallNotification();
@ -329,9 +335,8 @@ module.exports = React.createClass({
this.state.room.getJoinedMembers().length == 1 &&
this.state.room.getLiveTimeline() &&
this.state.room.getLiveTimeline().getEvents() &&
this.state.room.getLiveTimeline().getEvents().length <= 6)
{
var inviteBox = document.getElementById("mx_SearchableEntityList_query");
this.state.room.getLiveTimeline().getEvents().length <= 6) {
const inviteBox = document.getElementById("mx_SearchableEntityList_query");
setTimeout(function() {
if (inviteBox) {
inviteBox.focus();
@ -347,7 +352,7 @@ module.exports = React.createClass({
componentDidUpdate: function() {
if (this.refs.roomView) {
var roomView = ReactDOM.findDOMNode(this.refs.roomView);
const roomView = ReactDOM.findDOMNode(this.refs.roomView);
if (!roomView.ondrop) {
roomView.addEventListener('drop', this.onDrop);
roomView.addEventListener('dragover', this.onDragOver);
@ -374,7 +379,7 @@ module.exports = React.createClass({
// is really just for hygiene - we're going to be
// deleted anyway, so it doesn't matter if the event listeners
// don't get cleaned up.
var roomView = ReactDOM.findDOMNode(this.refs.roomView);
const roomView = ReactDOM.findDOMNode(this.refs.roomView);
roomView.removeEventListener('drop', this.onDrop);
roomView.removeEventListener('dragover', this.onDragOver);
roomView.removeEventListener('dragleave', this.onDragLeaveOrEnd);
@ -480,8 +485,7 @@ module.exports = React.createClass({
if (call) {
callState = call.call_state;
}
else {
} else {
callState = "ended";
}
@ -593,17 +597,17 @@ module.exports = React.createClass({
},
_calculatePeekRules: function(room) {
var guestAccessEvent = room.currentState.getStateEvents("m.room.guest_access", "");
const guestAccessEvent = room.currentState.getStateEvents("m.room.guest_access", "");
if (guestAccessEvent && guestAccessEvent.getContent().guest_access === "can_join") {
this.setState({
guestsCanJoin: true
guestsCanJoin: true,
});
}
var historyVisibility = room.currentState.getStateEvents("m.room.history_visibility", "");
const historyVisibility = room.currentState.getStateEvents("m.room.history_visibility", "");
if (historyVisibility && historyVisibility.getContent().history_visibility === "world_readable") {
this.setState({
canPeek: true
canPeek: true,
});
}
},
@ -612,35 +616,35 @@ module.exports = React.createClass({
// console.log("_updatePreviewUrlVisibility");
// check our per-room overrides
var roomPreviewUrls = room.getAccountData("org.matrix.room.preview_urls");
const roomPreviewUrls = room.getAccountData("org.matrix.room.preview_urls");
if (roomPreviewUrls && roomPreviewUrls.getContent().disable !== undefined) {
this.setState({
showUrlPreview: !roomPreviewUrls.getContent().disable
showUrlPreview: !roomPreviewUrls.getContent().disable,
});
return;
}
// check our global disable override
var userRoomPreviewUrls = MatrixClientPeg.get().getAccountData("org.matrix.preview_urls");
const userRoomPreviewUrls = MatrixClientPeg.get().getAccountData("org.matrix.preview_urls");
if (userRoomPreviewUrls && userRoomPreviewUrls.getContent().disable) {
this.setState({
showUrlPreview: false
showUrlPreview: false,
});
return;
}
// check the room state event
var roomStatePreviewUrls = room.currentState.getStateEvents('org.matrix.room.preview_urls', '');
const roomStatePreviewUrls = room.currentState.getStateEvents('org.matrix.room.preview_urls', '');
if (roomStatePreviewUrls && roomStatePreviewUrls.getContent().disable) {
this.setState({
showUrlPreview: false
showUrlPreview: false,
});
return;
}
// otherwise, we assume they're on.
this.setState({
showUrlPreview: true
showUrlPreview: true,
});
},
@ -650,18 +654,17 @@ module.exports = React.createClass({
}
this.setState({
room: room,
waitingForRoom: false,
}, () => {
this._onRoomLoaded(room);
});
},
updateTint: function() {
var room = this.state.room;
const room = this.state.room;
if (!room) return;
var color_scheme_event = room.getAccountData("org.matrix.room.color_scheme");
var color_scheme = {};
const color_scheme_event = room.getAccountData("org.matrix.room.color_scheme");
let color_scheme = {};
if (color_scheme_event) {
color_scheme = color_scheme_event.getContent();
// XXX: we should validate the event
@ -679,12 +682,11 @@ module.exports = React.createClass({
onRoomAccountData: function(event, room) {
if (room.roomId == this.state.roomId) {
if (event.getType() === "org.matrix.room.color_scheme") {
var color_scheme = event.getContent();
const color_scheme = event.getContent();
// XXX: we should validate the event
console.log("Tinter.tint from onRoomAccountData");
Tinter.tint(color_scheme.primary_color, color_scheme.secondary_color);
}
else if (event.getType() === "org.matrix.room.preview_urls") {
} else if (event.getType() === "org.matrix.room.preview_urls") {
this._updatePreviewUrlVisibility(room);
}
}
@ -706,14 +708,7 @@ module.exports = React.createClass({
onRoomMemberMembership: function(ev, member, oldMembership) {
if (member.userId == MatrixClientPeg.get().credentials.userId) {
if (member.membership === 'join') {
this.setState({
waitingForRoom: false,
});
} else {
this.forceUpdate();
}
this.forceUpdate();
}
},
@ -730,7 +725,7 @@ module.exports = React.createClass({
// if we are now a member of the room, where we were not before, that
// means we have finished joining a room we were previously peeking
// into.
var me = MatrixClientPeg.get().credentials.userId;
const me = MatrixClientPeg.get().credentials.userId;
if (this.state.joining && this.state.room.hasMembershipState(me, "join")) {
// Having just joined a room, check to see if it looks like a DM room, and if so,
// mark it as one. This is to work around the fact that some clients don't support
@ -748,6 +743,17 @@ module.exports = React.createClass({
_getUnsentMessageError: function(room) {
const unsentMessages = this._getUnsentMessages(room);
if (!unsentMessages.length) return "";
if (
unsentMessages.length === 1 &&
unsentMessages[0].error &&
unsentMessages[0].error.data &&
unsentMessages[0].error.data.error &&
unsentMessages[0].error.name !== "UnknownDeviceError"
) {
return unsentMessages[0].error.data.error;
}
for (const event of unsentMessages) {
if (!event.error || event.error.name !== "UnknownDeviceError") {
return _t("Some of your messages have not been sent.");
@ -764,18 +770,18 @@ module.exports = React.createClass({
},
_updateConfCallNotification: function() {
var room = this.state.room;
const room = this.state.room;
if (!room || !this.props.ConferenceHandler) {
return;
}
var confMember = room.getMember(
this.props.ConferenceHandler.getConferenceUserIdForRoom(room.roomId)
const confMember = room.getMember(
this.props.ConferenceHandler.getConferenceUserIdForRoom(room.roomId),
);
if (!confMember) {
return;
}
var confCall = this.props.ConferenceHandler.getConferenceCallForRoom(confMember.roomId);
const confCall = this.props.ConferenceHandler.getConferenceCallForRoom(confMember.roomId);
// A conf call notification should be displayed if there is an ongoing
// conf call but this cilent isn't a part of it.
@ -783,7 +789,7 @@ module.exports = React.createClass({
displayConfCallNotification: (
(!confCall || confCall.call_state === "ended") &&
confMember.membership === "join"
)
),
});
},
@ -798,7 +804,7 @@ module.exports = React.createClass({
if (this.state.searchResults.next_batch) {
debuglog("requesting more search results");
var searchPromise = MatrixClientPeg.get().backPaginateRoomEventsSearch(
const searchPromise = MatrixClientPeg.get().backPaginateRoomEventsSearch(
this.state.searchResults);
return this._handleSearchResult(searchPromise);
} else {
@ -893,8 +899,7 @@ module.exports = React.createClass({
numUnreadMessages: 0,
atEndOfLiveTimeline: true,
});
}
else {
} else {
this.setState({
atEndOfLiveTimeline: false,
});
@ -908,10 +913,10 @@ module.exports = React.createClass({
ev.dataTransfer.dropEffect = 'none';
var items = ev.dataTransfer.items;
const items = ev.dataTransfer.items;
if (items.length == 1) {
if (items[0].kind == 'file') {
this.setState({ draggingFile : true });
this.setState({ draggingFile: true });
ev.dataTransfer.dropEffect = 'copy';
}
}
@ -920,8 +925,8 @@ module.exports = React.createClass({
onDrop: function(ev) {
ev.stopPropagation();
ev.preventDefault();
this.setState({ draggingFile : false });
var files = ev.dataTransfer.files;
this.setState({ draggingFile: false });
const files = ev.dataTransfer.files;
if (files.length == 1) {
this.uploadFile(files[0]);
}
@ -930,7 +935,7 @@ module.exports = React.createClass({
onDragLeaveOrEnd: function(ev) {
ev.stopPropagation();
ev.preventDefault();
this.setState({ draggingFile : false });
this.setState({ draggingFile: false });
},
uploadFile: function(file) {
@ -940,7 +945,7 @@ module.exports = React.createClass({
}
ContentMessages.sendContentToRoom(
file, this.state.room.roomId, MatrixClientPeg.get()
file, this.state.room.roomId, MatrixClientPeg.get(),
).done(undefined, (error) => {
if (error.name === "UnknownDeviceError") {
dis.dispatch({
@ -979,19 +984,19 @@ module.exports = React.createClass({
// todo: should cancel any previous search requests.
this.searchId = new Date().getTime();
var filter;
let filter;
if (scope === "Room") {
filter = {
// XXX: it's unintuitive that the filter for searching doesn't have the same shape as the v2 filter API :(
rooms: [
this.state.room.roomId
]
this.state.room.roomId,
],
};
}
debuglog("sending search request");
var searchPromise = MatrixClientPeg.get().searchRoomEvents({
const searchPromise = MatrixClientPeg.get().searchRoomEvents({
filter: filter,
term: term,
});
@ -999,11 +1004,11 @@ module.exports = React.createClass({
},
_handleSearchResult: function(searchPromise) {
var self = this;
const self = this;
// keep a record of the current search id, so that if the search terms
// change before we get a response, we can ignore the results.
var localSearchId = this.searchId;
const localSearchId = this.searchId;
this.setState({
searchInProgress: true,
@ -1022,7 +1027,7 @@ module.exports = React.createClass({
// In either case, we want to highlight the literal search term
// whether it was used by the search engine or not.
var highlights = results.highlights;
let highlights = results.highlights;
if (highlights.indexOf(self.state.searchTerm) < 0) {
highlights = highlights.concat(self.state.searchTerm);
}
@ -1030,14 +1035,15 @@ module.exports = React.createClass({
// For overlapping highlights,
// favour longer (more specific) terms first
highlights = highlights.sort(function(a, b) {
return b.length - a.length; });
return b.length - a.length;
});
self.setState({
searchHighlights: highlights,
searchResults: results,
});
}, function(error) {
var ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
console.error("Search failed: " + error);
Modal.createTrackedDialog('Search failed', '', ErrorDialog, {
title: _t("Search failed"),
@ -1045,17 +1051,17 @@ module.exports = React.createClass({
});
}).finally(function() {
self.setState({
searchInProgress: false
searchInProgress: false,
});
});
},
getSearchResultTiles: function() {
var EventTile = sdk.getComponent('rooms.EventTile');
var SearchResultTile = sdk.getComponent('rooms.SearchResultTile');
var Spinner = sdk.getComponent("elements.Spinner");
const EventTile = sdk.getComponent('rooms.EventTile');
const SearchResultTile = sdk.getComponent('rooms.SearchResultTile');
const Spinner = sdk.getComponent("elements.Spinner");
var cli = MatrixClientPeg.get();
const cli = MatrixClientPeg.get();
// XXX: todo: merge overlapping results somehow?
// XXX: why doesn't searching on name work?
@ -1065,7 +1071,7 @@ module.exports = React.createClass({
return [];
}
var ret = [];
const ret = [];
if (this.state.searchInProgress) {
ret.push(<li key="search-spinner">
@ -1077,32 +1083,32 @@ module.exports = React.createClass({
if (this.state.searchResults.results.length == 0) {
ret.push(<li key="search-top-marker">
<h2 className="mx_RoomView_topMarker">{ _t("No results") }</h2>
</li>
</li>,
);
} else {
ret.push(<li key="search-top-marker">
<h2 className="mx_RoomView_topMarker">{ _t("No more results") }</h2>
</li>
</li>,
);
}
}
// once dynamic content in the search results load, make the scrollPanel check
// the scroll offsets.
var onWidgetLoad = () => {
var scrollPanel = this.refs.searchResultsPanel;
const onWidgetLoad = () => {
const scrollPanel = this.refs.searchResultsPanel;
if (scrollPanel) {
scrollPanel.checkScroll();
}
};
var lastRoomId;
let lastRoomId;
for (var i = this.state.searchResults.results.length - 1; i >= 0; i--) {
var result = this.state.searchResults.results[i];
for (let i = this.state.searchResults.results.length - 1; i >= 0; i--) {
const result = this.state.searchResults.results[i];
var mxEv = result.context.getEvent();
var roomId = mxEv.getRoomId();
const mxEv = result.context.getEvent();
const roomId = mxEv.getRoomId();
if (!EventTile.haveTileForEvent(mxEv)) {
// XXX: can this ever happen? It will make the result count
@ -1112,13 +1118,13 @@ module.exports = React.createClass({
if (this.state.searchScope === 'All') {
if(roomId != lastRoomId) {
var room = cli.getRoom(roomId);
const room = cli.getRoom(roomId);
// XXX: if we've left the room, we might not know about
// it. We should tell the js sdk to go and find out about
// it. But that's not an issue currently, as synapse only
// returns results for rooms we're joined to.
var roomName = room ? room.name : _t("Unknown room %(roomId)s", { roomId: roomId });
const roomName = room ? room.name : _t("Unknown room %(roomId)s", { roomId: roomId });
ret.push(<li key={mxEv.getId() + "-room"}>
<h1>{ _t("Room") }: { roomName }</h1>
@ -1127,13 +1133,13 @@ module.exports = React.createClass({
}
}
var resultLink = "#/room/"+roomId+"/"+mxEv.getId();
const resultLink = "#/room/"+roomId+"/"+mxEv.getId();
ret.push(<SearchResultTile key={mxEv.getId()}
searchResult={result}
searchHighlights={this.state.searchHighlights}
resultLink={resultLink}
onWidgetLoad={onWidgetLoad}/>);
onWidgetLoad={onWidgetLoad} />);
}
return ret;
},
@ -1149,38 +1155,37 @@ module.exports = React.createClass({
uploadingRoomSettings: true,
});
var newName = this.refs.header.getEditedName();
const newName = this.refs.header.getEditedName();
if (newName !== undefined) {
this.refs.room_settings.setName(newName);
}
var newTopic = this.refs.header.getEditedTopic();
const newTopic = this.refs.header.getEditedTopic();
if (newTopic !== undefined) {
this.refs.room_settings.setTopic(newTopic);
}
this.refs.room_settings.save().then((results) => {
var fails = results.filter(function(result) { return result.state !== "fulfilled"; });
const fails = results.filter(function(result) { return result.state !== "fulfilled"; });
console.log("Settings saved with %s errors", fails.length);
if (fails.length) {
fails.forEach(function(result) {
console.error(result.reason);
});
var ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
Modal.createTrackedDialog('Failed to save room settings', '', ErrorDialog, {
title: _t("Failed to save settings"),
description: fails.map(function(result) { return result.reason; }).join("\n"),
});
// still editing room settings
}
else {
} else {
this.setState({
editingRoomSettings: false
editingRoomSettings: false,
});
}
}).finally(() => {
this.setState({
uploadingRoomSettings: false,
editingRoomSettings: false
editingRoomSettings: false,
});
}).done();
},
@ -1211,8 +1216,8 @@ module.exports = React.createClass({
MatrixClientPeg.get().forget(this.state.room.roomId).done(function() {
dis.dispatch({ action: 'view_next_room' });
}, function(err) {
var errCode = err.errcode || _t("unknown error code");
var ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
const errCode = err.errcode || _t("unknown error code");
const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
Modal.createTrackedDialog('Failed to forget room', '', ErrorDialog, {
title: _t("Error"),
description: _t("Failed to forget room %(errCode)s", { errCode: errCode }),
@ -1221,20 +1226,20 @@ module.exports = React.createClass({
},
onRejectButtonClicked: function(ev) {
var self = this;
const self = this;
this.setState({
rejecting: true
rejecting: true,
});
MatrixClientPeg.get().leave(this.state.roomId).done(function() {
dis.dispatch({ action: 'view_next_room' });
self.setState({
rejecting: false
rejecting: false,
});
}, function(error) {
console.error("Failed to reject invite: %s", error);
var msg = error.message ? error.message : JSON.stringify(error);
var ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
const msg = error.message ? error.message : JSON.stringify(error);
const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
Modal.createTrackedDialog('Failed to reject invite', '', ErrorDialog, {
title: _t("Failed to reject invite"),
description: msg,
@ -1242,7 +1247,7 @@ module.exports = React.createClass({
self.setState({
rejecting: false,
rejectError: error
rejectError: error,
});
});
},
@ -1302,7 +1307,7 @@ module.exports = React.createClass({
// restored when we switch back to it.
//
_getScrollState: function() {
var messagePanel = this.refs.messagePanel;
const messagePanel = this.refs.messagePanel;
if (!messagePanel) return null;
// if we're following the live timeline, we want to return null; that
@ -1317,7 +1322,7 @@ module.exports = React.createClass({
return null;
}
var scrollState = messagePanel.getScrollState();
const scrollState = messagePanel.getScrollState();
if (scrollState.stuckAtBottom) {
// we don't really expect to be in this state, but it will
@ -1344,7 +1349,7 @@ module.exports = React.createClass({
// a maxHeight on the underlying remote video tag.
// header + footer + status + give us at least 120px of scrollback at all times.
var auxPanelMaxHeight = window.innerHeight -
let auxPanelMaxHeight = window.innerHeight -
(83 + // height of RoomHeader
36 + // height of the status area
72 + // minimum height of the message compmoser
@ -1363,26 +1368,26 @@ module.exports = React.createClass({
onFullscreenClick: function() {
dis.dispatch({
action: 'video_fullscreen',
fullscreen: true
fullscreen: true,
}, true);
},
onMuteAudioClick: function() {
var call = this._getCallForRoom();
const call = this._getCallForRoom();
if (!call) {
return;
}
var newState = !call.isMicrophoneMuted();
const newState = !call.isMicrophoneMuted();
call.setMicrophoneMuted(newState);
this.forceUpdate(); // TODO: just update the voip buttons
},
onMuteVideoClick: function() {
var call = this._getCallForRoom();
const call = this._getCallForRoom();
if (!call) {
return;
}
var newState = !call.isLocalVideoMuted();
const newState = !call.isLocalVideoMuted();
call.setLocalVideoMuted(newState);
this.forceUpdate(); // TODO: just update the voip buttons
},
@ -1418,7 +1423,7 @@ module.exports = React.createClass({
* We pass it down to the scroll panel.
*/
handleScrollKey: function(ev) {
var panel;
let panel;
if(this.refs.searchResultsPanel) {
panel = this.refs.searchResultsPanel;
} else if(this.refs.messagePanel) {
@ -1463,10 +1468,6 @@ module.exports = React.createClass({
const Loader = sdk.getComponent("elements.Spinner");
const TimelinePanel = sdk.getComponent("structures.TimelinePanel");
// Whether the preview bar spinner should be shown. We do this when joining or
// when waiting for a room to be returned by js-sdk when joining
const previewBarSpinner = this.state.joining || this.state.waitingForRoom;
if (!this.state.room) {
if (this.state.roomLoading || this.state.peekLoading) {
return (
@ -1492,15 +1493,15 @@ module.exports = React.createClass({
<RoomHeader ref="header"
room={this.state.room}
oobData={this.props.oobData}
collapsedRhs={ this.props.collapsedRhs }
collapsedRhs={this.props.collapsedRhs}
/>
<div className="mx_RoomView_auxPanel">
<RoomPreviewBar onJoinClick={ this.onJoinButtonClicked }
onForgetClick={ this.onForgetClick }
onRejectClick={ this.onRejectThreepidInviteButtonClicked }
canPreview={ false } error={ this.state.roomLoadError }
<RoomPreviewBar onJoinClick={this.onJoinButtonClicked}
onForgetClick={this.onForgetClick}
onRejectClick={this.onRejectThreepidInviteButtonClicked}
canPreview={false} error={this.state.roomLoadError}
roomAlias={roomAlias}
spinner={previewBarSpinner}
spinner={this.state.joining}
inviterName={inviterName}
invitedEmail={invitedEmail}
room={this.state.room}
@ -1512,8 +1513,8 @@ module.exports = React.createClass({
}
}
var myUserId = MatrixClientPeg.get().credentials.userId;
var myMember = this.state.room.getMember(myUserId);
const myUserId = MatrixClientPeg.get().credentials.userId;
const myMember = this.state.room.getMember(myUserId);
if (myMember && myMember.membership == 'invite') {
if (this.state.joining || this.state.rejecting) {
return (
@ -1522,7 +1523,7 @@ module.exports = React.createClass({
</div>
);
} else {
var inviteEvent = myMember.events.member;
const inviteEvent = myMember.events.member;
var inviterName = inviteEvent.sender ? inviteEvent.sender.name : inviteEvent.getSender();
// We deliberately don't try to peek into invites, even if we have permission to peek
@ -1535,15 +1536,15 @@ module.exports = React.createClass({
<RoomHeader
ref="header"
room={this.state.room}
collapsedRhs={ this.props.collapsedRhs }
collapsedRhs={this.props.collapsedRhs}
/>
<div className="mx_RoomView_auxPanel">
<RoomPreviewBar onJoinClick={ this.onJoinButtonClicked }
onForgetClick={ this.onForgetClick }
onRejectClick={ this.onRejectButtonClicked }
inviterName={ inviterName }
canPreview={ false }
spinner={previewBarSpinner}
<RoomPreviewBar onJoinClick={this.onJoinButtonClicked}
onForgetClick={this.onForgetClick}
onRejectClick={this.onRejectButtonClicked}
inviterName={inviterName}
canPreview={false}
spinner={this.state.joining}
room={this.state.room}
/>
</div>
@ -1556,24 +1557,24 @@ module.exports = React.createClass({
// We have successfully loaded this room, and are not previewing.
// Display the "normal" room view.
var call = this._getCallForRoom();
var inCall = false;
const call = this._getCallForRoom();
let inCall = false;
if (call && (this.state.callState !== 'ended' && this.state.callState !== 'ringing')) {
inCall = true;
}
var scrollheader_classes = classNames({
const scrollheader_classes = classNames({
mx_RoomView_scrollheader: true,
});
var statusBar;
let statusBar;
let isStatusAreaExpanded = true;
if (ContentMessages.getCurrentUploads().length > 0) {
var UploadBar = sdk.getComponent('structures.UploadBar');
const UploadBar = sdk.getComponent('structures.UploadBar');
statusBar = <UploadBar room={this.state.room} />;
} else if (!this.state.searchResults) {
var RoomStatusBar = sdk.getComponent('structures.RoomStatusBar');
const RoomStatusBar = sdk.getComponent('structures.RoomStatusBar');
isStatusAreaExpanded = this.state.statusBarVisible;
statusBar = <RoomStatusBar
room={this.state.room}
@ -1596,12 +1597,12 @@ module.exports = React.createClass({
if (this.state.editingRoomSettings) {
aux = <RoomSettings ref="room_settings" onSaveClick={this.onSettingsSaveClick} onCancelClick={this.onCancelClick} room={this.state.room} />;
} else if (this.state.uploadingRoomSettings) {
aux = <Loader/>;
aux = <Loader />;
} else if (this.state.forwardingEvent !== null) {
aux = <ForwardMessage onCancelClick={this.onCancelClick} />;
} else if (this.state.searching) {
hideCancel = true; // has own cancel
aux = <SearchBar ref="search_bar" searchInProgress={this.state.searchInProgress } onCancelClick={this.onCancelSearchClick} onSearch={this.onSearch}/>;
aux = <SearchBar ref="search_bar" searchInProgress={this.state.searchInProgress} onCancelClick={this.onCancelSearchClick} onSearch={this.onSearch} />;
} else if (!myMember || myMember.membership !== "join") {
// We do have a room object for this room, but we're not currently in it.
// We may have a 3rd party invite to it.
@ -1616,9 +1617,9 @@ module.exports = React.createClass({
hideCancel = true;
aux = (
<RoomPreviewBar onJoinClick={this.onJoinButtonClicked}
onForgetClick={ this.onForgetClick }
onForgetClick={this.onForgetClick}
onRejectClick={this.onRejectThreepidInviteButtonClicked}
spinner={previewBarSpinner}
spinner={this.state.joining}
inviterName={inviterName}
invitedEmail={invitedEmail}
canPreview={this.state.canPeek}
@ -1627,7 +1628,7 @@ module.exports = React.createClass({
);
}
var auxPanel = (
const auxPanel = (
<AuxPanel ref="auxPanel" room={this.state.room}
userId={MatrixClientPeg.get().credentials.userId}
conferenceHandler={this.props.ConferenceHandler}
@ -1640,8 +1641,8 @@ module.exports = React.createClass({
</AuxPanel>
);
var messageComposer, searchInfo;
var canSpeak = (
let messageComposer, searchInfo;
const canSpeak = (
// joined and not showing search results
myMember && (myMember.membership == 'join') && !this.state.searchResults
);
@ -1652,8 +1653,8 @@ module.exports = React.createClass({
onResize={this.onChildResize}
uploadFile={this.uploadFile}
callState={this.state.callState}
opacity={ this.props.opacity }
showApps={ this.state.showApps }
opacity={this.props.opacity}
showApps={this.state.showApps}
/>;
}
@ -1661,19 +1662,19 @@ module.exports = React.createClass({
// in this.state if this is what RoomHeader desires?
if (this.state.searchResults) {
searchInfo = {
searchTerm : this.state.searchTerm,
searchScope : this.state.searchScope,
searchCount : this.state.searchResults.count,
searchTerm: this.state.searchTerm,
searchScope: this.state.searchScope,
searchCount: this.state.searchResults.count,
};
}
if (inCall) {
var zoomButton, voiceMuteButton, videoMuteButton;
let zoomButton, voiceMuteButton, videoMuteButton;
if (call.type === "video") {
zoomButton = (
<div className="mx_RoomView_voipButton" onClick={this.onFullscreenClick} title={ _t("Fill screen") }>
<TintableSvg src="img/fullscreen.svg" width="29" height="22" style={{ marginTop: 1, marginRight: 4 }}/>
<div className="mx_RoomView_voipButton" onClick={this.onFullscreenClick} title={_t("Fill screen")}>
<TintableSvg src="img/fullscreen.svg" width="29" height="22" style={{ marginTop: 1, marginRight: 4 }} />
</div>
);
@ -1681,14 +1682,14 @@ module.exports = React.createClass({
<div className="mx_RoomView_voipButton" onClick={this.onMuteVideoClick}>
<TintableSvg src={call.isLocalVideoMuted() ? "img/video-unmute.svg" : "img/video-mute.svg"}
alt={call.isLocalVideoMuted() ? _t("Click to unmute video") : _t("Click to mute video")}
width="31" height="27"/>
width="31" height="27" />
</div>;
}
voiceMuteButton =
<div className="mx_RoomView_voipButton" onClick={this.onMuteAudioClick}>
<TintableSvg src={call.isMicrophoneMuted() ? "img/voice-unmute.svg" : "img/voice-mute.svg"}
alt={call.isMicrophoneMuted() ? _t("Click to unmute audio") : _t("Click to mute audio")}
width="21" height="26"/>
width="21" height="26" />
</div>;
// wrap the existing status bar into a 'callStatusBar' which adds more knobs.
@ -1698,25 +1699,25 @@ module.exports = React.createClass({
{ videoMuteButton }
{ zoomButton }
{ statusBar }
<TintableSvg className="mx_RoomView_voipChevron" src="img/voip-chevron.svg" width="22" height="17"/>
<TintableSvg className="mx_RoomView_voipChevron" src="img/voip-chevron.svg" width="22" height="17" />
</div>;
}
// if we have search results, we keep the messagepanel (so that it preserves its
// scroll state), but hide it.
var searchResultsPanel;
var hideMessagePanel = false;
let searchResultsPanel;
let hideMessagePanel = false;
if (this.state.searchResults) {
searchResultsPanel = (
<ScrollPanel ref="searchResultsPanel"
className="mx_RoomView_messagePanel mx_RoomView_searchResultsPanel"
onFillRequest={ this.onSearchResultsFillRequest }
onResize={ this.onSearchResultsResize }
onFillRequest={this.onSearchResultsFillRequest}
onResize={this.onSearchResultsResize}
style={{ opacity: this.props.opacity }}
>
<li className={scrollheader_classes}></li>
{this.getSearchResultTiles()}
{ this.getSearchResultTiles() }
</ScrollPanel>
);
hideMessagePanel = true;
@ -1731,26 +1732,26 @@ module.exports = React.createClass({
}
// console.log("ShowUrlPreview for %s is %s", this.state.room.roomId, this.state.showUrlPreview);
var messagePanel = (
const messagePanel = (
<TimelinePanel ref={this._gatherTimelinePanelRef}
timelineSet={this.state.room.getUnfilteredTimelineSet()}
showReadReceipts={!UserSettingsStore.getSyncedSetting('hideReadReceipts', false)}
manageReadReceipts={true}
manageReadMarkers={true}
manageReadReceipts={!this.state.isPeeking}
manageReadMarkers={!this.state.isPeeking}
hidden={hideMessagePanel}
highlightedEventId={highlightedEventId}
eventId={this.state.initialEventId}
eventPixelOffset={this.state.initialEventPixelOffset}
onScroll={ this.onMessageListScroll }
onReadMarkerUpdated={ this._updateTopUnreadMessagesBar }
showUrlPreview = { this.state.showUrlPreview }
opacity={ this.props.opacity }
onScroll={this.onMessageListScroll}
onReadMarkerUpdated={this._updateTopUnreadMessagesBar}
showUrlPreview = {this.state.showUrlPreview}
opacity={this.props.opacity}
className="mx_RoomView_messagePanel"
/>);
var topUnreadMessagesBar = null;
let topUnreadMessagesBar = null;
if (this.state.showTopUnreadMessagesBar) {
var TopUnreadMessagesBar = sdk.getComponent('rooms.TopUnreadMessagesBar');
const TopUnreadMessagesBar = sdk.getComponent('rooms.TopUnreadMessagesBar');
topUnreadMessagesBar = (
<div className="mx_RoomView_topUnreadMessagesBar mx_fadable" style={{ opacity: this.props.opacity }}>
<TopUnreadMessagesBar
@ -1766,13 +1767,13 @@ module.exports = React.createClass({
}
return (
<div className={ "mx_RoomView" + (inCall ? " mx_RoomView_inCall" : "") } ref="roomView">
<div className={"mx_RoomView" + (inCall ? " mx_RoomView_inCall" : "")} ref="roomView">
<RoomHeader ref="header" room={this.state.room} searchInfo={searchInfo}
oobData={this.props.oobData}
editing={this.state.editingRoomSettings}
saving={this.state.uploadingRoomSettings}
inRoom={myMember && myMember.membership === 'join'}
collapsedRhs={ this.props.collapsedRhs }
collapsedRhs={this.props.collapsedRhs}
onSearchClick={this.onSearchClick}
onSettingsClick={this.onSettingsClick}
onSaveClick={this.onSettingsSaveClick}

View File

@ -14,13 +14,13 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
var React = require("react");
var ReactDOM = require("react-dom");
var GeminiScrollbar = require('react-gemini-scrollbar');
const React = require("react");
const ReactDOM = require("react-dom");
const GeminiScrollbar = require('react-gemini-scrollbar');
import Promise from 'bluebird';
var KeyCode = require('../../KeyCode');
const KeyCode = require('../../KeyCode');
var DEBUG_SCROLL = false;
const DEBUG_SCROLL = false;
// var DEBUG_SCROLL = true;
// The amount of extra scroll distance to allow prior to unfilling.
@ -178,7 +178,7 @@ module.exports = React.createClass({
},
onScroll: function(ev) {
var sn = this._getScrollNode();
const sn = this._getScrollNode();
debuglog("Scroll event: offset now:", sn.scrollTop,
"_lastSetScroll:", this._lastSetScroll);
@ -238,7 +238,7 @@ module.exports = React.createClass({
// about whether the the content is scrolled down right now, irrespective of
// whether it will stay that way when the children update.
isAtBottom: function() {
var sn = this._getScrollNode();
const sn = this._getScrollNode();
// there seems to be some bug with flexbox/gemini/chrome/richvdh's
// understanding of the box model, wherein the scrollNode ends up 2
@ -281,7 +281,7 @@ module.exports = React.createClass({
// |#########| |
// `---------' -
_getExcessHeight: function(backwards) {
var sn = this._getScrollNode();
const sn = this._getScrollNode();
if (backwards) {
return sn.scrollTop - sn.clientHeight - UNPAGINATION_PADDING;
} else {
@ -295,7 +295,7 @@ module.exports = React.createClass({
return;
}
var sn = this._getScrollNode();
const sn = this._getScrollNode();
// if there is less than a screenful of messages above or below the
// viewport, try to get some more messages.
@ -377,7 +377,7 @@ module.exports = React.createClass({
// check if there is already a pending fill request. If not, set one off.
_maybeFill: function(backwards) {
var dir = backwards ? 'b' : 'f';
const dir = backwards ? 'b' : 'f';
if (this._pendingFillRequests[dir]) {
debuglog("ScrollPanel: Already a "+dir+" fill in progress - not starting another");
return;
@ -470,8 +470,8 @@ module.exports = React.createClass({
* mult: -1 to page up, +1 to page down
*/
scrollRelative: function(mult) {
var scrollNode = this._getScrollNode();
var delta = mult * scrollNode.clientHeight * 0.5;
const scrollNode = this._getScrollNode();
const delta = mult * scrollNode.clientHeight * 0.5;
this._setScrollTop(scrollNode.scrollTop + delta);
this._saveScrollState();
},
@ -535,7 +535,7 @@ module.exports = React.createClass({
this.scrollState = {
stuckAtBottom: false,
trackedScrollToken: scrollToken,
pixelOffset: pixelOffset
pixelOffset: pixelOffset,
};
// ... then make it so.
@ -546,10 +546,10 @@ module.exports = React.createClass({
// given offset in the window. A helper for _restoreSavedScrollState.
_scrollToToken: function(scrollToken, pixelOffset) {
/* find the dom node with the right scrolltoken */
var node;
var messages = this.refs.itemlist.children;
for (var i = messages.length-1; i >= 0; --i) {
var m = messages[i];
let node;
const messages = this.refs.itemlist.children;
for (let i = messages.length-1; i >= 0; --i) {
const m = messages[i];
// 'data-scroll-tokens' is a DOMString of comma-separated scroll tokens
// There might only be one scroll token
if (m.dataset.scrollTokens &&
@ -564,10 +564,10 @@ module.exports = React.createClass({
return;
}
var scrollNode = this._getScrollNode();
var wrapperRect = ReactDOM.findDOMNode(this).getBoundingClientRect();
var boundingRect = node.getBoundingClientRect();
var scrollDelta = boundingRect.bottom + pixelOffset - wrapperRect.bottom;
const scrollNode = this._getScrollNode();
const wrapperRect = ReactDOM.findDOMNode(this).getBoundingClientRect();
const boundingRect = node.getBoundingClientRect();
const scrollDelta = boundingRect.bottom + pixelOffset - wrapperRect.bottom;
debuglog("ScrollPanel: scrolling to token '" + scrollToken + "'+" +
pixelOffset + " (delta: "+scrollDelta+")");
@ -575,7 +575,6 @@ module.exports = React.createClass({
if(scrollDelta != 0) {
this._setScrollTop(scrollNode.scrollTop + scrollDelta);
}
},
_saveScrollState: function() {
@ -585,16 +584,16 @@ module.exports = React.createClass({
return;
}
var itemlist = this.refs.itemlist;
var wrapperRect = ReactDOM.findDOMNode(this).getBoundingClientRect();
var messages = itemlist.children;
const itemlist = this.refs.itemlist;
const wrapperRect = ReactDOM.findDOMNode(this).getBoundingClientRect();
const messages = itemlist.children;
let newScrollState = null;
for (var i = messages.length-1; i >= 0; --i) {
var node = messages[i];
for (let i = messages.length-1; i >= 0; --i) {
const node = messages[i];
if (!node.dataset.scrollTokens) continue;
var boundingRect = node.getBoundingClientRect();
const boundingRect = node.getBoundingClientRect();
newScrollState = {
stuckAtBottom: false,
trackedScrollToken: node.dataset.scrollTokens.split(',')[0],
@ -619,8 +618,8 @@ module.exports = React.createClass({
},
_restoreSavedScrollState: function() {
var scrollState = this.scrollState;
var scrollNode = this._getScrollNode();
const scrollState = this.scrollState;
const scrollNode = this._getScrollNode();
if (scrollState.stuckAtBottom) {
this._setScrollTop(Number.MAX_VALUE);
@ -631,9 +630,9 @@ module.exports = React.createClass({
},
_setScrollTop: function(scrollTop) {
var scrollNode = this._getScrollNode();
const scrollNode = this._getScrollNode();
var prevScroll = scrollNode.scrollTop;
const prevScroll = scrollNode.scrollTop;
// FF ignores attempts to set scrollTop to very large numbers
scrollNode.scrollTop = Math.min(scrollTop, scrollNode.scrollHeight);
@ -676,7 +675,7 @@ module.exports = React.createClass({
className={this.props.className} style={this.props.style}>
<div className="mx_RoomView_messageListWrapper">
<ol ref="itemlist" className="mx_RoomView_MessageList" aria-live="polite">
{this.props.children}
{ this.props.children }
</ol>
</div>
</GeminiScrollbar>

View File

@ -15,27 +15,27 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
var React = require('react');
var ReactDOM = require("react-dom");
const React = require('react');
const ReactDOM = require("react-dom");
import Promise from 'bluebird';
var Matrix = require("matrix-js-sdk");
var EventTimeline = Matrix.EventTimeline;
const Matrix = require("matrix-js-sdk");
const EventTimeline = Matrix.EventTimeline;
var sdk = require('../../index');
const sdk = require('../../index');
import { _t } from '../../languageHandler';
var MatrixClientPeg = require("../../MatrixClientPeg");
var dis = require("../../dispatcher");
var ObjectUtils = require('../../ObjectUtils');
var Modal = require("../../Modal");
var UserActivity = require("../../UserActivity");
var KeyCode = require('../../KeyCode');
const MatrixClientPeg = require("../../MatrixClientPeg");
const dis = require("../../dispatcher");
const ObjectUtils = require('../../ObjectUtils');
const Modal = require("../../Modal");
const UserActivity = require("../../UserActivity");
const KeyCode = require('../../KeyCode');
import UserSettingsStore from '../../UserSettingsStore';
var PAGINATE_SIZE = 20;
var INITIAL_SIZE = 20;
const PAGINATE_SIZE = 20;
const INITIAL_SIZE = 20;
var DEBUG = false;
const DEBUG = false;
if (DEBUG) {
// using bind means that we get to keep useful line numbers in the console
@ -260,7 +260,7 @@ var TimelinePanel = React.createClass({
dis.unregister(this.dispatcherRef);
var client = MatrixClientPeg.get();
const client = MatrixClientPeg.get();
if (client) {
client.removeListener("Room.timeline", this.onRoomTimeline);
client.removeListener("Room.timelineReset", this.onRoomTimelineReset);
@ -275,20 +275,20 @@ var TimelinePanel = React.createClass({
onMessageListUnfillRequest: function(backwards, scrollToken) {
// If backwards, unpaginate from the back (i.e. the start of the timeline)
let dir = backwards ? EventTimeline.BACKWARDS : EventTimeline.FORWARDS;
const dir = backwards ? EventTimeline.BACKWARDS : EventTimeline.FORWARDS;
debuglog("TimelinePanel: unpaginating events in direction", dir);
// All tiles are inserted by MessagePanel to have a scrollToken === eventId, and
// this particular event should be the first or last to be unpaginated.
let eventId = scrollToken;
const eventId = scrollToken;
let marker = this.state.events.findIndex(
const marker = this.state.events.findIndex(
(ev) => {
return ev.getId() === eventId;
}
},
);
let count = backwards ? marker + 1 : this.state.events.length - marker;
const count = backwards ? marker + 1 : this.state.events.length - marker;
if (count > 0) {
debuglog("TimelinePanel: Unpaginating", count, "in direction", dir);
@ -305,9 +305,9 @@ var TimelinePanel = React.createClass({
// set off a pagination request.
onMessageListFillRequest: function(backwards) {
var dir = backwards ? EventTimeline.BACKWARDS : EventTimeline.FORWARDS;
var canPaginateKey = backwards ? 'canBackPaginate' : 'canForwardPaginate';
var paginatingKey = backwards ? 'backPaginating' : 'forwardPaginating';
const dir = backwards ? EventTimeline.BACKWARDS : EventTimeline.FORWARDS;
const canPaginateKey = backwards ? 'canBackPaginate' : 'canForwardPaginate';
const paginatingKey = backwards ? 'backPaginating' : 'forwardPaginating';
if (!this.state[canPaginateKey]) {
debuglog("TimelinePanel: have given up", dir, "paginating this timeline");
@ -328,7 +328,7 @@ var TimelinePanel = React.createClass({
debuglog("TimelinePanel: paginate complete backwards:"+backwards+"; success:"+r);
var newState = {
const newState = {
[paginatingKey]: false,
[canPaginateKey]: r,
events: this._getEvents(),
@ -336,8 +336,8 @@ var TimelinePanel = React.createClass({
// moving the window in this direction may mean that we can now
// paginate in the other where we previously could not.
var otherDirection = backwards ? EventTimeline.FORWARDS : EventTimeline.BACKWARDS;
var canPaginateOtherWayKey = backwards ? 'canForwardPaginate' : 'canBackPaginate';
const otherDirection = backwards ? EventTimeline.FORWARDS : EventTimeline.BACKWARDS;
const canPaginateOtherWayKey = backwards ? 'canForwardPaginate' : 'canBackPaginate';
if (!this.state[canPaginateOtherWayKey] &&
this._timelineWindow.canPaginate(otherDirection)) {
debuglog('TimelinePanel: can now', otherDirection, 'paginate again');
@ -384,6 +384,9 @@ var TimelinePanel = React.createClass({
this.sendReadReceipt();
this.updateReadMarker();
break;
case 'ignore_state_changed':
this.forceUpdate();
break;
}
},
@ -417,15 +420,15 @@ var TimelinePanel = React.createClass({
this._timelineWindow.paginate(EventTimeline.FORWARDS, 1, false).done(() => {
if (this.unmounted) { return; }
var events = this._timelineWindow.getEvents();
var lastEv = events[events.length-1];
const events = this._timelineWindow.getEvents();
const lastEv = events[events.length-1];
// if we're at the end of the live timeline, append the pending events
if (this.props.timelineSet.room && !this._timelineWindow.canPaginate(EventTimeline.FORWARDS)) {
events.push(...this.props.timelineSet.room.getPendingEvents());
}
var updatedState = {events: events};
const updatedState = {events: events};
if (this.props.manageReadMarkers) {
// when a new event arrives when the user is not watching the
@ -436,8 +439,8 @@ var TimelinePanel = React.createClass({
// read-marker when a remote echo of an event we have just sent takes
// more than the timeout on userCurrentlyActive.
//
var myUserId = MatrixClientPeg.get().credentials.userId;
var sender = ev.sender ? ev.sender.userId : null;
const myUserId = MatrixClientPeg.get().credentials.userId;
const sender = ev.sender ? ev.sender.userId : null;
var callback = null;
if (sender != myUserId && !UserActivity.userCurrentlyActive()) {
updatedState.readMarkerVisible = true;
@ -643,7 +646,7 @@ var TimelinePanel = React.createClass({
// and we'll get confused when their ID changes and we can't figure out
// where the RM is pointing to. The read marker will be invisible for
// now anyway, so this doesn't really matter.
var lastDisplayedIndex = this._getLastDisplayedEventIndex({
const lastDisplayedIndex = this._getLastDisplayedEventIndex({
allowPartial: true,
ignoreEchoes: true,
});
@ -652,7 +655,7 @@ var TimelinePanel = React.createClass({
return;
}
var lastDisplayedEvent = this.state.events[lastDisplayedIndex];
const lastDisplayedEvent = this.state.events[lastDisplayedIndex];
this._setReadMarker(lastDisplayedEvent.getId(),
lastDisplayedEvent.getTs());
@ -673,7 +676,7 @@ var TimelinePanel = React.createClass({
// we call _timelineWindow.getEvents() rather than using
// this.state.events, because react batches the update to the latter, so it
// may not have been updated yet.
var events = this._timelineWindow.getEvents();
const events = this._timelineWindow.getEvents();
// first find where the current RM is
for (var i = 0; i < events.length; i++) {
@ -686,7 +689,7 @@ var TimelinePanel = React.createClass({
}
// now think about advancing it
var myUserId = MatrixClientPeg.get().credentials.userId;
const myUserId = MatrixClientPeg.get().credentials.userId;
for (i++; i < events.length; i++) {
var ev = events[i];
if (!ev.sender || ev.sender.userId != myUserId) {
@ -731,7 +734,7 @@ var TimelinePanel = React.createClass({
//
// a quick way to figure out if we've loaded the relevant event is
// simply to check if the messagepanel knows where the read-marker is.
var ret = this.refs.messagePanel.getReadMarkerPosition();
const ret = this.refs.messagePanel.getReadMarkerPosition();
if (ret !== null) {
// The messagepanel knows where the RM is, so we must have loaded
// the relevant event.
@ -752,13 +755,13 @@ var TimelinePanel = React.createClass({
forgetReadMarker: function() {
if (!this.props.manageReadMarkers) return;
var rmId = this._getCurrentReadReceipt();
const rmId = this._getCurrentReadReceipt();
// see if we know the timestamp for the rr event
var tl = this.props.timelineSet.getTimelineForEvent(rmId);
var rmTs;
const tl = this.props.timelineSet.getTimelineForEvent(rmId);
let rmTs;
if (tl) {
var event = tl.getEvents().find((e) => { return e.getId() == rmId; });
const event = tl.getEvents().find((e) => { return e.getId() == rmId; });
if (event) {
rmTs = event.getTs();
}
@ -798,7 +801,7 @@ var TimelinePanel = React.createClass({
if (!this.props.manageReadMarkers) return null;
if (!this.refs.messagePanel) return null;
var ret = this.refs.messagePanel.getReadMarkerPosition();
const ret = this.refs.messagePanel.getReadMarkerPosition();
if (ret !== null) {
return ret;
}
@ -841,8 +844,7 @@ var TimelinePanel = React.createClass({
// jump to the live timeline on ctrl-end, rather than the end of the
// timeline window.
if (ev.ctrlKey && !ev.shiftKey && !ev.altKey && !ev.metaKey &&
ev.keyCode == KeyCode.END)
{
ev.keyCode == KeyCode.END) {
this.jumpToLiveTimeline();
} else {
this.refs.messagePanel.handleScrollKey(ev);
@ -850,12 +852,12 @@ var TimelinePanel = React.createClass({
},
_initTimeline: function(props) {
var initialEvent = props.eventId;
var pixelOffset = props.eventPixelOffset;
const initialEvent = props.eventId;
const pixelOffset = props.eventPixelOffset;
// if a pixelOffset is given, it is relative to the bottom of the
// container. If not, put the event in the middle of the container.
var offsetBase = 1;
let offsetBase = 1;
if (pixelOffset == null) {
offsetBase = 0.5;
}
@ -884,7 +886,7 @@ var TimelinePanel = React.createClass({
MatrixClientPeg.get(), this.props.timelineSet,
{windowLimit: this.props.timelineCap});
var onLoaded = () => {
const onLoaded = () => {
this._reloadEvents();
// If we switched away from the room while there were pending
@ -919,15 +921,15 @@ var TimelinePanel = React.createClass({
});
};
var onError = (error) => {
const onError = (error) => {
this.setState({timelineLoading: false});
console.error(
`Error loading timeline panel at ${eventId}: ${error}`,
);
var msg = error.message ? error.message : JSON.stringify(error);
var ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
const msg = error.message ? error.message : JSON.stringify(error);
const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
var onFinished;
let onFinished;
// if we were given an event ID, then when the user closes the
// dialog, let's jump to the end of the timeline. If we weren't,
@ -942,7 +944,7 @@ var TimelinePanel = React.createClass({
});
};
}
var message = (error.errcode == 'M_FORBIDDEN')
const message = (error.errcode == 'M_FORBIDDEN')
? _t("Tried to load a specific point in this room's timeline, but you do not have permission to view the message in question.")
: _t("Tried to load a specific point in this room's timeline, but was unable to find it.");
Modal.createTrackedDialog('Failed to load timeline position', '', ErrorDialog, {
@ -952,7 +954,7 @@ var TimelinePanel = React.createClass({
});
};
var prom = this._timelineWindow.load(eventId, INITIAL_SIZE);
let prom = this._timelineWindow.load(eventId, INITIAL_SIZE);
// if we already have the event in question, TimelineWindow.load
// returns a resolved promise.
@ -993,7 +995,7 @@ var TimelinePanel = React.createClass({
// get the list of events from the timeline window and the pending event list
_getEvents: function() {
var events = this._timelineWindow.getEvents();
const events = this._timelineWindow.getEvents();
// if we're at the end of the live timeline, append the pending events
if (!this._timelineWindow.canPaginate(EventTimeline.FORWARDS)) {
@ -1004,7 +1006,7 @@ var TimelinePanel = React.createClass({
},
_indexForEventId: function(evId) {
for (var i = 0; i < this.state.events.length; ++i) {
for (let i = 0; i < this.state.events.length; ++i) {
if (evId == this.state.events[i].getId()) {
return i;
}
@ -1014,18 +1016,18 @@ var TimelinePanel = React.createClass({
_getLastDisplayedEventIndex: function(opts) {
opts = opts || {};
var ignoreOwn = opts.ignoreOwn || false;
var ignoreEchoes = opts.ignoreEchoes || false;
var allowPartial = opts.allowPartial || false;
const ignoreOwn = opts.ignoreOwn || false;
const ignoreEchoes = opts.ignoreEchoes || false;
const allowPartial = opts.allowPartial || false;
var messagePanel = this.refs.messagePanel;
const messagePanel = this.refs.messagePanel;
if (messagePanel === undefined) return null;
var wrapperRect = ReactDOM.findDOMNode(messagePanel).getBoundingClientRect();
var myUserId = MatrixClientPeg.get().credentials.userId;
const wrapperRect = ReactDOM.findDOMNode(messagePanel).getBoundingClientRect();
const myUserId = MatrixClientPeg.get().credentials.userId;
for (var i = this.state.events.length-1; i >= 0; --i) {
var ev = this.state.events[i];
for (let i = this.state.events.length-1; i >= 0; --i) {
const ev = this.state.events[i];
if (ignoreOwn && ev.sender && ev.sender.userId == myUserId) {
continue;
@ -1036,10 +1038,10 @@ var TimelinePanel = React.createClass({
continue;
}
var node = messagePanel.getNodeForEventId(ev.getId());
const node = messagePanel.getNodeForEventId(ev.getId());
if (!node) continue;
var boundingRect = node.getBoundingClientRect();
const boundingRect = node.getBoundingClientRect();
if ((allowPartial && boundingRect.top < wrapperRect.bottom) ||
(!allowPartial && boundingRect.bottom < wrapperRect.bottom)) {
return i;
@ -1057,18 +1059,18 @@ var TimelinePanel = React.createClass({
* SDK.
*/
_getCurrentReadReceipt: function(ignoreSynthesized) {
var client = MatrixClientPeg.get();
const client = MatrixClientPeg.get();
// the client can be null on logout
if (client == null) {
return null;
}
var myUserId = client.credentials.userId;
const myUserId = client.credentials.userId;
return this.props.timelineSet.room.getEventReadUpTo(myUserId, ignoreSynthesized);
},
_setReadMarker: function(eventId, eventTs, inhibitSetState) {
var roomId = this.props.timelineSet.room.roomId;
const roomId = this.props.timelineSet.room.roomId;
// don't update the state (and cause a re-render) if there is
// no change to the RM.
@ -1093,8 +1095,8 @@ var TimelinePanel = React.createClass({
},
render: function() {
var MessagePanel = sdk.getComponent("structures.MessagePanel");
var Loader = sdk.getComponent("elements.Spinner");
const MessagePanel = sdk.getComponent("structures.MessagePanel");
const Loader = sdk.getComponent("elements.Spinner");
// just show a spinner while the timeline loads.
//
@ -1109,7 +1111,7 @@ var TimelinePanel = React.createClass({
// exist.
if (this.state.timelineLoading) {
return (
<div className={ this.props.className + " mx_RoomView_messageListWrapper" }>
<div className={this.props.className + " mx_RoomView_messageListWrapper"}>
<Loader />
</div>
);
@ -1117,7 +1119,7 @@ var TimelinePanel = React.createClass({
if (this.state.events.length == 0 && !this.state.canBackPaginate && this.props.empty) {
return (
<div className={ this.props.className + " mx_RoomView_messageListWrapper" }>
<div className={this.props.className + " mx_RoomView_messageListWrapper"}>
<div className="mx_RoomView_empty">{ this.props.empty }</div>
</div>
);
@ -1131,7 +1133,7 @@ var TimelinePanel = React.createClass({
// forwards, otherwise if somebody hits the bottom of the loaded
// events when viewing historical messages, we get stuck in a loop
// of paginating our way through the entire history of the room.
var stickyBottom = !this._timelineWindow.canPaginate(EventTimeline.FORWARDS);
const stickyBottom = !this._timelineWindow.canPaginate(EventTimeline.FORWARDS);
// If the state is PREPARED, we're still waiting for the js-sdk to sync with
// the HS and fetch the latest events, so we are effectively forward paginating.
@ -1140,26 +1142,26 @@ var TimelinePanel = React.createClass({
);
return (
<MessagePanel ref="messagePanel"
hidden={ this.props.hidden }
backPaginating={ this.state.backPaginating }
forwardPaginating={ forwardPaginating }
events={ this.state.events }
highlightedEventId={ this.props.highlightedEventId }
readMarkerEventId={ this.state.readMarkerEventId }
readMarkerVisible={ this.state.readMarkerVisible }
suppressFirstDateSeparator={ this.state.canBackPaginate }
showUrlPreview={ this.props.showUrlPreview }
showReadReceipts={ this.props.showReadReceipts }
ourUserId={ MatrixClientPeg.get().credentials.userId }
stickyBottom={ stickyBottom }
onScroll={ this.onMessageListScroll }
onFillRequest={ this.onMessageListFillRequest }
onUnfillRequest={ this.onMessageListUnfillRequest }
opacity={ this.props.opacity }
isTwelveHour={ this.state.isTwelveHour }
alwaysShowTimestamps={ this.state.alwaysShowTimestamps }
className={ this.props.className }
tileShape={ this.props.tileShape }
hidden={this.props.hidden}
backPaginating={this.state.backPaginating}
forwardPaginating={forwardPaginating}
events={this.state.events}
highlightedEventId={this.props.highlightedEventId}
readMarkerEventId={this.state.readMarkerEventId}
readMarkerVisible={this.state.readMarkerVisible}
suppressFirstDateSeparator={this.state.canBackPaginate}
showUrlPreview={this.props.showUrlPreview}
showReadReceipts={this.props.showReadReceipts}
ourUserId={MatrixClientPeg.get().credentials.userId}
stickyBottom={stickyBottom}
onScroll={this.onMessageListScroll}
onFillRequest={this.onMessageListFillRequest}
onUnfillRequest={this.onMessageListUnfillRequest}
opacity={this.props.opacity}
isTwelveHour={this.state.isTwelveHour}
alwaysShowTimestamps={this.state.alwaysShowTimestamps}
className={this.props.className}
tileShape={this.props.tileShape}
/>
);
},

View File

@ -14,15 +14,15 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
var React = require('react');
var ContentMessages = require('../../ContentMessages');
var dis = require('../../dispatcher');
var filesize = require('filesize');
const React = require('react');
const ContentMessages = require('../../ContentMessages');
const dis = require('../../dispatcher');
const filesize = require('filesize');
import { _t } from '../../languageHandler';
module.exports = React.createClass({displayName: 'UploadBar',
propTypes: {
room: React.PropTypes.object
room: React.PropTypes.object,
},
componentDidMount: function() {
@ -46,7 +46,7 @@ module.exports = React.createClass({displayName: 'UploadBar',
},
render: function() {
var uploads = ContentMessages.getCurrentUploads();
const uploads = ContentMessages.getCurrentUploads();
// for testing UI... - also fix up the ContentMessages.getCurrentUploads().length
// check in RoomView
@ -62,8 +62,8 @@ module.exports = React.createClass({displayName: 'UploadBar',
return <div />;
}
var upload;
for (var i = 0; i < uploads.length; ++i) {
let upload;
for (let i = 0; i < uploads.length; ++i) {
if (uploads[i].roomId == this.props.room.roomId) {
upload = uploads[i];
break;
@ -73,32 +73,32 @@ module.exports = React.createClass({displayName: 'UploadBar',
return <div />;
}
var innerProgressStyle = {
width: ((upload.loaded / (upload.total || 1)) * 100) + '%'
const innerProgressStyle = {
width: ((upload.loaded / (upload.total || 1)) * 100) + '%',
};
var uploadedSize = filesize(upload.loaded);
var totalSize = filesize(upload.total);
let uploadedSize = filesize(upload.loaded);
const totalSize = filesize(upload.total);
if (uploadedSize.replace(/^.* /, '') === totalSize.replace(/^.* /, '')) {
uploadedSize = uploadedSize.replace(/ .*/, '');
}
// MUST use var name 'count' for pluralization to kick in
var uploadText = _t("Uploading %(filename)s and %(count)s others", {filename: upload.fileName, count: (uploads.length - 1)});
const uploadText = _t("Uploading %(filename)s and %(count)s others", {filename: upload.fileName, count: (uploads.length - 1)});
return (
<div className="mx_UploadBar">
<div className="mx_UploadBar_uploadProgressOuter">
<div className="mx_UploadBar_uploadProgressInner" style={innerProgressStyle}></div>
</div>
<img className="mx_UploadBar_uploadIcon mx_filterFlipColor" src="img/fileicon.png" width="17" height="22"/>
<img className="mx_UploadBar_uploadIcon mx_filterFlipColor" src="img/fileicon.png" width="17" height="22" />
<img className="mx_UploadBar_uploadCancel mx_filterFlipColor" src="img/cancel.svg" width="18" height="18"
onClick={function() { ContentMessages.cancelUpload(upload.promise); }}
/>
<div className="mx_UploadBar_uploadBytes">
{ uploadedSize } / { totalSize }
</div>
<div className="mx_UploadBar_uploadFilename">{uploadText}</div>
<div className="mx_UploadBar_uploadFilename">{ uploadText }</div>
</div>
);
}
},
});

View File

@ -32,7 +32,7 @@ const AddThreepid = require('../../AddThreepid');
const SdkConfig = require('../../SdkConfig');
import Analytics from '../../Analytics';
import AccessibleButton from '../views/elements/AccessibleButton';
import { _t } from '../../languageHandler';
import { _t, _td } from '../../languageHandler';
import * as languageHandler from '../../languageHandler';
import * as FormattingUtils from '../../utils/FormattingUtils';
@ -52,7 +52,7 @@ const gHVersionLabel = function(repo, token='') {
} else {
url = `https://github.com/${repo}/commit/${token.split('-')[0]}`;
}
return <a target="_blank" rel="noopener" href={url}>{token}</a>;
return <a target="_blank" rel="noopener" href={url}>{ token }</a>;
};
// Enumerate some simple 'flip a bit' UI settings (if any).
@ -63,51 +63,55 @@ const gHVersionLabel = function(repo, token='') {
const SETTINGS_LABELS = [
{
id: 'autoplayGifsAndVideos',
label: 'Autoplay GIFs and videos',
label: _td('Autoplay GIFs and videos'),
},
{
id: 'hideReadReceipts',
label: 'Hide read receipts',
label: _td('Hide read receipts'),
},
{
id: 'dontSendTypingNotifications',
label: "Don't send typing notifications",
label: _td("Don't send typing notifications"),
},
{
id: 'alwaysShowTimestamps',
label: 'Always show message timestamps',
label: _td('Always show message timestamps'),
},
{
id: 'showTwelveHourTimestamps',
label: 'Show timestamps in 12 hour format (e.g. 2:30pm)',
label: _td('Show timestamps in 12 hour format (e.g. 2:30pm)'),
},
{
id: 'hideJoinLeaves',
label: 'Hide join/leave messages (invites/kicks/bans unaffected)',
label: _td('Hide join/leave messages (invites/kicks/bans unaffected)'),
},
{
id: 'hideAvatarDisplaynameChanges',
label: 'Hide avatar and display name changes',
label: _td('Hide avatar and display name changes'),
},
{
id: 'useCompactLayout',
label: 'Use compact timeline layout',
label: _td('Use compact timeline layout'),
},
{
id: 'hideRedactions',
label: 'Hide removed messages',
label: _td('Hide removed messages'),
},
{
id: 'enableSyntaxHighlightLanguageDetection',
label: 'Enable automatic language detection for syntax highlighting',
label: _td('Enable automatic language detection for syntax highlighting'),
},
{
id: 'MessageComposerInput.autoReplaceEmoji',
label: 'Automatically replace plain text Emoji',
label: _td('Automatically replace plain text Emoji'),
},
{
id: 'MessageComposerInput.dontSuggestEmoji',
label: _td('Disable Emoji suggestions while typing'),
},
{
id: 'Pill.shouldHidePillAvatar',
label: 'Hide avatars in user and room mentions',
label: _td('Hide avatars in user and room mentions'),
},
/*
{
@ -120,7 +124,7 @@ const SETTINGS_LABELS = [
const ANALYTICS_SETTINGS_LABELS = [
{
id: 'analyticsOptOut',
label: 'Opt out of analytics',
label: _td('Opt out of analytics'),
fn: function(checked) {
Analytics[checked ? 'disable' : 'enable']();
},
@ -130,7 +134,7 @@ const ANALYTICS_SETTINGS_LABELS = [
const WEBRTC_SETTINGS_LABELS = [
{
id: 'webRtcForceTURN',
label: 'Disable Peer-to-Peer for 1:1 calls',
label: _td('Disable Peer-to-Peer for 1:1 calls'),
},
];
@ -139,7 +143,7 @@ const WEBRTC_SETTINGS_LABELS = [
const CRYPTO_SETTINGS_LABELS = [
{
id: 'blacklistUnverifiedDevices',
label: 'Never send encrypted messages to unverified devices from this device',
label: _td('Never send encrypted messages to unverified devices from this device'),
fn: function(checked) {
MatrixClientPeg.get().setGlobalBlacklistUnverifiedDevices(checked);
},
@ -162,16 +166,44 @@ const CRYPTO_SETTINGS_LABELS = [
const THEMES = [
{
id: 'theme',
label: 'Light theme',
label: _td('Light theme'),
value: 'light',
},
{
id: 'theme',
label: 'Dark theme',
label: _td('Dark theme'),
value: 'dark',
},
];
const IgnoredUser = React.createClass({
propTypes: {
userId: React.PropTypes.string.isRequired,
onUnignored: React.PropTypes.func.isRequired,
},
_onUnignoreClick: function() {
const ignoredUsers = MatrixClientPeg.get().getIgnoredUsers();
const index = ignoredUsers.indexOf(this.props.userId);
if (index !== -1) {
ignoredUsers.splice(index, 1);
MatrixClientPeg.get().setIgnoredUsers(ignoredUsers)
.then(() => this.props.onUnignored(this.props.userId));
} else this.props.onUnignored(this.props.userId);
},
render: function() {
return (
<li>
<AccessibleButton onClick={this._onUnignoreClick} className="mx_UserSettings_button mx_UserSettings_buttonSmall">
{ _t("Unignore") }
</AccessibleButton>
{ this.props.userId }
</li>
);
},
});
module.exports = React.createClass({
displayName: 'UserSettings',
@ -207,6 +239,7 @@ module.exports = React.createClass({
vectorVersion: undefined,
rejectingInvites: false,
mediaDevices: null,
ignoredUsers: [],
};
},
@ -228,6 +261,7 @@ module.exports = React.createClass({
}
this._refreshMediaDevices();
this._refreshIgnoredUsers();
// Bulk rejecting invites:
// /sync won't have had time to return when UserSettings re-renders from state changes, so getRooms()
@ -346,9 +380,22 @@ module.exports = React.createClass({
});
},
_refreshIgnoredUsers: function(userIdUnignored=null) {
const users = MatrixClientPeg.get().getIgnoredUsers();
if (userIdUnignored) {
const index = users.indexOf(userIdUnignored);
if (index !== -1) users.splice(index, 1);
}
this.setState({
ignoredUsers: users,
});
},
onAction: function(payload) {
if (payload.action === "notifier_enabled") {
this.forceUpdate();
} else if (payload.action === "ignore_state_changed") {
this._refreshIgnoredUsers();
}
},
@ -627,7 +674,7 @@ module.exports = React.createClass({
<div>
<h3>Referral</h3>
<div className="mx_UserSettings_section">
{_t("Refer a friend to Riot:")} <a href={href}>{href}</a>
{ _t("Refer a friend to Riot:") } <a href={href}>{ href }</a>
</div>
</div>
);
@ -646,7 +693,7 @@ module.exports = React.createClass({
_renderLanguageSetting: function() {
const LanguageDropdown = sdk.getComponent('views.elements.LanguageDropdown');
return <div>
<label htmlFor="languageSelector">{_t('Interface Language')}</label>
<label htmlFor="languageSelector">{ _t('Interface Language') }</label>
<LanguageDropdown ref="language" onOptionChange={this.onLanguageChange}
className="mx_UserSettings_language"
value={this.state.language}
@ -669,7 +716,7 @@ module.exports = React.createClass({
<table>
<tbody>
<tr>
<td><strong>{_t('Autocomplete Delay (ms):')}</strong></td>
<td><strong>{ _t('Autocomplete Delay (ms):') }</strong></td>
<td>
<input
type="number"
@ -690,8 +737,8 @@ module.exports = React.createClass({
return <div className="mx_UserSettings_toggle">
<input id="urlPreviewsDisabled"
type="checkbox"
defaultChecked={ UserSettingsStore.getUrlPreviewsDisabled() }
onChange={ this._onPreviewsDisabledChanged }
defaultChecked={UserSettingsStore.getUrlPreviewsDisabled()}
onChange={this._onPreviewsDisabledChanged}
/>
<label htmlFor="urlPreviewsDisabled">
{ _t("Disable inline URL previews by default") }
@ -712,13 +759,13 @@ module.exports = React.createClass({
if (setting.fn) setting.fn(e.target.checked);
};
return <div className="mx_UserSettings_toggle" key={ setting.id }>
<input id={ setting.id }
return <div className="mx_UserSettings_toggle" key={setting.id}>
<input id={setting.id}
type="checkbox"
defaultChecked={ this._syncedSettings[setting.id] }
onChange={ onChange }
defaultChecked={this._syncedSettings[setting.id]}
onChange={onChange}
/>
<label htmlFor={ setting.id }>
<label htmlFor={setting.id}>
{ _t(setting.label) }
</label>
</div>;
@ -737,16 +784,16 @@ module.exports = React.createClass({
value: setting.value,
});
};
return <div className="mx_UserSettings_toggle" key={ setting.id + "_" + setting.value }>
<input id={ setting.id + "_" + setting.value }
return <div className="mx_UserSettings_toggle" key={setting.id + "_" + setting.value}>
<input id={setting.id + "_" + setting.value}
type="radio"
name={ setting.id }
value={ setting.value }
checked={ this._syncedSettings[setting.id] === setting.value }
onChange={ onChange }
name={setting.id}
value={setting.value}
checked={this._syncedSettings[setting.id] === setting.value}
onChange={onChange}
/>
<label htmlFor={ setting.id + "_" + setting.value }>
{ setting.label }
<label htmlFor={setting.id + "_" + setting.value}>
{ _t(setting.label) }
</label>
</div>;
},
@ -782,10 +829,10 @@ module.exports = React.createClass({
<h3>{ _t("Cryptography") }</h3>
<div className="mx_UserSettings_section mx_UserSettings_cryptoSection">
<ul>
<li><label>{_t("Device ID:")}</label>
<span><code>{deviceId}</code></span></li>
<li><label>{_t("Device key:")}</label>
<span><code><b>{identityKey}</b></code></span></li>
<li><label>{ _t("Device ID:") }</label>
<span><code>{ deviceId }</code></span></li>
<li><label>{ _t("Device key:") }</label>
<span><code><b>{ identityKey }</b></code></span></li>
</ul>
{ importExportButtons }
</div>
@ -796,6 +843,26 @@ module.exports = React.createClass({
);
},
_renderIgnoredUsers: function() {
if (this.state.ignoredUsers.length > 0) {
const updateHandler = this._refreshIgnoredUsers;
return (
<div>
<h3>{ _t("Ignored Users") }</h3>
<div className="mx_UserSettings_section mx_UserSettings_ignoredUsersSection">
<ul>
{ this.state.ignoredUsers.map(function(userId) {
return (<IgnoredUser key={userId}
userId={userId}
onUnignored={updateHandler}></IgnoredUser>);
}) }
</ul>
</div>
</div>
);
} else return (<div />);
},
_renderLocalSetting: function(setting) {
// TODO: this ought to be a separate component so that we don't need
// to rebind the onChange each time we render
@ -804,13 +871,13 @@ module.exports = React.createClass({
if (setting.fn) setting.fn(e.target.checked);
};
return <div className="mx_UserSettings_toggle" key={ setting.id }>
<input id={ setting.id }
return <div className="mx_UserSettings_toggle" key={setting.id}>
<input id={setting.id}
type="checkbox"
defaultChecked={ this._localSettings[setting.id] }
onChange={ onChange }
defaultChecked={this._localSettings[setting.id]}
onChange={onChange}
/>
<label htmlFor={ setting.id }>
<label htmlFor={setting.id}>
{ _t(setting.label) }
</label>
</div>;
@ -820,8 +887,8 @@ module.exports = React.createClass({
const DevicesPanel = sdk.getComponent('settings.DevicesPanel');
return (
<div>
<h3>{_t("Devices")}</h3>
<DevicesPanel className="mx_UserSettings_section"/>
<h3>{ _t("Devices") }</h3>
<DevicesPanel className="mx_UserSettings_section" />
</div>
);
},
@ -836,7 +903,7 @@ module.exports = React.createClass({
<div className="mx_UserSettings_section">
<p>{ _t("Found a bug?") }</p>
<button className="mx_UserSettings_button danger"
onClick={this._onBugReportClicked}>{_t('Report it')}
onClick={this._onBugReportClicked}>{ _t('Report it') }
</button>
</div>
</div>
@ -844,13 +911,13 @@ module.exports = React.createClass({
},
_renderAnalyticsControl: function() {
if (!SdkConfig.get().piwik) return <div/>;
if (!SdkConfig.get().piwik) return <div />;
return <div>
<h3>{ _t('Analytics') }</h3>
<div className="mx_UserSettings_section">
{_t('Riot collects anonymous analytics to allow us to improve the application.')}
{ANALYTICS_SETTINGS_LABELS.map( this._renderLocalSetting )}
{ _t('Riot collects anonymous analytics to allow us to improve the application.') }
{ ANALYTICS_SETTINGS_LABELS.map( this._renderLocalSetting ) }
</div>
</div>;
},
@ -880,10 +947,10 @@ module.exports = React.createClass({
type="checkbox"
id={feature.id}
name={feature.id}
defaultChecked={ UserSettingsStore.isFeatureEnabled(feature.id) }
onChange={ onChange }
defaultChecked={UserSettingsStore.isFeatureEnabled(feature.id)}
onChange={onChange}
/>
<label htmlFor={feature.id}>{feature.name}</label>
<label htmlFor={feature.id}>{ feature.name }</label>
</div>);
});
@ -897,7 +964,7 @@ module.exports = React.createClass({
<h3>{ _t("Labs") }</h3>
<div className="mx_UserSettings_section">
<p>{ _t("These are experimental features that may break in unexpected ways") }. { _t("Use with caution") }.</p>
{features}
{ features }
</div>
</div>
);
@ -930,10 +997,10 @@ module.exports = React.createClass({
const platform = PlatformPeg.get();
if ('canSelfUpdate' in platform && platform.canSelfUpdate() && 'startUpdateCheck' in platform) {
return <div>
<h3>{_t('Updates')}</h3>
<h3>{ _t('Updates') }</h3>
<div className="mx_UserSettings_section">
<AccessibleButton className="mx_UserSettings_button" onClick={platform.startUpdateCheck}>
{_t('Check for update')}
{ _t('Check for update') }
</AccessibleButton>
</div>
</div>;
@ -959,7 +1026,7 @@ module.exports = React.createClass({
reject = (
<AccessibleButton className="mx_UserSettings_button danger"
onClick={onClick}>
{_t("Reject all %(invitedRooms)s invites", {invitedRooms: invitedRooms.length})}
{ _t("Reject all %(invitedRooms)s invites", {invitedRooms: invitedRooms.length}) }
</AccessibleButton>
);
}
@ -967,7 +1034,7 @@ module.exports = React.createClass({
return <div>
<h3>{ _t("Bulk Options") }</h3>
<div className="mx_UserSettings_section">
{reject}
{ reject }
</div>
</div>;
},
@ -985,7 +1052,7 @@ module.exports = React.createClass({
defaultChecked={settings['auto-launch']}
onChange={this._onAutoLaunchChanged}
/>
<label htmlFor="auto-launch">{_t('Start automatically after system login')}</label>
<label htmlFor="auto-launch">{ _t('Start automatically after system login') }</label>
</div>
</div>
</div>;
@ -997,7 +1064,7 @@ module.exports = React.createClass({
},
_mapWebRtcDevicesToSpans: function(devices) {
return devices.map((device) => <span key={device.deviceId}>{device.label}</span>);
return devices.map((device) => <span key={device.deviceId}>{ device.label }</span>);
},
_setAudioInput: function(deviceId) {
@ -1033,15 +1100,15 @@ module.exports = React.createClass({
if (this.state.mediaDevices === false) {
return (
<p className="mx_UserSettings_link" onClick={this._requestMediaPermissions}>
{_t('Missing Media Permissions, click here to request.')}
{ _t('Missing Media Permissions, click here to request.') }
</p>
);
} else if (!this.state.mediaDevices) return;
const Dropdown = sdk.getComponent('elements.Dropdown');
let microphoneDropdown = <p>{_t('No Microphones detected')}</p>;
let webcamDropdown = <p>{_t('No Webcams detected')}</p>;
let microphoneDropdown = <p>{ _t('No Microphones detected') }</p>;
let webcamDropdown = <p>{ _t('No Webcams detected') }</p>;
const defaultOption = {
deviceId: '',
@ -1058,12 +1125,12 @@ module.exports = React.createClass({
}
microphoneDropdown = <div>
<h4>{_t('Microphone')}</h4>
<h4>{ _t('Microphone') }</h4>
<Dropdown
className="mx_UserSettings_webRtcDevices_dropdown"
value={this.state.activeAudioInput || defaultInput}
onOptionChange={this._setAudioInput}>
{this._mapWebRtcDevicesToSpans(audioInputs)}
{ this._mapWebRtcDevicesToSpans(audioInputs) }
</Dropdown>
</div>;
}
@ -1078,25 +1145,25 @@ module.exports = React.createClass({
}
webcamDropdown = <div>
<h4>{_t('Camera')}</h4>
<h4>{ _t('Camera') }</h4>
<Dropdown
className="mx_UserSettings_webRtcDevices_dropdown"
value={this.state.activeVideoInput || defaultInput}
onOptionChange={this._setVideoInput}>
{this._mapWebRtcDevicesToSpans(videoInputs)}
{ this._mapWebRtcDevicesToSpans(videoInputs) }
</Dropdown>
</div>;
}
return <div>
{microphoneDropdown}
{webcamDropdown}
{ microphoneDropdown }
{ webcamDropdown }
</div>;
},
_renderWebRtcSettings: function() {
return <div>
<h3>{_t('VoIP')}</h3>
<h3>{ _t('VoIP') }</h3>
<div className="mx_UserSettings_section">
{ WEBRTC_SETTINGS_LABELS.map(this._renderLocalSetting) }
{ this._renderWebRtcDeviceSettings() }
@ -1162,7 +1229,7 @@ module.exports = React.createClass({
return (
<div className="mx_UserSettings_profileTableRow" key={pidIndex}>
<div className="mx_UserSettings_profileLabelCell">
<label htmlFor={id}>{this.nameForMedium(val.medium)}</label>
<label htmlFor={id}>{ this.nameForMedium(val.medium) }</label>
</div>
<div className="mx_UserSettings_profileInputCell">
<input type="text" key={val.address} id={id}
@ -1170,7 +1237,7 @@ module.exports = React.createClass({
/>
</div>
<div className="mx_UserSettings_threepidButton mx_filterFlipColor">
<img src="img/cancel-small.svg" width="14" height="14" alt={ _t("Remove") }
<img src="img/cancel-small.svg" width="14" height="14" alt={_t("Remove")}
onClick={onRemoveClick} />
</div>
</div>
@ -1183,16 +1250,16 @@ module.exports = React.createClass({
addEmailSection = (
<div className="mx_UserSettings_profileTableRow" key="_newEmail">
<div className="mx_UserSettings_profileLabelCell">
<label>{_t('Email')}</label>
<label>{ _t('Email') }</label>
</div>
<div className="mx_UserSettings_profileInputCell">
<EditableText
ref="add_email_input"
className="mx_UserSettings_editable"
placeholderClassName="mx_UserSettings_threepidPlaceholder"
placeholder={ _t("Add email address") }
blurToCancel={ false }
onValueChanged={ this._onAddEmailEditFinished } />
placeholder={_t("Add email address")}
blurToCancel={false}
onValueChanged={this._onAddEmailEditFinished} />
</div>
<div className="mx_UserSettings_threepidButton mx_filterFlipColor">
<img src="img/plus.svg" width="14" height="14" alt={_t("Add")} onClick={this._addEmail} />
@ -1240,8 +1307,8 @@ module.exports = React.createClass({
return (
<div className="mx_UserSettings">
<SimpleRoomHeader
title={ _t("Settings") }
onCancelClick={ this.props.onClose }
title={_t("Settings")}
onCancelClick={this.props.onClose}
/>
<GeminiScrollbar className="mx_UserSettings_body"
@ -1259,21 +1326,21 @@ module.exports = React.createClass({
<ChangeDisplayName />
</div>
</div>
{threepidsSection}
{ threepidsSection }
</div>
<div className="mx_UserSettings_avatarPicker">
<div onClick={ this.onAvatarPickerClick }>
<div onClick={this.onAvatarPickerClick}>
<ChangeAvatar ref="changeAvatar" initialAvatarUrl={avatarUrl}
showUploadSection={false} className="mx_UserSettings_avatarPicker_img"/>
showUploadSection={false} className="mx_UserSettings_avatarPicker_img" />
</div>
<div className="mx_UserSettings_avatarPicker_edit">
<label htmlFor="avatarInput" ref="file_label">
<img src="img/camera.svg" className="mx_filterFlipColor"
alt={ _t("Upload avatar") } title={ _t("Upload avatar") }
alt={_t("Upload avatar")} title={_t("Upload avatar")}
width="17" height="15" />
</label>
<input id="avatarInput" type="file" onChange={this.onAvatarSelected}/>
<input id="avatarInput" type="file" onChange={this.onAvatarSelected} />
</div>
</div>
</div>
@ -1290,36 +1357,37 @@ module.exports = React.createClass({
</div> : null
}
{accountJsx}
{ accountJsx }
</div>
{this._renderReferral()}
{ this._renderReferral() }
{notificationArea}
{ notificationArea }
{this._renderUserInterfaceSettings()}
{this._renderLabs()}
{this._renderWebRtcSettings()}
{this._renderDevicesPanel()}
{this._renderCryptoInfo()}
{this._renderBulkOptions()}
{this._renderBugReport()}
{ this._renderUserInterfaceSettings() }
{ this._renderLabs() }
{ this._renderWebRtcSettings() }
{ this._renderDevicesPanel() }
{ this._renderCryptoInfo() }
{ this._renderIgnoredUsers() }
{ this._renderBulkOptions() }
{ this._renderBugReport() }
{PlatformPeg.get().isElectron() && this._renderElectronSettings()}
{ PlatformPeg.get().isElectron() && this._renderElectronSettings() }
{this._renderAnalyticsControl()}
{ this._renderAnalyticsControl() }
<h3>{ _t("Advanced") }</h3>
<div className="mx_UserSettings_section">
<div className="mx_UserSettings_advanced">
{ _t("Logged in as:") } {this._me}
{ _t("Logged in as:") } { this._me }
</div>
<div className="mx_UserSettings_advanced">
{_t('Access Token:')}
{ _t('Access Token:') }
<span className="mx_UserSettings_advanced_spoiler"
onClick={this._showSpoiler}
data-spoiler={ MatrixClientPeg.get().getAccessToken() }>
data-spoiler={MatrixClientPeg.get().getAccessToken()}>
&lt;{ _t("click to reveal") }&gt;
</span>
</div>
@ -1330,23 +1398,23 @@ module.exports = React.createClass({
{ _t("Identity Server is") } { MatrixClientPeg.get().getIdentityServerUrl() }
</div>
<div className="mx_UserSettings_advanced">
{_t('matrix-react-sdk version:')} {(REACT_SDK_VERSION !== '<local>')
{ _t('matrix-react-sdk version:') } { (REACT_SDK_VERSION !== '<local>')
? gHVersionLabel('matrix-org/matrix-react-sdk', REACT_SDK_VERSION)
: REACT_SDK_VERSION
}<br/>
{_t('riot-web version:')} {(this.state.vectorVersion !== undefined)
}<br />
{ _t('riot-web version:') } { (this.state.vectorVersion !== undefined)
? gHVersionLabel('vector-im/riot-web', this.state.vectorVersion)
: 'unknown'
}<br/>
{ _t("olm version:") } {olmVersionString}<br/>
}<br />
{ _t("olm version:") } { olmVersionString }<br />
</div>
</div>
{this._renderCheckUpdate()}
{ this._renderCheckUpdate() }
{this._renderClearCache()}
{ this._renderClearCache() }
{this._renderDeactivateAccount()}
{ this._renderDeactivateAccount() }
</GeminiScrollbar>
</div>

View File

@ -17,13 +17,13 @@ limitations under the License.
'use strict';
var React = require('react');
const React = require('react');
import { _t } from '../../../languageHandler';
var sdk = require('../../../index');
var Modal = require("../../../Modal");
var MatrixClientPeg = require('../../../MatrixClientPeg');
const sdk = require('../../../index');
const Modal = require("../../../Modal");
const MatrixClientPeg = require('../../../MatrixClientPeg');
var PasswordReset = require("../../../PasswordReset");
const PasswordReset = require("../../../PasswordReset");
module.exports = React.createClass({
displayName: 'ForgotPassword',
@ -35,30 +35,30 @@ module.exports = React.createClass({
customIsUrl: React.PropTypes.string,
onLoginClick: React.PropTypes.func,
onRegisterClick: React.PropTypes.func,
onComplete: React.PropTypes.func.isRequired
onComplete: React.PropTypes.func.isRequired,
},
getInitialState: function() {
return {
enteredHomeserverUrl: this.props.customHsUrl || this.props.defaultHsUrl,
enteredIdentityServerUrl: this.props.customIsUrl || this.props.defaultIsUrl,
progress: null
progress: null,
};
},
submitPasswordReset: function(hsUrl, identityUrl, email, password) {
this.setState({
progress: "sending_email"
progress: "sending_email",
});
this.reset = new PasswordReset(hsUrl, identityUrl);
this.reset.resetPassword(email, password).done(() => {
this.setState({
progress: "sent_email"
progress: "sent_email",
});
}, (err) => {
this.showErrorDialog(_t('Failed to send email') + ": " + err.message);
this.setState({
progress: null
progress: null,
});
});
},
@ -81,15 +81,12 @@ module.exports = React.createClass({
if (!this.state.email) {
this.showErrorDialog(_t('The email address linked to your account must be entered.'));
}
else if (!this.state.password || !this.state.password2) {
} else if (!this.state.password || !this.state.password2) {
this.showErrorDialog(_t('A new password must be entered.'));
}
else if (this.state.password !== this.state.password2) {
} else if (this.state.password !== this.state.password2) {
this.showErrorDialog(_t('New passwords must match each other.'));
}
else {
var QuestionDialog = sdk.getComponent("dialogs.QuestionDialog");
} else {
const QuestionDialog = sdk.getComponent("dialogs.QuestionDialog");
Modal.createTrackedDialog('Forgot Password Warning', '', QuestionDialog, {
title: _t('Warning!'),
description:
@ -99,7 +96,7 @@ module.exports = React.createClass({
'end-to-end encryption keys on all devices, ' +
'making encrypted chat history unreadable, ' +
'unless you first export your room keys and re-import ' +
'them afterwards. In future this will be improved.'
'them afterwards. In future this will be improved.',
) }
</div>,
button: _t('Continue'),
@ -107,13 +104,13 @@ module.exports = React.createClass({
<button className="mx_Dialog_primary"
onClick={this._onExportE2eKeysClicked}>
{ _t('Export E2E room keys') }
</button>
</button>,
],
onFinished: (confirmed) => {
if (confirmed) {
this.submitPasswordReset(
this.state.enteredHomeserverUrl, this.state.enteredIdentityServerUrl,
this.state.email, this.state.password
this.state.email, this.state.password,
);
}
},
@ -133,7 +130,7 @@ module.exports = React.createClass({
onInputChanged: function(stateKey, ev) {
this.setState({
[stateKey]: ev.target.value
[stateKey]: ev.target.value,
});
},
@ -149,7 +146,7 @@ module.exports = React.createClass({
},
showErrorDialog: function(body, title) {
var ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
Modal.createTrackedDialog('Forgot Password Error', '', ErrorDialog, {
title: title,
description: body,
@ -157,37 +154,34 @@ module.exports = React.createClass({
},
render: function() {
var LoginHeader = sdk.getComponent("login.LoginHeader");
var LoginFooter = sdk.getComponent("login.LoginFooter");
var ServerConfig = sdk.getComponent("login.ServerConfig");
var Spinner = sdk.getComponent("elements.Spinner");
const LoginHeader = sdk.getComponent("login.LoginHeader");
const LoginFooter = sdk.getComponent("login.LoginFooter");
const ServerConfig = sdk.getComponent("login.ServerConfig");
const Spinner = sdk.getComponent("elements.Spinner");
var resetPasswordJsx;
let resetPasswordJsx;
if (this.state.progress === "sending_email") {
resetPasswordJsx = <Spinner />;
}
else if (this.state.progress === "sent_email") {
} else if (this.state.progress === "sent_email") {
resetPasswordJsx = (
<div>
{ _t('An email has been sent to') } {this.state.email}. { _t("Once you've followed the link it contains, click below") }.
{ _t('An email has been sent to') } { this.state.email }. { _t("Once you've followed the link it contains, click below") }.
<br />
<input className="mx_Login_submit" type="button" onClick={this.onVerify}
value={ _t('I have verified my email address') } />
value={_t('I have verified my email address')} />
</div>
);
}
else if (this.state.progress === "complete") {
} else if (this.state.progress === "complete") {
resetPasswordJsx = (
<div>
<p>{ _t('Your password has been reset') }.</p>
<p>{ _t('You have been logged out of all devices and will no longer receive push notifications. To re-enable notifications, sign in again on each device') }.</p>
<input className="mx_Login_submit" type="button" onClick={this.props.onComplete}
value={ _t('Return to login screen') } />
value={_t('Return to login screen')} />
</div>
);
}
else {
} else {
resetPasswordJsx = (
<div>
<div className="mx_Login_prompt">
@ -199,21 +193,21 @@ module.exports = React.createClass({
name="reset_email" // define a name so browser's password autofill gets less confused
value={this.state.email}
onChange={this.onInputChanged.bind(this, "email")}
placeholder={ _t('Email address') } autoFocus />
placeholder={_t('Email address')} autoFocus />
<br />
<input className="mx_Login_field" ref="pass" type="password"
name="reset_password"
value={this.state.password}
onChange={this.onInputChanged.bind(this, "password")}
placeholder={ _t('New password') } />
placeholder={_t('New password')} />
<br />
<input className="mx_Login_field" ref="pass" type="password"
name="reset_password_confirm"
value={this.state.password2}
onChange={this.onInputChanged.bind(this, "password2")}
placeholder={ _t('Confirm your new password') } />
placeholder={_t('Confirm your new password')} />
<br />
<input className="mx_Login_submit" type="submit" value={ _t('Send Reset Email') } />
<input className="mx_Login_submit" type="submit" value={_t('Send Reset Email')} />
</form>
<ServerConfig ref="serverConfig"
withToggleButton={true}
@ -222,11 +216,11 @@ module.exports = React.createClass({
customHsUrl={this.props.customHsUrl}
customIsUrl={this.props.customIsUrl}
onServerConfigChange={this.onServerConfigChange}
delayTimeMs={0}/>
delayTimeMs={0} />
<div className="mx_Login_error">
</div>
<a className="mx_Login_create" onClick={this.props.onLoginClick} href="#">
{_t('Return to login screen')}
{ _t('Return to login screen') }
</a>
<a className="mx_Login_create" onClick={this.props.onRegisterClick} href="#">
{ _t('Create an account') }
@ -242,9 +236,9 @@ module.exports = React.createClass({
<div className="mx_Login">
<div className="mx_Login_box">
<LoginHeader />
{resetPasswordJsx}
{ resetPasswordJsx }
</div>
</div>
);
}
},
});

View File

@ -134,7 +134,7 @@ module.exports = React.createClass({
},
_onLoginAsGuestClick: function() {
var self = this;
const self = this;
self.setState({
busy: true,
errorText: null,
@ -156,7 +156,7 @@ module.exports = React.createClass({
});
}).finally(function() {
self.setState({
busy: false
busy: false,
});
}).done();
},
@ -183,8 +183,8 @@ module.exports = React.createClass({
},
onServerConfigChange: function(config) {
var self = this;
let newState = {
const self = this;
const newState = {
errorText: null, // reset err messages
};
if (config.hsUrl !== undefined) {
@ -199,13 +199,13 @@ module.exports = React.createClass({
},
_initLoginLogic: function(hsUrl, isUrl) {
var self = this;
const self = this;
hsUrl = hsUrl || this.state.enteredHomeserverUrl;
isUrl = isUrl || this.state.enteredIdentityServerUrl;
var fallbackHsUrl = hsUrl == this.props.defaultHsUrl ? this.props.fallbackHsUrl : null;
const fallbackHsUrl = hsUrl == this.props.defaultHsUrl ? this.props.fallbackHsUrl : null;
var loginLogic = new Login(hsUrl, isUrl, fallbackHsUrl, {
const loginLogic = new Login(hsUrl, isUrl, fallbackHsUrl, {
defaultDeviceDisplayName: this.props.defaultDeviceDisplayName,
});
this._loginLogic = loginLogic;
@ -259,15 +259,15 @@ module.exports = React.createClass({
{ _tJsx("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>.",
/<a>(.*?)<\/a>/,
(sub) => { return <a href="https://www.google.com/search?&q=enable%20unsafe%20scripts">{ sub }</a>; }
)}
(sub) => { return <a href="https://www.google.com/search?&q=enable%20unsafe%20scripts">{ sub }</a>; },
) }
</span>;
} else {
errorText = <span>
{ _tJsx("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>(.*?)<\/a>/,
(sub) => { return <a href={this.state.enteredHomeserverUrl}>{ sub }</a>; }
)}
(sub) => { return <a href={this.state.enteredHomeserverUrl}>{ sub }</a>; },
) }
</span>;
}
}
@ -290,6 +290,7 @@ module.exports = React.createClass({
onPhoneNumberChanged={this.onPhoneNumberChanged}
onForgotPasswordClick={this.props.onForgotPasswordClick}
loginIncorrect={this.state.loginIncorrect}
hsUrl={this.state.enteredHomeserverUrl}
/>
);
case 'm.login.cas':
@ -303,7 +304,7 @@ module.exports = React.createClass({
}
return (
<div>
{ _t('Sorry, this homeserver is using a login which is not recognised ')}({step})
{ _t('Sorry, this homeserver is using a login which is not recognised ') }({ step })
</div>
);
}
@ -333,19 +334,19 @@ module.exports = React.createClass({
const ServerConfig = sdk.getComponent("login.ServerConfig");
const loader = this.state.busy ? <div className="mx_Login_loader"><Loader /></div> : null;
var loginAsGuestJsx;
let loginAsGuestJsx;
if (this.props.enableGuest) {
loginAsGuestJsx =
<a className="mx_Login_create" onClick={this._onLoginAsGuestClick} href="#">
{ _t('Login as guest')}
{ _t('Login as guest') }
</a>;
}
var returnToAppJsx;
let returnToAppJsx;
if (this.props.onCancelClick) {
returnToAppJsx =
<a className="mx_Login_create" onClick={this.props.onCancelClick} href="#">
{ _t('Return to app')}
{ _t('Return to app') }
</a>;
}
@ -354,7 +355,7 @@ module.exports = React.createClass({
<div className="mx_Login_box">
<LoginHeader />
<div>
<h2>{ _t('Sign in')}
<h2>{ _t('Sign in') }
{ loader }
</h2>
{ this.componentForStep(this.state.currentFlow) }
@ -365,12 +366,12 @@ module.exports = React.createClass({
defaultHsUrl={this.props.defaultHsUrl}
defaultIsUrl={this.props.defaultIsUrl}
onServerConfigChange={this.onServerConfigChange}
delayTimeMs={1000}/>
delayTimeMs={1000} />
<div className="mx_Login_error">
{ this.state.errorText }
</div>
<a className="mx_Login_create" onClick={this.props.onRegisterClick} href="#">
{ _t('Create an account')}
{ _t('Create an account') }
</a>
{ loginAsGuestJsx }
{ returnToAppJsx }
@ -380,5 +381,5 @@ module.exports = React.createClass({
</div>
</div>
);
}
},
});

View File

@ -25,14 +25,14 @@ module.exports = React.createClass({
displayName: 'PostRegistration',
propTypes: {
onComplete: React.PropTypes.func.isRequired
onComplete: React.PropTypes.func.isRequired,
},
getInitialState: function() {
return {
avatarUrl: null,
errorString: null,
busy: false
busy: false,
};
},
@ -40,26 +40,26 @@ module.exports = React.createClass({
// There is some assymetry between ChangeDisplayName and ChangeAvatar,
// as ChangeDisplayName will auto-get the name but ChangeAvatar expects
// the URL to be passed to you (because it's also used for room avatars).
var cli = MatrixClientPeg.get();
const cli = MatrixClientPeg.get();
this.setState({busy: true});
var self = this;
const self = this;
cli.getProfileInfo(cli.credentials.userId).done(function(result) {
self.setState({
avatarUrl: MatrixClientPeg.get().mxcUrlToHttp(result.avatar_url),
busy: false
busy: false,
});
}, function(error) {
self.setState({
errorString: _t("Failed to fetch avatar URL"),
busy: false
busy: false,
});
});
},
render: function() {
var ChangeDisplayName = sdk.getComponent('settings.ChangeDisplayName');
var ChangeAvatar = sdk.getComponent('settings.ChangeAvatar');
var LoginHeader = sdk.getComponent('login.LoginHeader');
const ChangeDisplayName = sdk.getComponent('settings.ChangeDisplayName');
const ChangeAvatar = sdk.getComponent('settings.ChangeAvatar');
const LoginHeader = sdk.getComponent('login.LoginHeader');
return (
<div className="mx_Login">
<div className="mx_Login_box">
@ -71,10 +71,10 @@ module.exports = React.createClass({
<ChangeAvatar
initialAvatarUrl={this.state.avatarUrl} />
<button onClick={this.props.onComplete}>{ _t('Continue') }</button>
{this.state.errorString}
{ this.state.errorString }
</div>
</div>
</div>
);
}
},
});

View File

@ -57,7 +57,7 @@ module.exports = React.createClass({
// registration shouldn't know or care how login is done.
onLoginClick: React.PropTypes.func.isRequired,
onCancelClick: React.PropTypes.func
onCancelClick: React.PropTypes.func,
},
getInitialState: function() {
@ -121,7 +121,7 @@ module.exports = React.createClass({
},
onServerConfigChange: function(config) {
let newState = {};
const newState = {};
if (config.hsUrl !== undefined) {
newState.hsUrl = config.hsUrl;
}
@ -195,7 +195,7 @@ module.exports = React.createClass({
this._rtsClient.getTeam(teamToken).then((team) => {
console.log(
`User successfully registered with team ${team.name}`
`User successfully registered with team ${team.name}`,
);
if (!team.rooms) {
return;
@ -223,7 +223,7 @@ module.exports = React.createClass({
deviceId: response.device_id,
homeserverUrl: this._matrixClient.getHomeserverUrl(),
identityServerUrl: this._matrixClient.getIdentityServerUrl(),
accessToken: response.access_token
accessToken: response.access_token,
}, teamToken);
}).then((cli) => {
return this._setupPushers(cli);
@ -253,7 +253,7 @@ module.exports = React.createClass({
},
onFormValidationFailed: function(errCode) {
var errMsg;
let errMsg;
switch (errCode) {
case "RegistrationForm.ERR_PASSWORD_MISSING":
errMsg = _t('Missing password.');
@ -282,7 +282,7 @@ module.exports = React.createClass({
break;
}
this.setState({
errorText: errMsg
errorText: errMsg,
});
},
@ -316,7 +316,7 @@ module.exports = React.createClass({
emailAddress: this.state.formVals.email,
phoneCountry: this.state.formVals.phoneCountry,
phoneNumber: this.state.formVals.phoneNumber,
}
};
},
render: function() {
@ -346,7 +346,7 @@ module.exports = React.createClass({
} else {
let errorSection;
if (this.state.errorText) {
errorSection = <div className="mx_Login_error">{this.state.errorText}</div>;
errorSection = <div className="mx_Login_error">{ this.state.errorText }</div>;
}
registerBody = (
<div>
@ -362,7 +362,7 @@ module.exports = React.createClass({
onRegisterClick={this.onFormSubmit}
onTeamSelected={this.onTeamSelected}
/>
{errorSection}
{ errorSection }
<ServerConfig ref="serverConfig"
withToggleButton={true}
customHsUrl={this.props.customHsUrl}
@ -380,7 +380,7 @@ module.exports = React.createClass({
if (this.props.onCancelClick) {
returnToAppJsx = (
<a className="mx_Login_create" onClick={this.props.onCancelClick} href="#">
{_t('Return to app')}
{ _t('Return to app') }
</a>
);
}
@ -393,15 +393,15 @@ module.exports = React.createClass({
this.state.teamSelected.domain + "/icon.png" :
null}
/>
<h2>{_t('Create an account')}</h2>
{registerBody}
<h2>{ _t('Create an account') }</h2>
{ registerBody }
<a className="mx_Login_create" onClick={this.props.onLoginClick} href="#">
{_t('I already have an account')}
{ _t('I already have an account') }
</a>
{returnToAppJsx}
{ returnToAppJsx }
<LoginFooter />
</div>
</div>
);
}
},
});

View File

@ -14,10 +14,8 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
'use strict';
var React = require('react');
var AvatarLogic = require("../../../Avatar");
import React from 'react';
import AvatarLogic from '../../../Avatar';
import sdk from '../../../index';
import AccessibleButton from '../elements/AccessibleButton';
@ -34,7 +32,7 @@ module.exports = React.createClass({
height: React.PropTypes.number,
// XXX resizeMethod not actually used.
resizeMethod: React.PropTypes.string,
defaultToInitialLetter: React.PropTypes.bool // true to add default url
defaultToInitialLetter: React.PropTypes.bool, // true to add default url
},
getDefaultProps: function() {
@ -42,7 +40,7 @@ module.exports = React.createClass({
width: 40,
height: 40,
resizeMethod: 'crop',
defaultToInitialLetter: true
defaultToInitialLetter: true,
};
},
@ -52,15 +50,14 @@ module.exports = React.createClass({
componentWillReceiveProps: function(nextProps) {
// work out if we need to call setState (if the image URLs array has changed)
var newState = this._getState(nextProps);
var newImageUrls = newState.imageUrls;
var oldImageUrls = this.state.imageUrls;
const newState = this._getState(nextProps);
const newImageUrls = newState.imageUrls;
const oldImageUrls = this.state.imageUrls;
if (newImageUrls.length !== oldImageUrls.length) {
this.setState(newState); // detected a new entry
}
else {
} else {
// check each one to see if they are the same
for (var i = 0; i < newImageUrls.length; i++) {
for (let i = 0; i < newImageUrls.length; i++) {
if (oldImageUrls[i] !== newImageUrls[i]) {
this.setState(newState); // detected a diff
break;
@ -73,31 +70,31 @@ module.exports = React.createClass({
// work out the full set of urls to try to load. This is formed like so:
// imageUrls: [ props.url, props.urls, default image ]
var urls = props.urls || [];
const urls = props.urls || [];
if (props.url) {
urls.unshift(props.url); // put in urls[0]
}
var defaultImageUrl = null;
let defaultImageUrl = null;
if (props.defaultToInitialLetter) {
defaultImageUrl = AvatarLogic.defaultAvatarUrlForString(
props.idName || props.name
props.idName || props.name,
);
urls.push(defaultImageUrl); // lowest priority
}
return {
imageUrls: urls,
defaultImageUrl: defaultImageUrl,
urlsIndex: 0
urlsIndex: 0,
};
},
onError: function(ev) {
var nextIndex = this.state.urlsIndex + 1;
const nextIndex = this.state.urlsIndex + 1;
if (nextIndex < this.state.imageUrls.length) {
// try the next one
this.setState({
urlsIndex: nextIndex
urlsIndex: nextIndex,
});
}
},
@ -111,32 +108,32 @@ module.exports = React.createClass({
return undefined;
}
var idx = 0;
var initial = name[0];
let idx = 0;
const initial = name[0];
if ((initial === '@' || initial === '#') && name[1]) {
idx++;
}
// string.codePointAt(0) would do this, but that isn't supported by
// some browsers (notably PhantomJS).
var chars = 1;
var first = name.charCodeAt(idx);
let chars = 1;
const first = name.charCodeAt(idx);
// check if its the start of a surrogate pair
if (first >= 0xD800 && first <= 0xDBFF && name[idx+1]) {
var second = name.charCodeAt(idx+1);
const second = name.charCodeAt(idx+1);
if (second >= 0xDC00 && second <= 0xDFFF) {
chars++;
}
}
var firstChar = name.substring(idx, idx+chars);
const firstChar = name.substring(idx, idx+chars);
return firstChar.toUpperCase();
},
render: function() {
const EmojiText = sdk.getComponent('elements.EmojiText');
var imageUrl = this.state.imageUrls[this.state.urlsIndex];
const imageUrl = this.state.imageUrls[this.state.urlsIndex];
const {
name, idName, title, url, urls, width, height, resizeMethod,
@ -152,7 +149,7 @@ module.exports = React.createClass({
width: width + "px",
lineHeight: height + "px" }}
>
{initialLetter}
{ initialLetter }
</EmojiText>
);
const imgNode = (
@ -165,15 +162,15 @@ module.exports = React.createClass({
<AccessibleButton element='span' className="mx_BaseAvatar"
onClick={onClick} {...otherProps}
>
{textNode}
{imgNode}
{ textNode }
{ imgNode }
</AccessibleButton>
);
} else {
return (
<span className="mx_BaseAvatar" {...otherProps}>
{textNode}
{imgNode}
{ textNode }
{ imgNode }
</span>
);
}
@ -198,5 +195,5 @@ module.exports = React.createClass({
{...otherProps} />
);
}
}
},
});

View File

@ -16,9 +16,9 @@ limitations under the License.
'use strict';
var React = require('react');
var Avatar = require('../../../Avatar');
var sdk = require("../../../index");
const React = require('react');
const Avatar = require('../../../Avatar');
const sdk = require("../../../index");
const dispatcher = require("../../../dispatcher");
module.exports = React.createClass({
@ -63,14 +63,14 @@ module.exports = React.createClass({
imageUrl: Avatar.avatarUrlForMember(props.member,
props.width,
props.height,
props.resizeMethod)
props.resizeMethod),
};
},
render: function() {
var BaseAvatar = sdk.getComponent("avatars.BaseAvatar");
const BaseAvatar = sdk.getComponent("avatars.BaseAvatar");
var {member, onClick, viewUserOnClick, ...otherProps} = this.props;
let {member, onClick, viewUserOnClick, ...otherProps} = this.props;
if (viewUserOnClick) {
onClick = () => {
@ -83,7 +83,7 @@ module.exports = React.createClass({
return (
<BaseAvatar {...otherProps} name={this.state.name} title={this.state.title}
idName={member.userId} url={this.state.imageUrl} onClick={onClick}/>
idName={member.userId} url={this.state.imageUrl} onClick={onClick} />
);
}
},
});

View File

@ -13,11 +13,10 @@ 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.
*/
var React = require('react');
var ContentRepo = require("matrix-js-sdk").ContentRepo;
var MatrixClientPeg = require('../../../MatrixClientPeg');
var Avatar = require('../../../Avatar');
var sdk = require("../../../index");
import React from "react";
import {ContentRepo} from "matrix-js-sdk";
import MatrixClientPeg from "../../../MatrixClientPeg";
import sdk from "../../../index";
module.exports = React.createClass({
displayName: 'RoomAvatar',
@ -30,7 +29,7 @@ module.exports = React.createClass({
oobData: React.PropTypes.object,
width: React.PropTypes.number,
height: React.PropTypes.number,
resizeMethod: React.PropTypes.string
resizeMethod: React.PropTypes.string,
},
getDefaultProps: function() {
@ -44,13 +43,13 @@ module.exports = React.createClass({
getInitialState: function() {
return {
urls: this.getImageUrls(this.props)
urls: this.getImageUrls(this.props),
};
},
componentWillReceiveProps: function(newProps) {
this.setState({
urls: this.getImageUrls(newProps)
urls: this.getImageUrls(newProps),
});
},
@ -61,11 +60,10 @@ module.exports = React.createClass({
props.oobData.avatarUrl,
Math.floor(props.width * window.devicePixelRatio),
Math.floor(props.height * window.devicePixelRatio),
props.resizeMethod
props.resizeMethod,
), // highest priority
this.getRoomAvatarUrl(props),
this.getOneToOneAvatar(props),
this.getFallbackAvatar(props) // lowest priority
this.getOneToOneAvatar(props), // lowest priority
].filter(function(url) {
return (url != null && url != "");
});
@ -79,17 +77,17 @@ module.exports = React.createClass({
Math.floor(props.width * window.devicePixelRatio),
Math.floor(props.height * window.devicePixelRatio),
props.resizeMethod,
false
false,
);
},
getOneToOneAvatar: function(props) {
if (!props.room) return null;
var mlist = props.room.currentState.members;
var userIds = [];
const mlist = props.room.currentState.members;
const userIds = [];
// for .. in optimisation to return early if there are >2 keys
for (var uid in mlist) {
for (const uid in mlist) {
if (mlist.hasOwnProperty(uid)) {
userIds.push(uid);
}
@ -99,7 +97,7 @@ module.exports = React.createClass({
}
if (userIds.length == 2) {
var theOtherGuy = null;
let theOtherGuy = null;
if (mlist[userIds[0]].userId == MatrixClientPeg.get().credentials.userId) {
theOtherGuy = mlist[userIds[1]];
} else {
@ -110,7 +108,7 @@ module.exports = React.createClass({
Math.floor(props.width * window.devicePixelRatio),
Math.floor(props.height * window.devicePixelRatio),
props.resizeMethod,
false
false,
);
} else if (userIds.length == 1) {
return mlist[userIds[0]].getAvatarUrl(
@ -118,37 +116,24 @@ module.exports = React.createClass({
Math.floor(props.width * window.devicePixelRatio),
Math.floor(props.height * window.devicePixelRatio),
props.resizeMethod,
false
false,
);
} else {
return null;
}
},
getFallbackAvatar: function(props) {
let roomId = null;
if (props.oobData && props.oobData.roomId) {
roomId = this.props.oobData.roomId;
} else if (props.room) {
roomId = props.room.roomId;
} else {
return null;
}
return Avatar.defaultAvatarUrlForString(roomId);
},
render: function() {
var BaseAvatar = sdk.getComponent("avatars.BaseAvatar");
const BaseAvatar = sdk.getComponent("avatars.BaseAvatar");
var {room, oobData, ...otherProps} = this.props;
const {room, oobData, ...otherProps} = this.props;
var roomName = room ? room.name : oobData.name;
const roomName = room ? room.name : oobData.name;
return (
<BaseAvatar {...otherProps} name={roomName}
idName={room ? room.roomId : null}
urls={this.state.urls} />
);
}
},
});

View File

@ -36,7 +36,7 @@ module.exports = React.createClass({
render: function() {
return (
<button className="mx_CreateRoomButton" onClick={this.onClick}>{_t("Create Room")}</button>
<button className="mx_CreateRoomButton" onClick={this.onClick}>{ _t("Create Room") }</button>
);
}
},
});

View File

@ -16,10 +16,10 @@ limitations under the License.
'use strict';
var React = require('react');
const React = require('react');
import { _t } from '../../../languageHandler';
var Presets = {
const Presets = {
PrivateChat: "private_chat",
PublicChat: "public_chat",
Custom: "custom",
@ -29,7 +29,7 @@ module.exports = React.createClass({
displayName: 'CreateRoomPresets',
propTypes: {
onChange: React.PropTypes.func,
preset: React.PropTypes.string
preset: React.PropTypes.string,
},
Presets: Presets,
@ -47,10 +47,10 @@ module.exports = React.createClass({
render: function() {
return (
<select className="mx_Presets" onChange={this.onValueChanged} value={this.props.preset}>
<option value={this.Presets.PrivateChat}>{_t("Private Chat")}</option>
<option value={this.Presets.PublicChat}>{_t("Public Chat")}</option>
<option value={this.Presets.Custom}>{_t("Custom")}</option>
<option value={this.Presets.PrivateChat}>{ _t("Private Chat") }</option>
<option value={this.Presets.PublicChat}>{ _t("Public Chat") }</option>
<option value={this.Presets.Custom}>{ _t("Custom") }</option>
</select>
);
}
},
});

View File

@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
var React = require('react');
const React = require('react');
import { _t } from '../../../languageHandler';
module.exports = React.createClass({
@ -35,10 +35,10 @@ module.exports = React.createClass({
},
getAliasLocalpart: function() {
var room_alias = this.props.alias;
let room_alias = this.props.alias;
if (room_alias && this.props.homeserver) {
var suffix = ":" + this.props.homeserver;
const suffix = ":" + this.props.homeserver;
if (room_alias.startsWith("#") && room_alias.endsWith(suffix)) {
room_alias = room_alias.slice(1, -suffix.length);
}
@ -52,22 +52,22 @@ module.exports = React.createClass({
},
onFocus: function(ev) {
var target = ev.target;
var curr_val = ev.target.value;
const target = ev.target;
const curr_val = ev.target.value;
if (this.props.homeserver) {
if (curr_val == "") {
var self = this;
const self = this;
setTimeout(function() {
target.value = "#:" + self.props.homeserver;
target.setSelectionRange(1, 1);
}, 0);
} else {
var suffix = ":" + this.props.homeserver;
const suffix = ":" + this.props.homeserver;
setTimeout(function() {
target.setSelectionRange(
curr_val.startsWith("#") ? 1 : 0,
curr_val.endsWith(suffix) ? (target.value.length - suffix.length) : target.value.length
curr_val.endsWith(suffix) ? (target.value.length - suffix.length) : target.value.length,
);
}, 0);
}
@ -75,7 +75,7 @@ module.exports = React.createClass({
},
onBlur: function(ev) {
var curr_val = ev.target.value;
const curr_val = ev.target.value;
if (this.props.homeserver) {
if (curr_val == "#:" + this.props.homeserver) {
@ -84,8 +84,8 @@ module.exports = React.createClass({
}
if (curr_val != "") {
var new_val = ev.target.value;
var suffix = ":" + this.props.homeserver;
let new_val = ev.target.value;
const suffix = ":" + this.props.homeserver;
if (!curr_val.startsWith("#")) new_val = "#" + new_val;
if (!curr_val.endsWith(suffix)) new_val = new_val + suffix;
ev.target.value = new_val;
@ -97,7 +97,7 @@ module.exports = React.createClass({
return (
<input type="text" className="mx_RoomAlias" placeholder={_t("Alias (optional)")}
onChange={this.onValueChanged} onFocus={this.onFocus} onBlur={this.onBlur}
value={this.props.alias}/>
value={this.props.alias} />
);
}
},
});

View File

@ -23,12 +23,13 @@ import MatrixClientPeg from '../../../MatrixClientPeg';
import AccessibleButton from '../elements/AccessibleButton';
import Promise from 'bluebird';
import { addressTypes, getAddressType } from '../../../UserAddress.js';
import GroupStoreCache from '../../../stores/GroupStoreCache';
const TRUNCATE_QUERY_LIST = 40;
const QUERY_USER_DIRECTORY_DEBOUNCE_MS = 200;
module.exports = React.createClass({
displayName: "UserPickerDialog",
displayName: "AddressPickerDialog",
propTypes: {
title: PropTypes.string.isRequired,
@ -40,6 +41,12 @@ module.exports = React.createClass({
focus: PropTypes.bool,
validAddressTypes: PropTypes.arrayOf(PropTypes.oneOf(addressTypes)),
onFinished: PropTypes.func.isRequired,
groupId: PropTypes.string,
// The type of entity to search for. Default: 'user'.
pickerType: PropTypes.oneOf(['user', 'room']),
// Whether the current user should be included in the addresses returned. Only
// applicable when pickerType is `user`. Default: false.
includeSelf: PropTypes.bool,
},
getDefaultProps: function() {
@ -47,6 +54,8 @@ module.exports = React.createClass({
value: "",
focus: true,
validAddressTypes: addressTypes,
pickerType: 'user',
includeSelf: false,
};
},
@ -140,10 +149,22 @@ module.exports = React.createClass({
// Only do search if there is something to search
if (query.length > 0 && query != '@' && query.length >= 2) {
this.queryChangedDebouncer = setTimeout(() => {
if (this.state.serverSupportsUserDirectory) {
this._doUserDirectorySearch(query);
if (this.props.pickerType === 'user') {
if (this.props.groupId) {
this._doNaiveGroupSearch(query);
} else if (this.state.serverSupportsUserDirectory) {
this._doUserDirectorySearch(query);
} else {
this._doLocalSearch(query);
}
} else if (this.props.pickerType === 'room') {
if (this.props.groupId) {
this._doNaiveGroupRoomSearch(query);
} else {
this._doRoomSearch(query);
}
} else {
this._doLocalSearch(query);
console.error('Unknown pickerType', this.props.pickerType);
}
}, QUERY_USER_DIRECTORY_DEBOUNCE_MS);
} else {
@ -185,6 +206,94 @@ module.exports = React.createClass({
if (this._cancelThreepidLookup) this._cancelThreepidLookup();
},
_doNaiveGroupSearch: function(query) {
const lowerCaseQuery = query.toLowerCase();
this.setState({
busy: true,
query,
searchError: null,
});
MatrixClientPeg.get().getGroupUsers(this.props.groupId).then((resp) => {
const results = [];
resp.chunk.forEach((u) => {
const userIdMatch = u.user_id.toLowerCase().includes(lowerCaseQuery);
const displayNameMatch = (u.displayname || '').toLowerCase().includes(lowerCaseQuery);
if (!(userIdMatch || displayNameMatch)) {
return;
}
results.push({
user_id: u.user_id,
avatar_url: u.avatar_url,
display_name: u.displayname,
});
});
this._processResults(results, query);
}).catch((err) => {
console.error('Error whilst searching group rooms: ', err);
this.setState({
searchError: err.errcode ? err.message : _t('Something went wrong!'),
});
}).done(() => {
this.setState({
busy: false,
});
});
},
_doNaiveGroupRoomSearch: function(query) {
const lowerCaseQuery = query.toLowerCase();
const groupStore = GroupStoreCache.getGroupStore(MatrixClientPeg.get(), this.props.groupId);
const results = [];
groupStore.getGroupRooms().forEach((r) => {
const nameMatch = (r.name || '').toLowerCase().includes(lowerCaseQuery);
const topicMatch = (r.topic || '').toLowerCase().includes(lowerCaseQuery);
const aliasMatch = (r.canonical_alias || '').toLowerCase().includes(lowerCaseQuery);
if (!(nameMatch || topicMatch || aliasMatch)) {
return;
}
results.push({
room_id: r.room_id,
avatar_url: r.avatar_url,
name: r.name || r.canonical_alias,
});
});
this._processResults(results, query);
this.setState({
busy: false,
});
},
_doRoomSearch: function(query) {
const lowerCaseQuery = query.toLowerCase();
const rooms = MatrixClientPeg.get().getRooms();
const results = [];
rooms.forEach((room) => {
const nameEvent = room.currentState.getStateEvents('m.room.name', '');
const topicEvent = room.currentState.getStateEvents('m.room.topic', '');
const name = nameEvent ? nameEvent.getContent().name : '';
const canonicalAlias = room.getCanonicalAlias();
const topic = topicEvent ? topicEvent.getContent().topic : '';
const nameMatch = (name || '').toLowerCase().includes(lowerCaseQuery);
const aliasMatch = (canonicalAlias || '').toLowerCase().includes(lowerCaseQuery);
const topicMatch = (topic || '').toLowerCase().includes(lowerCaseQuery);
if (!(nameMatch || topicMatch || aliasMatch)) {
return;
}
const avatarEvent = room.currentState.getStateEvents('m.room.avatar', '');
const avatarUrl = avatarEvent ? avatarEvent.getContent().url : undefined;
results.push({
room_id: room.roomId,
avatar_url: avatarUrl,
name: name || canonicalAlias,
});
});
this._processResults(results, query);
this.setState({
busy: false,
});
},
_doUserDirectorySearch: function(query) {
this.setState({
busy: true,
@ -245,17 +354,30 @@ module.exports = React.createClass({
_processResults: function(results, query) {
const queryList = [];
results.forEach((user) => {
if (user.user_id === MatrixClientPeg.get().credentials.userId) {
results.forEach((result) => {
if (result.room_id) {
queryList.push({
addressType: 'mx-room-id',
address: result.room_id,
displayName: result.name,
avatarMxc: result.avatar_url,
isKnown: true,
});
return;
}
if (!this.props.includeSelf &&
result.user_id === MatrixClientPeg.get().credentials.userId
) {
return;
}
// Return objects, structure of which is defined
// by UserAddressType
queryList.push({
addressType: 'mx',
address: user.user_id,
displayName: user.display_name,
avatarMxc: user.avatar_url,
addressType: 'mx-user-id',
address: result.user_id,
displayName: result.display_name,
avatarMxc: result.avatar_url,
isKnown: true,
});
});
@ -291,16 +413,23 @@ module.exports = React.createClass({
address: addressText,
isKnown: false,
};
if (addrType == null) {
if (!this.props.validAddressTypes.includes(addrType)) {
this.setState({ error: true });
return null;
} else if (addrType == 'mx') {
} else if (addrType == 'mx-user-id') {
const user = MatrixClientPeg.get().getUser(addrObj.address);
if (user) {
addrObj.displayName = user.displayName;
addrObj.avatarMxc = user.avatarUrl;
addrObj.isKnown = true;
}
} else if (addrType == 'mx-room-id') {
const room = MatrixClientPeg.get().getRoom(addrObj.address);
if (room) {
addrObj.displayName = room.name;
addrObj.avatarMxc = room.avatarUrl;
addrObj.isKnown = true;
}
}
const userList = this.state.userList.slice();
@ -360,7 +489,7 @@ module.exports = React.createClass({
const AddressTile = sdk.getComponent("elements.AddressTile");
for (let i = 0; i < this.state.userList.length; i++) {
query.push(
<AddressTile key={i} address={this.state.userList[i]} canDismiss={true} onDismissed={ this.onDismissed(i) } />,
<AddressTile key={i} address={this.state.userList[i]} canDismiss={true} onDismissed={this.onDismissed(i)} />,
);
}
}
@ -382,23 +511,36 @@ module.exports = React.createClass({
let error;
let addressSelector;
if (this.state.error) {
let tryUsing = '';
const validTypeDescriptions = this.props.validAddressTypes.map((t) => {
return {
'mx-user-id': _t("Matrix ID"),
'mx-room-id': _t("Matrix Room ID"),
'email': _t("email address"),
}[t];
});
tryUsing = _t("Try using one of the following valid address types: %(validTypesList)s.", {
validTypesList: validTypeDescriptions.join(", "),
});
error = <div className="mx_ChatInviteDialog_error">
{_t("You have entered an invalid contact. Try using their Matrix ID or email address.")}
{ _t("You have entered an invalid address.") }
<br />
{ tryUsing }
</div>;
} else if (this.state.searchError) {
error = <div className="mx_ChatInviteDialog_error">{this.state.searchError}</div>;
error = <div className="mx_ChatInviteDialog_error">{ this.state.searchError }</div>;
} else if (
this.state.query.length > 0 &&
this.state.queryList.length === 0 &&
!this.state.busy
) {
error = <div className="mx_ChatInviteDialog_error">{_t("No results")}</div>;
error = <div className="mx_ChatInviteDialog_error">{ _t("No results") }</div>;
} else {
addressSelector = (
<AddressSelector ref={(ref) => {this.addressSelector = ref;}}
addressList={ this.state.queryList }
onSelected={ this.onSelected }
truncateAt={ TRUNCATE_QUERY_LIST }
addressList={this.state.queryList}
onSelected={this.onSelected}
truncateAt={TRUNCATE_QUERY_LIST}
/>
);
}
@ -406,7 +548,7 @@ module.exports = React.createClass({
return (
<div className="mx_ChatInviteDialog" onKeyDown={this.onKeyDown}>
<div className="mx_Dialog_title">
{this.props.title}
{ this.props.title }
</div>
<AccessibleButton className="mx_ChatInviteDialog_cancel"
onClick={this.onCancel} >
@ -422,7 +564,7 @@ module.exports = React.createClass({
</div>
<div className="mx_Dialog_buttons">
<button className="mx_Dialog_primary" onClick={this.onButtonClick}>
{this.props.button}
{ this.props.button }
</button>
</div>
</div>

View File

@ -155,7 +155,7 @@ export default class ChatCreateOrReuseDialog extends React.Component {
width={48} height={48}
/>
<div className="mx_ChatCreateOrReuseDialog_profile_name">
{this.state.profile.displayName || this.props.userId}
{ this.state.profile.displayName || this.props.userId }
</div>
</div>;
}
@ -177,7 +177,7 @@ export default class ChatCreateOrReuseDialog extends React.Component {
const BaseDialog = sdk.getComponent('views.dialogs.BaseDialog');
return (
<BaseDialog className='mx_ChatCreateOrReuseDialog'
onFinished={ this.props.onFinished.bind(false) }
onFinished={this.props.onFinished.bind(false)}
title={title}
>
{ content }

View File

@ -52,20 +52,20 @@ export default React.createClass({
return (
<BaseDialog className="mx_ConfirmUserActionDialog" onFinished={this.props.onFinished}
onEnterPressed={ this.onOk }
onEnterPressed={this.onOk}
title={title}
>
<div className="mx_Dialog_content">
{_t("Are you sure you wish to remove (delete) this event? " +
"Note that if you delete a room name or topic change, it could undo the change.")}
{ _t("Are you sure you wish to remove (delete) this event? " +
"Note that if you delete a room name or topic change, it could undo the change.") }
</div>
<div className="mx_Dialog_buttons">
<button className={confirmButtonClass} onClick={this.onOk}>
{_t("Remove")}
{ _t("Remove") }
</button>
<button onClick={this.onCancel}>
{_t("Cancel")}
{ _t("Cancel") }
</button>
</div>
</BaseDialog>

View File

@ -18,6 +18,7 @@ import React from 'react';
import sdk from '../../../index';
import { _t } from '../../../languageHandler';
import classnames from 'classnames';
import { GroupMemberType } from '../../../groups';
/*
* A dialog for confirming an operation on another user.
@ -30,7 +31,10 @@ import classnames from 'classnames';
export default React.createClass({
displayName: 'ConfirmUserActionDialog',
propTypes: {
member: React.PropTypes.object.isRequired, // matrix-js-sdk member object
// matrix-js-sdk (room) member object. Supply either this or 'groupMember'
member: React.PropTypes.object,
// group member object. Supply either this or 'member'
groupMember: GroupMemberType,
action: React.PropTypes.string.isRequired, // eg. 'Ban'
// Whether to display a text field for a reason
@ -69,6 +73,7 @@ export default React.createClass({
render: function() {
const BaseDialog = sdk.getComponent('views.dialogs.BaseDialog');
const MemberAvatar = sdk.getComponent("views.avatars.MemberAvatar");
const BaseAvatar = sdk.getComponent("views.avatars.BaseAvatar");
const title = _t("%(actionVerb)s this person?", { actionVerb: this.props.action});
const confirmButtonClass = classnames({
@ -83,7 +88,7 @@ export default React.createClass({
<form onSubmit={this.onOk}>
<input className="mx_ConfirmUserActionDialog_reasonField"
ref={this._collectReasonField}
placeholder={ _t("Reason") }
placeholder={_t("Reason")}
autoFocus={true}
/>
</form>
@ -91,24 +96,38 @@ export default React.createClass({
);
}
let avatar;
let name;
let userId;
if (this.props.member) {
avatar = <MemberAvatar member={this.props.member} width={48} height={48} />;
name = this.props.member.name;
userId = this.props.member.userId;
} else {
// we don't get this info from the API yet
avatar = <BaseAvatar name={this.props.groupMember.userId} width={48} height={48} />;
name = this.props.groupMember.userId;
userId = this.props.groupMember.userId;
}
return (
<BaseDialog className="mx_ConfirmUserActionDialog" onFinished={this.props.onFinished}
onEnterPressed={ this.onOk }
onEnterPressed={this.onOk}
title={title}
>
<div className="mx_Dialog_content">
<div className="mx_ConfirmUserActionDialog_avatar">
<MemberAvatar member={this.props.member} width={48} height={48} />
{ avatar }
</div>
<div className="mx_ConfirmUserActionDialog_name">{this.props.member.name}</div>
<div className="mx_ConfirmUserActionDialog_userId">{this.props.member.userId}</div>
<div className="mx_ConfirmUserActionDialog_name">{ name }</div>
<div className="mx_ConfirmUserActionDialog_userId">{ userId }</div>
</div>
{reasonBox}
{ reasonBox }
<div className="mx_Dialog_buttons">
<button className={confirmButtonClass}
onClick={this.onOk} autoFocus={!this.props.askReason}
>
{this.props.action}
{ this.props.action }
</button>
<button onClick={this.onCancel}>

View File

@ -142,8 +142,8 @@ export default React.createClass({
// rather than displaying what the server gives us, but synapse doesn't give
// any yet.
createErrorNode = <div className="error">
<div>{_t('Room creation failed')}</div>
<div>{this.state.createError.message}</div>
<div>{ _t('Room creation failed') }</div>
<div>{ this.state.createError.message }</div>
</div>;
}
@ -156,7 +156,7 @@ export default React.createClass({
<div className="mx_Dialog_content">
<div className="mx_CreateGroupDialog_inputRow">
<div className="mx_CreateGroupDialog_label">
<label htmlFor="groupname">{_t('Group Name')}</label>
<label htmlFor="groupname">{ _t('Group Name') }</label>
</div>
<div>
<input id="groupname" className="mx_CreateGroupDialog_input"
@ -169,7 +169,7 @@ export default React.createClass({
</div>
<div className="mx_CreateGroupDialog_inputRow">
<div className="mx_CreateGroupDialog_label">
<label htmlFor="groupid">{_t('Group ID')}</label>
<label htmlFor="groupid">{ _t('Group ID') }</label>
</div>
<div>
<input id="groupid" className="mx_CreateGroupDialog_input"
@ -182,9 +182,9 @@ export default React.createClass({
</div>
</div>
<div className="error">
{this.state.groupIdError}
{ this.state.groupIdError }
</div>
{createErrorNode}
{ createErrorNode }
</div>
<div className="mx_Dialog_buttons">
<button onClick={this._onCancel}>

View File

@ -0,0 +1,81 @@
/*
Copyright 2017 Michael Telatynski <7t3chguy@gmail.com>
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
import React from 'react';
import sdk from '../../../index';
import SdkConfig from '../../../SdkConfig';
import { _t } from '../../../languageHandler';
export default React.createClass({
displayName: 'CreateRoomDialog',
propTypes: {
onFinished: React.PropTypes.func.isRequired,
},
componentDidMount: function() {
const config = SdkConfig.get();
// Dialog shows inverse of m.federate (noFederate) strict false check to skip undefined check (default = true)
this.defaultNoFederate = config.default_federate === false;
},
onOk: function() {
this.props.onFinished(true, this.refs.textinput.value, this.refs.checkbox.checked);
},
onCancel: function() {
this.props.onFinished(false);
},
render: function() {
const BaseDialog = sdk.getComponent('views.dialogs.BaseDialog');
return (
<BaseDialog className="mx_CreateRoomDialog" onFinished={this.props.onFinished}
onEnterPressed={this.onOk}
title={_t('Create Room')}
>
<div className="mx_Dialog_content">
<div className="mx_CreateRoomDialog_label">
<label htmlFor="textinput"> { _t('Room name (optional)') } </label>
</div>
<div>
<input id="textinput" ref="textinput" className="mx_CreateRoomDialog_input" autoFocus={true} size="64" />
</div>
<br />
<details className="mx_CreateRoomDialog_details">
<summary className="mx_CreateRoomDialog_details_summary">{ _t('Advanced options') }</summary>
<div>
<input type="checkbox" id="checkbox" ref="checkbox" defaultChecked={this.defaultNoFederate} />
<label htmlFor="checkbox">
{ _t('Block users on other matrix homeservers from joining this room') }
<br />
({ _t('This setting cannot be changed later!') })
</label>
</div>
</details>
</div>
<div className="mx_Dialog_buttons">
<button onClick={this.onCancel}>
{ _t('Cancel') }
</button>
<button className="mx_Dialog_primary" onClick={this.onOk}>
{ _t('Create Room') }
</button>
</div>
</BaseDialog>
);
},
});

View File

@ -83,7 +83,7 @@ export default class DeactivateAccountDialog extends React.Component {
let error = null;
if (this.state.errStr) {
error = <div className="error">
{this.state.errStr}
{ this.state.errStr }
</div>;
passwordBoxClass = 'error';
}
@ -94,30 +94,30 @@ export default class DeactivateAccountDialog extends React.Component {
let cancelButton = null;
if (!this.state.busy) {
cancelButton = <button onClick={this._onCancel} autoFocus={true}>
{_t("Cancel")}
{ _t("Cancel") }
</button>;
}
return (
<div className="mx_DeactivateAccountDialog">
<div className="mx_Dialog_title danger">
{_t("Deactivate Account")}
{ _t("Deactivate Account") }
</div>
<div className="mx_Dialog_content">
<p>{_t("This will make your account permanently unusable. You will not be able to re-register the same user ID.")}</p>
<p>{ _t("This will make your account permanently unusable. You will not be able to re-register the same user ID.") }</p>
<p>{_t("This action is irreversible.")}</p>
<p>{ _t("This action is irreversible.") }</p>
<p>{_t("To continue, please enter your password.")}</p>
<p>{ _t("To continue, please enter your password.") }</p>
<p>{_t("Password")}:</p>
<p>{ _t("Password") }:</p>
<input
type="password"
onChange={this._onPasswordFieldChange}
ref={(e) => {this._passwordField = e;}}
className={passwordBoxClass}
/>
{error}
{ error }
</div>
<div className="mx_Dialog_buttons">
<button
@ -125,10 +125,10 @@ export default class DeactivateAccountDialog extends React.Component {
onClick={this._onOk}
disabled={!okEnabled}
>
{okLabel}
{ okLabel }
</button>
{cancelButton}
{ cancelButton }
</div>
</div>
);

View File

@ -28,25 +28,25 @@ export default function DeviceVerifyDialog(props) {
const body = (
<div>
<p>
{_t("To verify that this device can be trusted, please contact its " +
{ _t("To verify that this device can be trusted, please contact its " +
"owner using some other means (e.g. in person or a phone call) " +
"and ask them whether the key they see in their User Settings " +
"for this device matches the key below:")}
"for this device matches the key below:") }
</p>
<div className="mx_UserSettings_cryptoSection">
<ul>
<li><label>{_t("Device name")}:</label> <span>{ props.device.getDisplayName() }</span></li>
<li><label>{_t("Device ID")}:</label> <span><code>{ props.device.deviceId}</code></span></li>
<li><label>{_t("Device key")}:</label> <span><code><b>{ key }</b></code></span></li>
<li><label>{ _t("Device name") }:</label> <span>{ props.device.getDisplayName() }</span></li>
<li><label>{ _t("Device ID") }:</label> <span><code>{ props.device.deviceId }</code></span></li>
<li><label>{ _t("Device key") }:</label> <span><code><b>{ key }</b></code></span></li>
</ul>
</div>
<p>
{_t("If it matches, press the verify button below. " +
{ _t("If it matches, press the verify button below. " +
"If it doesn't, then someone else is intercepting this device " +
"and you probably want to press the blacklist button instead.")}
"and you probably want to press the blacklist button instead.") }
</p>
<p>
{_t("In future this verification process will be more sophisticated.")}
{ _t("In future this verification process will be more sophisticated.") }
</p>
</div>
);

View File

@ -63,11 +63,11 @@ export default React.createClass({
<BaseDialog className="mx_ErrorDialog" onFinished={this.props.onFinished}
title={this.props.title || _t('Error')}>
<div className="mx_Dialog_content">
{this.props.description || _t('An error has occurred.')}
{ this.props.description || _t('An error has occurred.') }
</div>
<div className="mx_Dialog_buttons">
<button ref="button" className="mx_Dialog_primary" onClick={this.props.onFinished}>
{this.props.button || _t('OK')}
{ this.props.button || _t('OK') }
</button>
</div>
</BaseDialog>

View File

@ -48,7 +48,7 @@ export default React.createClass({
getInitialState: function() {
return {
authError: null,
}
};
},
_onAuthFinished: function(success, result) {
@ -73,12 +73,12 @@ export default React.createClass({
if (this.state.authError) {
content = (
<div>
<div>{this.state.authError.message || this.state.authError.toString()}</div>
<div>{ this.state.authError.message || this.state.authError.toString() }</div>
<br />
<AccessibleButton onClick={this._onDismissClick}
className="mx_UserSettings_button"
>
{_t("Dismiss")}
{ _t("Dismiss") }
</AccessibleButton>
</div>
);
@ -100,7 +100,7 @@ export default React.createClass({
onFinished={this.props.onFinished}
title={this.state.authError ? 'Error' : (this.props.title || _t('Authentication'))}
>
{content}
{ content }
</BaseDialog>
);
},

View File

@ -18,7 +18,7 @@ import Modal from '../../../Modal';
import React from 'react';
import sdk from '../../../index';
import { _t } from '../../../languageHandler';
import { _t, _td } from '../../../languageHandler';
/**
* Dialog which asks the user whether they want to share their keys with
@ -116,27 +116,27 @@ export default React.createClass({
let text;
if (this.state.wasNewDevice) {
text = "You added a new device '%(displayName)s', which is"
+ " requesting encryption keys.";
text = _td("You added a new device '%(displayName)s', which is"
+ " requesting encryption keys.");
} else {
text = "Your unverified device '%(displayName)s' is requesting"
+ " encryption keys.";
text = _td("Your unverified device '%(displayName)s' is requesting"
+ " encryption keys.");
}
text = _t(text, {displayName: displayName});
return (
<div>
<p>{text}</p>
<p>{ text }</p>
<div className="mx_Dialog_buttons">
<button onClick={this._onVerifyClicked}>
{_t('Start verification')}
{ _t('Start verification') }
</button>
<button onClick={this._onShareClicked}>
{_t('Share without verifying')}
{ _t('Share without verifying') }
</button>
<button onClick={this._onIgnoreClicked}>
{_t('Ignore request')}
{ _t('Ignore request') }
</button>
</div>
</div>
@ -154,7 +154,7 @@ export default React.createClass({
} else {
content = (
<div>
<p>{_t('Loading device info...')}</p>
<p>{ _t('Loading device info...') }</p>
<Spinner />
</div>
);
@ -165,7 +165,7 @@ export default React.createClass({
onFinished={this.props.onFinished}
title={_t('Encryption key request')}
>
{content}
{ content }
</BaseDialog>
);
},

View File

@ -1,5 +1,6 @@
/*
Copyright 2015, 2016 OpenMarket Ltd
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.
@ -17,6 +18,7 @@ limitations under the License.
import React from 'react';
import sdk from '../../../index';
import { _t } from '../../../languageHandler';
import classnames from 'classnames';
export default React.createClass({
displayName: 'QuestionDialog',
@ -25,6 +27,7 @@ export default React.createClass({
description: React.PropTypes.node,
extraButtons: React.PropTypes.node,
button: React.PropTypes.string,
danger: React.PropTypes.bool,
focus: React.PropTypes.bool,
onFinished: React.PropTypes.func.isRequired,
},
@ -36,6 +39,7 @@ export default React.createClass({
extraButtons: null,
focus: true,
hasCancelButton: true,
danger: false,
};
},
@ -51,23 +55,27 @@ export default React.createClass({
const BaseDialog = sdk.getComponent('views.dialogs.BaseDialog');
const cancelButton = this.props.hasCancelButton ? (
<button onClick={this.onCancel}>
{_t("Cancel")}
{ _t("Cancel") }
</button>
) : null;
const buttonClasses = classnames({
mx_Dialog_primary: true,
danger: this.props.danger,
});
return (
<BaseDialog className="mx_QuestionDialog" onFinished={this.props.onFinished}
onEnterPressed={ this.onOk }
onEnterPressed={this.onOk}
title={this.props.title}
>
<div className="mx_Dialog_content">
{this.props.description}
{ this.props.description }
</div>
<div className="mx_Dialog_buttons">
<button className="mx_Dialog_primary" onClick={this.onOk} autoFocus={this.props.focus}>
{this.props.button || _t('OK')}
<button className={buttonClasses} onClick={this.onOk} autoFocus={this.props.focus}>
{ this.props.button || _t('OK') }
</button>
{this.props.extraButtons}
{cancelButton}
{ this.props.extraButtons }
{ cancelButton }
</div>
</BaseDialog>
);

View File

@ -45,10 +45,10 @@ export default React.createClass({
if (SdkConfig.get().bug_report_endpoint_url) {
bugreport = (
<p>
{_tJsx(
{ _tJsx(
"Otherwise, <a>click here</a> to send a bug report.",
/<a>(.*?)<\/a>/, (sub) => <a onClick={this._sendBugReport} key="bugreport" href='#'>{sub}</a>,
)}
/<a>(.*?)<\/a>/, (sub) => <a onClick={this._sendBugReport} key="bugreport" href='#'>{ sub }</a>,
) }
</p>
);
}
@ -57,19 +57,19 @@ export default React.createClass({
<BaseDialog className="mx_ErrorDialog" onFinished={this.props.onFinished}
title={_t('Unable to restore session')}>
<div className="mx_Dialog_content">
<p>{_t("We encountered an error trying to restore your previous session. If " +
<p>{ _t("We encountered an error trying to restore your previous session. If " +
"you continue, you will need to log in again, and encrypted chat " +
"history will be unreadable.")}</p>
"history will be unreadable.") }</p>
<p>{_t("If you have previously used a more recent version of Riot, your session " +
<p>{ _t("If you have previously used a more recent version of Riot, your session " +
"may be incompatible with this version. Close this window and return " +
"to the more recent version.")}</p>
"to the more recent version.") }</p>
{bugreport}
{ bugreport }
</div>
<div className="mx_Dialog_buttons">
<button className="mx_Dialog_primary" onClick={this._continueClicked}>
{_t("Continue anyway")}
{ _t("Continue anyway") }
</button>
</div>
</BaseDialog>

View File

@ -130,10 +130,10 @@ export default React.createClass({
const emailInput = this.state.emailBusy ? <Spinner /> : <EditableText
className="mx_SetEmailDialog_email_input"
placeholder={ _t("Email address") }
placeholder={_t("Email address")}
placeholderClassName="mx_SetEmailDialog_email_input_placeholder"
blurToCancel={ false }
onValueChanged={ this.onEmailAddressChanged } />;
blurToCancel={false}
onValueChanged={this.onEmailAddressChanged} />;
return (
<BaseDialog className="mx_SetEmailDialog"

View File

@ -226,7 +226,7 @@ export default React.createClass({
let usernameIndicator = null;
let usernameBusyIndicator = null;
if (this.state.usernameBusy) {
usernameBusyIndicator = <Spinner w="24" h="24"/>;
usernameBusyIndicator = <Spinner w="24" h="24" />;
} else {
const usernameAvailable = this.state.username &&
this.state.usernameCheckSupport && !this.state.usernameError;
@ -275,17 +275,17 @@ export default React.createClass({
/<a>(.*?)<\/a>/,
],
[
(sub) => <span>{this.props.homeserverUrl}</span>,
(sub) => <a href="#" onClick={this.props.onDifferentServerClicked}>{sub}</a>,
(sub) => <span>{ this.props.homeserverUrl }</span>,
(sub) => <a href="#" onClick={this.props.onDifferentServerClicked}>{ sub }</a>,
],
)}
) }
</p>
<p>
{ _tJsx(
'If you already have a Matrix account you can <a>log in</a> instead.',
/<a>(.*?)<\/a>/,
[(sub) => <a href="#" onClick={this.props.onLoginClick}>{sub}</a>],
)}
[(sub) => <a href="#" onClick={this.props.onLoginClick}>{ sub }</a>],
) }
</p>
{ auth }
{ authErrorIndicator }

View File

@ -65,10 +65,10 @@ export default React.createClass({
>
<div className="mx_Dialog_content">
<div className="mx_TextInputDialog_label">
<label htmlFor="textinput"> {this.props.description} </label>
<label htmlFor="textinput"> { this.props.description } </label>
</div>
<div>
<input id="textinput" ref="textinput" className="mx_TextInputDialog_input" defaultValue={this.props.value} autoFocus={this.props.focus} size="64" onKeyDown={this.onKeyDown}/>
<input id="textinput" ref="textinput" className="mx_TextInputDialog_input" defaultValue={this.props.value} autoFocus={this.props.focus} size="64" />
</div>
</div>
<div className="mx_Dialog_buttons">
@ -76,7 +76,7 @@ export default React.createClass({
{ _t("Cancel") }
</button>
<button className="mx_Dialog_primary" onClick={this.onOk}>
{this.props.button}
{ this.props.button }
</button>
</div>
</BaseDialog>

View File

@ -28,9 +28,9 @@ function DeviceListEntry(props) {
return (
<li>
<DeviceVerifyButtons device={ device } userId={ userId } />
<DeviceVerifyButtons device={device} userId={userId} />
{ device.deviceId }
<br/>
<br />
{ device.getDisplayName() }
</li>
);
@ -48,13 +48,13 @@ function UserUnknownDeviceList(props) {
const {userId, userDevices} = props;
const deviceListEntries = Object.keys(userDevices).map((deviceId) =>
<DeviceListEntry key={ deviceId } userId={ userId }
device={ userDevices[deviceId] } />,
<DeviceListEntry key={deviceId} userId={userId}
device={userDevices[deviceId]} />,
);
return (
<ul className="mx_UnknownDeviceDialog_deviceList">
{deviceListEntries}
{ deviceListEntries }
</ul>
);
}
@ -71,13 +71,13 @@ function UnknownDeviceList(props) {
const {devices} = props;
const userListEntries = Object.keys(devices).map((userId) =>
<li key={ userId }>
<li key={userId}>
<p>{ userId }:</p>
<UserUnknownDeviceList userId={ userId } userDevices={ devices[userId] } />
<UserUnknownDeviceList userId={userId} userDevices={devices[userId]} />
</li>,
);
return <ul>{userListEntries}</ul>;
return <ul>{ userListEntries }</ul>;
}
UnknownDeviceList.propTypes = {
@ -120,17 +120,17 @@ export default React.createClass({
if (blacklistUnverified) {
warning = (
<h4>
{_t("You are currently blacklisting unverified devices; to send " +
"messages to these devices you must verify them.")}
{ _t("You are currently blacklisting unverified devices; to send " +
"messages to these devices you must verify them.") }
</h4>
);
} else {
warning = (
<div>
<p>
{_t("We recommend you go through the verification process " +
{ _t("We recommend you go through the verification process " +
"for each device to confirm they belong to their legitimate owner, " +
"but you can resend the message without verifying if you prefer.")}
"but you can resend the message without verifying if you prefer.") }
</p>
</div>
);
@ -149,22 +149,22 @@ export default React.createClass({
>
<GeminiScrollbar autoshow={false} className="mx_Dialog_content">
<h4>
{_t('"%(RoomName)s" contains devices that you haven\'t seen before.', {RoomName: this.props.room.name})}
{ _t('"%(RoomName)s" contains devices that you haven\'t seen before.', {RoomName: this.props.room.name}) }
</h4>
{ warning }
{_t("Unknown devices")}:
{ _t("Unknown devices") }:
<UnknownDeviceList devices={this.props.devices} />
</GeminiScrollbar>
<div className="mx_Dialog_buttons">
<button className="mx_Dialog_primary" autoFocus={ true }
<button className="mx_Dialog_primary" autoFocus={true}
onClick={() => {
this.props.onFinished();
Resend.resendUnsentEvents(this.props.room);
}}>
{_t("Send anyway")}
{ _t("Send anyway") }
</button>
<button className="mx_Dialog_primary" autoFocus={ true }
<button className="mx_Dialog_primary" autoFocus={true}
onClick={() => {
// XXX: temporary logging to try to diagnose
// https://github.com/vector-im/riot-web/issues/3148

View File

@ -32,7 +32,7 @@ export default function AccessibleButton(props) {
};
restProps.tabIndex = restProps.tabIndex || "0";
restProps.role = "button";
restProps.className = (restProps.className ? restProps.className + " " : "") +
restProps.className = (restProps.className ? restProps.className + " " : "") +
"mx_AccessibleButton";
return React.createElement(element, restProps, children);
}

View File

@ -19,6 +19,7 @@ import PropTypes from 'prop-types';
import AccessibleButton from './AccessibleButton';
import dis from '../../../dispatcher';
import sdk from '../../../index';
import Analytics from '../../../Analytics';
export default React.createClass({
displayName: 'RoleButton',
@ -47,6 +48,7 @@ export default React.createClass({
_onClick: function(ev) {
ev.stopPropagation();
Analytics.trackEvent('Action Button', 'click', this.props.action);
dis.dispatch({action: this.props.action});
},
@ -77,8 +79,8 @@ export default React.createClass({
onMouseLeave={this._onMouseLeave}
>
<TintableSvg src={this.props.iconPath} width={this.props.size} height={this.props.size} />
{tooltip}
{ tooltip }
</AccessibleButton>
);
}
},
});

View File

@ -46,8 +46,8 @@ export default React.createClass({
componentWillReceiveProps: function(props) {
// Make sure the selected item isn't outside the list bounds
var selected = this.state.selected;
var maxSelected = this._maxSelected(props.addressList);
const selected = this.state.selected;
const maxSelected = this._maxSelected(props.addressList);
if (selected > maxSelected) {
this.setState({ selected: maxSelected });
}
@ -57,7 +57,7 @@ export default React.createClass({
// As the user scrolls with the arrow keys keep the selected item
// at the top of the window.
if (this.scrollElement && this.props.addressList.length > 0 && !this.state.hover) {
var elementHeight = this.addressListElement.getBoundingClientRect().height;
const elementHeight = this.addressListElement.getBoundingClientRect().height;
this.scrollElement.scrollTop = (this.state.selected * elementHeight) - elementHeight;
}
},
@ -75,7 +75,7 @@ export default React.createClass({
if (this.state.selected > 0) {
this.setState({
selected: this.state.selected - 1,
hover : false,
hover: false,
});
}
},
@ -84,7 +84,7 @@ export default React.createClass({
if (this.state.selected < this._maxSelected(this.props.addressList)) {
this.setState({
selected: this.state.selected + 1,
hover : false,
hover: false,
});
}
},
@ -105,7 +105,7 @@ export default React.createClass({
},
onMouseLeave: function() {
this.setState({ hover : false });
this.setState({ hover: false });
},
selectAddress: function(index) {
@ -117,15 +117,15 @@ export default React.createClass({
},
createAddressListTiles: function() {
var self = this;
var AddressTile = sdk.getComponent("elements.AddressTile");
var maxSelected = this._maxSelected(this.props.addressList);
var addressList = [];
const self = this;
const AddressTile = sdk.getComponent("elements.AddressTile");
const maxSelected = this._maxSelected(this.props.addressList);
const addressList = [];
// Only create the address elements if there are address
if (this.props.addressList.length > 0) {
for (var i = 0; i <= maxSelected; i++) {
var classes = classNames({
for (let i = 0; i <= maxSelected; i++) {
const classes = classNames({
"mx_AddressSelector_addressListElement": true,
"mx_AddressSelector_selected": this.state.selected === i,
});
@ -143,7 +143,7 @@ export default React.createClass({
ref={(ref) => { this.addressListElement = ref; }}
>
<AddressTile address={this.props.addressList[i]} justified={true} networkName="vector" networkUrl="img/search-icon-vector.svg" />
</div>
</div>,
);
}
}
@ -151,13 +151,13 @@ export default React.createClass({
},
_maxSelected: function(list) {
var listSize = list.length === 0 ? 0 : list.length - 1;
var maxSelected = listSize > (this.props.truncateAt - 1) ? (this.props.truncateAt - 1) : listSize;
const listSize = list.length === 0 ? 0 : list.length - 1;
const maxSelected = listSize > (this.props.truncateAt - 1) ? (this.props.truncateAt - 1) : listSize;
return maxSelected;
},
render: function() {
var classes = classNames({
const classes = classNames({
"mx_AddressSelector": true,
"mx_AddressSelector_empty": this.props.addressList.length === 0,
});
@ -168,5 +168,5 @@ export default React.createClass({
{ this.createAddressListTiles() }
</div>
);
}
},
});

View File

@ -45,11 +45,12 @@ export default React.createClass({
const address = this.props.address;
const name = address.displayName || address.address;
let imgUrls = [];
const imgUrls = [];
const isMatrixAddress = ['mx-user-id', 'mx-room-id'].includes(address.addressType);
if (address.addressType === "mx" && address.avatarMxc) {
if (isMatrixAddress && address.avatarMxc) {
imgUrls.push(MatrixClientPeg.get().mxcUrlToHttp(
address.avatarMxc, 25, 25, 'crop'
address.avatarMxc, 25, 25, 'crop',
));
} else if (address.addressType === 'email') {
imgUrls.push('img/icon-email-user.svg');
@ -77,7 +78,7 @@ export default React.createClass({
let info;
let error = false;
if (address.addressType === "mx" && address.isKnown) {
if (isMatrixAddress && address.isKnown) {
const idClasses = classNames({
"mx_AddressTile_id": true,
"mx_AddressTile_justified": this.props.justified,
@ -89,7 +90,7 @@ export default React.createClass({
<div className={idClasses}>{ address.address }</div>
</div>
);
} else if (address.addressType === "mx") {
} else if (isMatrixAddress) {
const unknownMxClasses = classNames({
"mx_AddressTile_unknownMx": true,
"mx_AddressTile_justified": this.props.justified,
@ -106,24 +107,24 @@ export default React.createClass({
let nameNode = null;
if (address.displayName) {
nameNode = <div className={nameClasses}>{ address.displayName }</div>
nameNode = <div className={nameClasses}>{ address.displayName }</div>;
}
info = (
<div className="mx_AddressTile_mx">
<div className={emailClasses}>{ address.address }</div>
{nameNode}
{ nameNode }
</div>
);
} else {
error = true;
var unknownClasses = classNames({
const unknownClasses = classNames({
"mx_AddressTile_unknown": true,
"mx_AddressTile_justified": this.props.justified,
});
info = (
<div className={unknownClasses}>{_t("Unknown Address")}</div>
<div className={unknownClasses}>{ _t("Unknown Address") }</div>
);
}
@ -150,5 +151,5 @@ export default React.createClass({
{ dismiss }
</div>
);
}
},
});

View File

@ -50,16 +50,16 @@ export default class AppPermission extends React.Component {
let e2eWarningText;
if (this.props.isRoomEncrypted) {
e2eWarningText =
<span className='mx_AppPermissionWarningTextLabel'>{_t('NOTE: Apps are not end-to-end encrypted')}</span>;
<span className='mx_AppPermissionWarningTextLabel'>{ _t('NOTE: Apps are not end-to-end encrypted') }</span>;
}
return (
<div className='mx_AppPermissionWarning'>
<div className='mx_AppPermissionWarningImage'>
<img src='img/warning.svg' alt={_t('Warning!')}/>
<img src='img/warning.svg' alt={_t('Warning!')} />
</div>
<div className='mx_AppPermissionWarningText'>
<span className='mx_AppPermissionWarningTextLabel'>{_t('Do you want to load widget from URL:')}</span> <span className='mx_AppPermissionWarningTextURL'>{this.state.curlBase}</span>
{e2eWarningText}
<span className='mx_AppPermissionWarningTextLabel'>{ _t('Do you want to load widget from URL:') }</span> <span className='mx_AppPermissionWarningTextURL'>{ this.state.curlBase }</span>
{ e2eWarningText }
</div>
<input
className='mx_AppPermissionButton'

View File

@ -19,10 +19,11 @@ limitations under the License.
import url from 'url';
import React from 'react';
import MatrixClientPeg from '../../../MatrixClientPeg';
import PlatformPeg from '../../../PlatformPeg';
import ScalarAuthClient from '../../../ScalarAuthClient';
import SdkConfig from '../../../SdkConfig';
import Modal from '../../../Modal';
import { _t } from '../../../languageHandler';
import { _t, _td } from '../../../languageHandler';
import sdk from '../../../index';
import AppPermission from './AppPermission';
import AppWarning from './AppWarning';
@ -127,6 +128,30 @@ export default React.createClass({
loading: false,
});
});
window.addEventListener('message', this._onMessage, false);
},
componentWillUnmount() {
window.removeEventListener('message', this._onMessage);
},
_onMessage(event) {
if (this.props.type !== 'jitsi') {
return;
}
if (!event.origin) {
event.origin = event.originalEvent.origin;
}
if (!this.state.widgetUrl.startsWith(event.origin)) {
return;
}
if (event.data.widgetAction === 'jitsi_iframe_loaded') {
const iframe = this.refs.appFrame.contentWindow
.document.querySelector('iframe[id^="jitsiConferenceFrame"]');
PlatformPeg.get().setupScreenSharingForIframe(iframe);
}
},
_canUserModify: function() {
@ -170,9 +195,9 @@ export default React.createClass({
// These strings are translated at the point that they are inserted in to the DOM, in the render method
_deleteWidgetLabel() {
if (this._canUserModify()) {
return 'Delete widget';
return _td('Delete widget');
}
return 'Revoke widget access';
return _td('Revoke widget access');
},
/* TODO -- Store permission in account data so that it is persisted across multiple devices */
@ -236,7 +261,7 @@ export default React.createClass({
if (this.state.loading) {
appTileBody = (
<div className='mx_AppTileBody mx_AppLoading'>
<MessageSpinner msg='Loading...'/>
<MessageSpinner msg='Loading...' />
</div>
);
} else if (this.state.hasPermissionToLoad == true) {
@ -287,19 +312,19 @@ export default React.createClass({
return (
<div className={this.props.fullWidth ? "mx_AppTileFullWidth" : "mx_AppTile"} id={this.props.id}>
<div ref="menu_bar" className="mx_AppTileMenuBar" onClick={this.onClickMenuBar}>
{this.formatAppTileName()}
{ this.formatAppTileName() }
<span className="mx_AppTileMenuBarWidgets">
{/* Edit widget */}
{showEditButton && <img
{ /* Edit widget */ }
{ showEditButton && <img
src="img/edit.svg"
className="mx_filterFlipColor mx_AppTileMenuBarWidget mx_AppTileMenuBarWidgetPadding"
width="8" height="8"
alt={_t('Edit')}
title={_t('Edit')}
onClick={this._onEditClick}
/>}
/> }
{/* Delete widget */}
{ /* Delete widget */ }
<img src={deleteIcon}
className={deleteClasses}
width="8" height="8"
@ -309,7 +334,7 @@ export default React.createClass({
/>
</span>
</div>
{appTileBody}
{ appTileBody }
</div>
);
},

View File

@ -6,10 +6,10 @@ const AppWarning = (props) => {
return (
<div className='mx_AppPermissionWarning'>
<div className='mx_AppPermissionWarningImage'>
<img src='img/warning.svg' alt={_t('Warning!')}/>
<img src='img/warning.svg' alt={_t('Warning!')} />
</div>
<div className='mx_AppPermissionWarningText'>
<span className='mx_AppPermissionWarningTextLabel'>{props.errorMsg}</span>
<span className='mx_AppPermissionWarningTextLabel'>{ props.errorMsg }</span>
</div>
</div>
);

View File

@ -24,7 +24,7 @@ const CreateRoomButton = function(props) {
return (
<ActionButton action="view_create_room"
mouseOverAction={props.callout ? "callout_create_room" : null}
label={ _t("Create new room") }
label={_t("Create new room")}
iconPath="img/icons-create-room.svg"
size={props.size}
tooltip={props.tooltip}

View File

@ -30,7 +30,7 @@ export default React.createClass({
getInitialState: function() {
return {
device: this.props.device
device: this.props.device,
};
},
@ -60,37 +60,37 @@ export default React.createClass({
onUnverifyClick: function() {
MatrixClientPeg.get().setDeviceVerified(
this.props.userId, this.state.device.deviceId, false
this.props.userId, this.state.device.deviceId, false,
);
},
onBlacklistClick: function() {
MatrixClientPeg.get().setDeviceBlocked(
this.props.userId, this.state.device.deviceId, true
this.props.userId, this.state.device.deviceId, true,
);
},
onUnblacklistClick: function() {
MatrixClientPeg.get().setDeviceBlocked(
this.props.userId, this.state.device.deviceId, false
this.props.userId, this.state.device.deviceId, false,
);
},
render: function() {
var blacklistButton = null, verifyButton = null;
let blacklistButton = null, verifyButton = null;
if (this.state.device.isBlocked()) {
blacklistButton = (
<button className="mx_MemberDeviceInfo_textButton mx_MemberDeviceInfo_unblacklist"
onClick={this.onUnblacklistClick}>
{_t("Unblacklist")}
{ _t("Unblacklist") }
</button>
);
} else {
blacklistButton = (
<button className="mx_MemberDeviceInfo_textButton mx_MemberDeviceInfo_blacklist"
onClick={this.onBlacklistClick}>
{_t("Blacklist")}
{ _t("Blacklist") }
</button>
);
}
@ -99,14 +99,14 @@ export default React.createClass({
verifyButton = (
<button className="mx_MemberDeviceInfo_textButton mx_MemberDeviceInfo_unverify"
onClick={this.onUnverifyClick}>
{_t("Unverify")}
{ _t("Unverify") }
</button>
);
} else {
verifyButton = (
<button className="mx_MemberDeviceInfo_textButton mx_MemberDeviceInfo_verify"
onClick={this.onVerifyClick}>
{_t("Verify...")}
{ _t("Verify...") }
</button>
);
}

View File

@ -95,7 +95,7 @@ export default class DirectorySearchBox extends React.Component {
onChange={this._onChange} onKeyUp={this._onKeyUp}
placeholder={this.props.placeholder} autoFocus
/>
{join_button}
{ join_button }
<span className="mx_DirectorySearchBox_clear_wrapper">
<span className="mx_DirectorySearchBox_clear" onClick={this._onClearClick} />
</span>

View File

@ -26,6 +26,12 @@ class MenuOption extends React.Component {
this._onClick = this._onClick.bind(this);
}
getDefaultProps() {
return {
disabled: false,
};
}
_onMouseEnter() {
this.props.onMouseEnter(this.props.dropdownKey);
}
@ -46,15 +52,15 @@ class MenuOption extends React.Component {
onClick={this._onClick} onKeyPress={this._onKeyPress}
onMouseEnter={this._onMouseEnter}
>
{this.props.children}
</div>
{ this.props.children }
</div>;
}
};
}
MenuOption.propTypes = {
children: React.PropTypes.oneOfType([
React.PropTypes.arrayOf(React.PropTypes.node),
React.PropTypes.node
React.PropTypes.node,
]),
highlighted: React.PropTypes.bool,
dropdownKey: React.PropTypes.string,
@ -153,6 +159,8 @@ export default class Dropdown extends React.Component {
}
_onInputClick(ev) {
if (this.props.disabled) return;
if (!this.state.expanded) {
this.setState({
expanded: true,
@ -250,13 +258,13 @@ export default class Dropdown extends React.Component {
onMouseEnter={this._setHighlightedOption}
onClick={this._onMenuOptionClick}
>
{child}
{ child }
</MenuOption>
);
});
if (options.length === 0) {
return [<div key="0" className="mx_Dropdown_option">
{_t("No results")}
{ _t("No results") }
</div>];
}
return options;
@ -279,7 +287,7 @@ export default class Dropdown extends React.Component {
/>;
}
menu = <div className="mx_Dropdown_menu" style={menuStyle}>
{this._getMenuOptions()}
{ this._getMenuOptions() }
</div>;
}
@ -288,12 +296,13 @@ export default class Dropdown extends React.Component {
this.props.getShortOption(this.props.value) :
this.childrenByKey[this.props.value];
currentValue = <div className="mx_Dropdown_option">
{selectedChild}
</div>
{ selectedChild }
</div>;
}
const dropdownClasses = {
mx_Dropdown: true,
mx_Dropdown_disabled: this.props.disabled,
};
if (this.props.className) {
dropdownClasses[this.props.className] = true;
@ -303,9 +312,9 @@ export default class Dropdown extends React.Component {
// to the input, but overflows below it. The root contains both.
return <div className={classnames(dropdownClasses)} ref={this._collectRoot}>
<AccessibleButton className="mx_Dropdown_input" onClick={this._onInputClick}>
{currentValue}
{ currentValue }
<span className="mx_Dropdown_arrow"></span>
{menu}
{ menu }
</AccessibleButton>
</div>;
}
@ -329,4 +338,6 @@ Dropdown.propTypes = {
// in the dropped-down menu.
getShortOption: React.PropTypes.func,
value: React.PropTypes.string,
}
// negative for consistency with HTML
disabled: React.PropTypes.bool,
};

View File

@ -0,0 +1,149 @@
/*
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.
*/
import React from 'react';
import PropTypes from 'prop-types';
import sdk from '../../../index';
import {_t} from '../../../languageHandler.js';
const EditableItem = React.createClass({
displayName: 'EditableItem',
propTypes: {
initialValue: PropTypes.string,
index: PropTypes.number,
placeholder: PropTypes.string,
onChange: PropTypes.func,
onRemove: PropTypes.func,
onAdd: PropTypes.func,
addOnChange: PropTypes.bool,
},
onChange: function(value) {
this.setState({ value });
if (this.props.onChange) this.props.onChange(value, this.props.index);
if (this.props.addOnChange && this.props.onAdd) this.props.onAdd(value);
},
onRemove: function() {
if (this.props.onRemove) this.props.onRemove(this.props.index);
},
onAdd: function() {
if (this.props.onAdd) this.props.onAdd(this.state.value);
},
render: function() {
const EditableText = sdk.getComponent('elements.EditableText');
return <div className="mx_EditableItem">
<EditableText
className="mx_EditableItem_editable"
placeholderClassName="mx_EditableItem_editablePlaceholder"
placeholder={this.props.placeholder}
blurToCancel={false}
editable={true}
initialValue={this.props.initialValue}
onValueChanged={this.onChange} />
{ this.props.onAdd ?
<div className="mx_EditableItem_addButton">
<img className="mx_filterFlipColor"
src="img/plus.svg" width="14" height="14"
alt={_t("Add")} onClick={this.onAdd} />
</div>
:
<div className="mx_EditableItem_removeButton">
<img className="mx_filterFlipColor"
src="img/cancel-small.svg" width="14" height="14"
alt={_t("Delete")} onClick={this.onRemove} />
</div>
}
</div>;
},
});
module.exports = React.createClass({
displayName: 'EditableItemList',
propTypes: {
items: PropTypes.arrayOf(PropTypes.string).isRequired,
onNewItemChanged: PropTypes.func,
onItemAdded: PropTypes.func,
onItemEdited: PropTypes.func,
onItemRemoved: PropTypes. func,
},
getDefaultProps: function() {
return {
onItemAdded: () => {},
onItemEdited: () => {},
onItemRemoved: () => {},
onNewItemChanged: () => {},
};
},
onItemAdded: function(value) {
this.props.onItemAdded(value);
},
onItemEdited: function(value, index) {
if (value.length === 0) {
this.onItemRemoved(index);
} else {
this.props.onItemEdited(value, index);
}
},
onItemRemoved: function(index) {
this.props.onItemRemoved(index);
},
onNewItemChanged: function(value) {
this.props.onNewItemChanged(value);
},
render: function() {
const editableItems = this.props.items.map((item, index) => {
return <EditableItem
key={index}
index={index}
initialValue={item}
onChange={this.onItemEdited}
onRemove={this.onItemRemoved}
placeholder={this.props.placeholder}
/>;
});
const label = this.props.items.length > 0 ?
this.props.itemsLabel : this.props.noItemsLabel;
return (<div className="mx_EditableItemList">
<div className="mx_EditableItemList_label">
{ label }
</div>
{ editableItems }
<EditableItem
key={-1}
initialValue={this.props.newItem}
onAdd={this.onItemAdded}
onChange={this.onNewItemChanged}
addOnChange={true}
placeholder={this.props.placeholder}
/>
</div>);
},
});

View File

@ -16,7 +16,7 @@ limitations under the License.
'use strict';
var React = require('react');
const React = require('react');
const KEY_TAB = 9;
const KEY_SHIFT = 16;
@ -65,7 +65,9 @@ module.exports = React.createClass({
},
componentWillReceiveProps: function(nextProps) {
if (nextProps.initialValue !== this.props.initialValue) {
if (nextProps.initialValue !== this.props.initialValue ||
nextProps.initialValue !== this.value
) {
this.value = nextProps.initialValue;
if (this.refs.editable_div) {
this.showPlaceholder(!this.value);
@ -93,8 +95,7 @@ module.exports = React.createClass({
this.refs.editable_div.setAttribute("class", this.props.className + " " + this.props.placeholderClassName);
this.placeholder = true;
this.value = '';
}
else {
} else {
this.refs.editable_div.textContent = this.value;
this.refs.editable_div.setAttribute("class", this.props.className);
this.placeholder = false;
@ -150,8 +151,7 @@ module.exports = React.createClass({
if (!ev.target.textContent) {
this.showPlaceholder(true);
}
else if (!this.placeholder) {
} else if (!this.placeholder) {
this.value = ev.target.textContent;
}
@ -175,21 +175,21 @@ module.exports = React.createClass({
onFocus: function(ev) {
//ev.target.setSelectionRange(0, ev.target.textContent.length);
var node = ev.target.childNodes[0];
const node = ev.target.childNodes[0];
if (node) {
var range = document.createRange();
const range = document.createRange();
range.setStart(node, 0);
range.setEnd(node, node.length);
var sel = window.getSelection();
const sel = window.getSelection();
sel.removeAllRanges();
sel.addRange(range);
}
},
onFinish: function(ev, shouldSubmit) {
var self = this;
var submit = (ev.key === "Enter") || shouldSubmit;
const self = this;
const submit = (ev.key === "Enter") || shouldSubmit;
this.setState({
phase: this.Phases.Display,
}, function() {
@ -200,19 +200,16 @@ module.exports = React.createClass({
},
onBlur: function(ev) {
var sel = window.getSelection();
const sel = window.getSelection();
sel.removeAllRanges();
if (this.props.blurToCancel)
{this.cancelEdit();}
else
{this.onFinish(ev, this.props.blurToSubmit);}
if (this.props.blurToCancel) {this.cancelEdit();} else {this.onFinish(ev, this.props.blurToSubmit);}
this.showPlaceholder(!this.value);
},
render: function() {
var editable_el;
let editable_el;
if (!this.props.editable || (this.state.phase == this.Phases.Display && (this.props.label || this.props.labelClassName) && !this.value)) {
// show the label
@ -224,5 +221,5 @@ module.exports = React.createClass({
}
return editable_el;
}
},
});

View File

@ -64,7 +64,7 @@ export default class EditableTextContainer extends React.Component {
errorString: error.toString(),
busy: false,
});
}
},
);
}
@ -96,22 +96,22 @@ export default class EditableTextContainer extends React.Component {
errorString: error.toString(),
busy: false,
});
}
},
);
}
render() {
if (this.state.busy) {
var Loader = sdk.getComponent("elements.Spinner");
const Loader = sdk.getComponent("elements.Spinner");
return (
<Loader />
);
} else if (this.state.errorString) {
return (
<div className="error">{this.state.errorString}</div>
<div className="error">{ this.state.errorString }</div>
);
} else {
var EditableText = sdk.getComponent('elements.EditableText');
const EditableText = sdk.getComponent('elements.EditableText');
return (
<EditableText initialValue={this.state.value}
placeholder={this.props.placeholder}

Some files were not shown because too many files have changed in this diff Show More