Merge branch 'master' of https://github.com/matrix-org/matrix-react-sdk into rxl881/apps
This commit is contained in:
commit
f9f924bbd6
163 changed files with 11304 additions and 2243 deletions
|
@ -64,7 +64,7 @@ module.exports = {
|
||||||
// to JSX.
|
// to JSX.
|
||||||
ignorePattern: '^\\s*<',
|
ignorePattern: '^\\s*<',
|
||||||
ignoreComments: true,
|
ignoreComments: true,
|
||||||
code: 90,
|
code: 120,
|
||||||
}],
|
}],
|
||||||
"valid-jsdoc": ["warn"],
|
"valid-jsdoc": ["warn"],
|
||||||
"new-cap": ["warn"],
|
"new-cap": ["warn"],
|
||||||
|
|
3
.gitignore
vendored
3
.gitignore
vendored
|
@ -9,3 +9,6 @@ npm-debug.log
|
||||||
|
|
||||||
# test reports created by karma
|
# test reports created by karma
|
||||||
/karma-reports
|
/karma-reports
|
||||||
|
|
||||||
|
/.idea
|
||||||
|
/src/component-index.js
|
||||||
|
|
|
@ -9,11 +9,16 @@ set -ev
|
||||||
RIOT_WEB_DIR=riot-web
|
RIOT_WEB_DIR=riot-web
|
||||||
REACT_SDK_DIR=`pwd`
|
REACT_SDK_DIR=`pwd`
|
||||||
|
|
||||||
git clone --depth=1 --branch develop https://github.com/vector-im/riot-web.git \
|
curbranch="${TRAVIS_PULL_REQUEST_BRANCH:-$TRAVIS_BRANCH}"
|
||||||
|
echo "Determined branch to be $curbranch"
|
||||||
|
|
||||||
|
git clone https://github.com/vector-im/riot-web.git \
|
||||||
"$RIOT_WEB_DIR"
|
"$RIOT_WEB_DIR"
|
||||||
|
|
||||||
cd "$RIOT_WEB_DIR"
|
cd "$RIOT_WEB_DIR"
|
||||||
|
|
||||||
|
git checkout "$curbranch" || git checkout develop
|
||||||
|
|
||||||
mkdir node_modules
|
mkdir node_modules
|
||||||
npm install
|
npm install
|
||||||
|
|
||||||
|
|
|
@ -5,5 +5,6 @@ install:
|
||||||
- npm install
|
- npm install
|
||||||
- (cd node_modules/matrix-js-sdk && npm install)
|
- (cd node_modules/matrix-js-sdk && npm install)
|
||||||
script:
|
script:
|
||||||
- npm run test
|
# don't run the riot tests unless the react-sdk tests pass, otherwise
|
||||||
- ./.travis-test-riot.sh
|
# the output is confusing.
|
||||||
|
- npm run test && ./.travis-test-riot.sh
|
||||||
|
|
333
CHANGELOG.md
333
CHANGELOG.md
|
@ -1,3 +1,336 @@
|
||||||
|
Changes in [0.9.2](https://github.com/matrix-org/matrix-react-sdk/releases/tag/v0.9.2) (2017-06-06)
|
||||||
|
===================================================================================================
|
||||||
|
[Full Changelog](https://github.com/matrix-org/matrix-react-sdk/compare/v0.9.1...v0.9.2)
|
||||||
|
|
||||||
|
* Hotfix: Allow password reset when logged in
|
||||||
|
[\#1044](https://github.com/matrix-org/matrix-react-sdk/pull/1044)
|
||||||
|
|
||||||
|
Changes in [0.9.1](https://github.com/matrix-org/matrix-react-sdk/releases/tag/v0.9.1) (2017-06-02)
|
||||||
|
===================================================================================================
|
||||||
|
[Full Changelog](https://github.com/matrix-org/matrix-react-sdk/compare/v0.9.0...v0.9.1)
|
||||||
|
|
||||||
|
* Update from Weblate.
|
||||||
|
[\#1012](https://github.com/matrix-org/matrix-react-sdk/pull/1012)
|
||||||
|
* typo, missing import and mis-casing
|
||||||
|
[\#1014](https://github.com/matrix-org/matrix-react-sdk/pull/1014)
|
||||||
|
* Update from Weblate.
|
||||||
|
[\#1010](https://github.com/matrix-org/matrix-react-sdk/pull/1010)
|
||||||
|
|
||||||
|
Changes in [0.9.0](https://github.com/matrix-org/matrix-react-sdk/releases/tag/v0.9.0) (2017-06-02)
|
||||||
|
===================================================================================================
|
||||||
|
[Full Changelog](https://github.com/matrix-org/matrix-react-sdk/compare/v0.9.0-rc.2...v0.9.0)
|
||||||
|
|
||||||
|
* sync pt with pt_BR
|
||||||
|
[\#1009](https://github.com/matrix-org/matrix-react-sdk/pull/1009)
|
||||||
|
* Update from Weblate.
|
||||||
|
[\#1008](https://github.com/matrix-org/matrix-react-sdk/pull/1008)
|
||||||
|
* Update from Weblate.
|
||||||
|
[\#1003](https://github.com/matrix-org/matrix-react-sdk/pull/1003)
|
||||||
|
* allow hiding redactions, restoring old behaviour
|
||||||
|
[\#1004](https://github.com/matrix-org/matrix-react-sdk/pull/1004)
|
||||||
|
* Add missing translations
|
||||||
|
[\#1005](https://github.com/matrix-org/matrix-react-sdk/pull/1005)
|
||||||
|
|
||||||
|
Changes in [0.9.0-rc.2](https://github.com/matrix-org/matrix-react-sdk/releases/tag/v0.9.0-rc.2) (2017-06-02)
|
||||||
|
=============================================================================================================
|
||||||
|
[Full Changelog](https://github.com/matrix-org/matrix-react-sdk/compare/v0.9.0-rc.1...v0.9.0-rc.2)
|
||||||
|
|
||||||
|
* Update from Weblate.
|
||||||
|
[\#1002](https://github.com/matrix-org/matrix-react-sdk/pull/1002)
|
||||||
|
* webrtc config electron
|
||||||
|
[\#850](https://github.com/matrix-org/matrix-react-sdk/pull/850)
|
||||||
|
* enable useCompactLayout user setting an add a class when it's enabled
|
||||||
|
[\#986](https://github.com/matrix-org/matrix-react-sdk/pull/986)
|
||||||
|
* Update from Weblate.
|
||||||
|
[\#987](https://github.com/matrix-org/matrix-react-sdk/pull/987)
|
||||||
|
* Translation fixes for everything but src/components
|
||||||
|
[\#990](https://github.com/matrix-org/matrix-react-sdk/pull/990)
|
||||||
|
* Fix tests
|
||||||
|
[\#1001](https://github.com/matrix-org/matrix-react-sdk/pull/1001)
|
||||||
|
* Fix tests for PR #989
|
||||||
|
[\#999](https://github.com/matrix-org/matrix-react-sdk/pull/999)
|
||||||
|
* Revert "Revert "add labels to language picker""
|
||||||
|
[\#1000](https://github.com/matrix-org/matrix-react-sdk/pull/1000)
|
||||||
|
* maybe fixxy [Electron] external thing?
|
||||||
|
[\#997](https://github.com/matrix-org/matrix-react-sdk/pull/997)
|
||||||
|
* travisci: Don't run the riot-web tests if the react-sdk tests fail
|
||||||
|
[\#992](https://github.com/matrix-org/matrix-react-sdk/pull/992)
|
||||||
|
* Support 12hr time on DateSeparator
|
||||||
|
[\#991](https://github.com/matrix-org/matrix-react-sdk/pull/991)
|
||||||
|
* Revert "add labels to language picker"
|
||||||
|
[\#994](https://github.com/matrix-org/matrix-react-sdk/pull/994)
|
||||||
|
* Call MatrixClient.clearStores on logout
|
||||||
|
[\#983](https://github.com/matrix-org/matrix-react-sdk/pull/983)
|
||||||
|
* Matthew/room avatar event
|
||||||
|
[\#988](https://github.com/matrix-org/matrix-react-sdk/pull/988)
|
||||||
|
* add labels to language picker
|
||||||
|
[\#989](https://github.com/matrix-org/matrix-react-sdk/pull/989)
|
||||||
|
* Update from Weblate.
|
||||||
|
[\#981](https://github.com/matrix-org/matrix-react-sdk/pull/981)
|
||||||
|
|
||||||
|
Changes in [0.9.0-rc.1](https://github.com/matrix-org/matrix-react-sdk/releases/tag/v0.9.0-rc.1) (2017-06-01)
|
||||||
|
=============================================================================================================
|
||||||
|
[Full Changelog](https://github.com/matrix-org/matrix-react-sdk/compare/v0.8.9...v0.9.0-rc.1)
|
||||||
|
|
||||||
|
* Fix rare case where presence duration is undefined
|
||||||
|
[\#982](https://github.com/matrix-org/matrix-react-sdk/pull/982)
|
||||||
|
* add concept of platform handling loudNotifications (bings/pings/whatHaveYou)
|
||||||
|
[\#985](https://github.com/matrix-org/matrix-react-sdk/pull/985)
|
||||||
|
* Fixes to i18n code
|
||||||
|
[\#984](https://github.com/matrix-org/matrix-react-sdk/pull/984)
|
||||||
|
* Update from Weblate.
|
||||||
|
[\#978](https://github.com/matrix-org/matrix-react-sdk/pull/978)
|
||||||
|
* Add partial support for RTL languages
|
||||||
|
[\#955](https://github.com/matrix-org/matrix-react-sdk/pull/955)
|
||||||
|
* Added two strings to translate
|
||||||
|
[\#975](https://github.com/matrix-org/matrix-react-sdk/pull/975)
|
||||||
|
* Update from Weblate.
|
||||||
|
[\#976](https://github.com/matrix-org/matrix-react-sdk/pull/976)
|
||||||
|
* Update from Weblate.
|
||||||
|
[\#974](https://github.com/matrix-org/matrix-react-sdk/pull/974)
|
||||||
|
* Initial Electron Settings - for Auto Launch
|
||||||
|
[\#920](https://github.com/matrix-org/matrix-react-sdk/pull/920)
|
||||||
|
* Fix missing string in the room settings
|
||||||
|
[\#973](https://github.com/matrix-org/matrix-react-sdk/pull/973)
|
||||||
|
* fix error in i18n string
|
||||||
|
[\#972](https://github.com/matrix-org/matrix-react-sdk/pull/972)
|
||||||
|
* Update from Weblate.
|
||||||
|
[\#970](https://github.com/matrix-org/matrix-react-sdk/pull/970)
|
||||||
|
* Support 12hr time in full date
|
||||||
|
[\#971](https://github.com/matrix-org/matrix-react-sdk/pull/971)
|
||||||
|
* Add _tJsx()
|
||||||
|
[\#968](https://github.com/matrix-org/matrix-react-sdk/pull/968)
|
||||||
|
* Update from Weblate.
|
||||||
|
[\#966](https://github.com/matrix-org/matrix-react-sdk/pull/966)
|
||||||
|
* Remove space between time and AM/PM
|
||||||
|
[\#969](https://github.com/matrix-org/matrix-react-sdk/pull/969)
|
||||||
|
* Piwik Analytics
|
||||||
|
[\#948](https://github.com/matrix-org/matrix-react-sdk/pull/948)
|
||||||
|
* Update from Weblate.
|
||||||
|
[\#965](https://github.com/matrix-org/matrix-react-sdk/pull/965)
|
||||||
|
* Improve ChatInviteDialog perf by ditching fuse, using indexOf and
|
||||||
|
lastActiveTs()
|
||||||
|
[\#960](https://github.com/matrix-org/matrix-react-sdk/pull/960)
|
||||||
|
* Say "X removed the room name" instead of showing nothing
|
||||||
|
[\#958](https://github.com/matrix-org/matrix-react-sdk/pull/958)
|
||||||
|
* roomview/roomheader fixes
|
||||||
|
[\#959](https://github.com/matrix-org/matrix-react-sdk/pull/959)
|
||||||
|
* Update from Weblate.
|
||||||
|
[\#953](https://github.com/matrix-org/matrix-react-sdk/pull/953)
|
||||||
|
* fix i18n in a situation where navigator.languages=[]
|
||||||
|
[\#956](https://github.com/matrix-org/matrix-react-sdk/pull/956)
|
||||||
|
* `t_` -> `_t` fix typo
|
||||||
|
[\#957](https://github.com/matrix-org/matrix-react-sdk/pull/957)
|
||||||
|
* Change redact -> remove for clarity
|
||||||
|
[\#831](https://github.com/matrix-org/matrix-react-sdk/pull/831)
|
||||||
|
* Update from Weblate.
|
||||||
|
[\#950](https://github.com/matrix-org/matrix-react-sdk/pull/950)
|
||||||
|
* fix mis-linting - missed it in code review :(
|
||||||
|
[\#952](https://github.com/matrix-org/matrix-react-sdk/pull/952)
|
||||||
|
* i18n fixes
|
||||||
|
[\#951](https://github.com/matrix-org/matrix-react-sdk/pull/951)
|
||||||
|
* Message Forwarding
|
||||||
|
[\#812](https://github.com/matrix-org/matrix-react-sdk/pull/812)
|
||||||
|
* don't focus_composer on window focus
|
||||||
|
[\#944](https://github.com/matrix-org/matrix-react-sdk/pull/944)
|
||||||
|
* Fix vector-im/riot-web#4042
|
||||||
|
[\#947](https://github.com/matrix-org/matrix-react-sdk/pull/947)
|
||||||
|
* import _t, drop two unused imports
|
||||||
|
[\#946](https://github.com/matrix-org/matrix-react-sdk/pull/946)
|
||||||
|
* Fix punctuation in TextForEvent to be i18n'd consistently
|
||||||
|
[\#945](https://github.com/matrix-org/matrix-react-sdk/pull/945)
|
||||||
|
* actually wire up alwaysShowTimestamps
|
||||||
|
[\#940](https://github.com/matrix-org/matrix-react-sdk/pull/940)
|
||||||
|
* Update from Weblate.
|
||||||
|
[\#943](https://github.com/matrix-org/matrix-react-sdk/pull/943)
|
||||||
|
* Update from Weblate.
|
||||||
|
[\#942](https://github.com/matrix-org/matrix-react-sdk/pull/942)
|
||||||
|
* Update from Weblate.
|
||||||
|
[\#941](https://github.com/matrix-org/matrix-react-sdk/pull/941)
|
||||||
|
* Update from Weblate.
|
||||||
|
[\#938](https://github.com/matrix-org/matrix-react-sdk/pull/938)
|
||||||
|
* Fix PM being AM
|
||||||
|
[\#939](https://github.com/matrix-org/matrix-react-sdk/pull/939)
|
||||||
|
* pass call state through dispatcher, for poor electron
|
||||||
|
[\#918](https://github.com/matrix-org/matrix-react-sdk/pull/918)
|
||||||
|
* Translations!
|
||||||
|
[\#934](https://github.com/matrix-org/matrix-react-sdk/pull/934)
|
||||||
|
* Remove suffix and prefix from login input username
|
||||||
|
[\#906](https://github.com/matrix-org/matrix-react-sdk/pull/906)
|
||||||
|
* Kierangould/12hourtimestamp
|
||||||
|
[\#903](https://github.com/matrix-org/matrix-react-sdk/pull/903)
|
||||||
|
* Don't include src in the test resolve root
|
||||||
|
[\#931](https://github.com/matrix-org/matrix-react-sdk/pull/931)
|
||||||
|
* Make the linked versions open a new tab, turt2live complained :P
|
||||||
|
[\#910](https://github.com/matrix-org/matrix-react-sdk/pull/910)
|
||||||
|
* Fix lint errors in SlashCommands
|
||||||
|
[\#919](https://github.com/matrix-org/matrix-react-sdk/pull/919)
|
||||||
|
* autoFocus input box
|
||||||
|
[\#911](https://github.com/matrix-org/matrix-react-sdk/pull/911)
|
||||||
|
* Make travis test against riot-web new-guest-access
|
||||||
|
[\#917](https://github.com/matrix-org/matrix-react-sdk/pull/917)
|
||||||
|
* Add right-branch logic to travis test script
|
||||||
|
[\#916](https://github.com/matrix-org/matrix-react-sdk/pull/916)
|
||||||
|
* Group e2e keys into blocks of 4 characters
|
||||||
|
[\#914](https://github.com/matrix-org/matrix-react-sdk/pull/914)
|
||||||
|
* Factor out DeviceVerifyDialog
|
||||||
|
[\#913](https://github.com/matrix-org/matrix-react-sdk/pull/913)
|
||||||
|
* Fix 'missing page_type' error
|
||||||
|
[\#909](https://github.com/matrix-org/matrix-react-sdk/pull/909)
|
||||||
|
* code style update
|
||||||
|
[\#904](https://github.com/matrix-org/matrix-react-sdk/pull/904)
|
||||||
|
|
||||||
|
Changes in [0.8.9](https://github.com/matrix-org/matrix-react-sdk/releases/tag/v0.8.9) (2017-05-22)
|
||||||
|
===================================================================================================
|
||||||
|
[Full Changelog](https://github.com/matrix-org/matrix-react-sdk/compare/v0.8.9-rc.1...v0.8.9)
|
||||||
|
|
||||||
|
* No changes
|
||||||
|
|
||||||
|
|
||||||
|
Changes in [0.8.9-rc.1](https://github.com/matrix-org/matrix-react-sdk/releases/tag/v0.8.9-rc.1) (2017-05-19)
|
||||||
|
=============================================================================================================
|
||||||
|
[Full Changelog](https://github.com/matrix-org/matrix-react-sdk/compare/v0.8.8...v0.8.9-rc.1)
|
||||||
|
|
||||||
|
* Prevent an exception getting scroll node
|
||||||
|
[\#902](https://github.com/matrix-org/matrix-react-sdk/pull/902)
|
||||||
|
* Fix a few remaining snags with country dd
|
||||||
|
[\#901](https://github.com/matrix-org/matrix-react-sdk/pull/901)
|
||||||
|
* Add left_aligned class to CountryDropdown
|
||||||
|
[\#900](https://github.com/matrix-org/matrix-react-sdk/pull/900)
|
||||||
|
* Swap to new flag files (which are stored as GB.png)
|
||||||
|
[\#899](https://github.com/matrix-org/matrix-react-sdk/pull/899)
|
||||||
|
* Improve phone number country dropdown for registration and login (Act. 2,
|
||||||
|
Return of the Prefix)
|
||||||
|
[\#897](https://github.com/matrix-org/matrix-react-sdk/pull/897)
|
||||||
|
* Support for pasting files into normal composer
|
||||||
|
[\#892](https://github.com/matrix-org/matrix-react-sdk/pull/892)
|
||||||
|
* tell guests they can't use filepanel until they register
|
||||||
|
[\#887](https://github.com/matrix-org/matrix-react-sdk/pull/887)
|
||||||
|
* Prevent reskindex -w from running when file names have not changed
|
||||||
|
[\#888](https://github.com/matrix-org/matrix-react-sdk/pull/888)
|
||||||
|
* I broke UserSettings for webpack-dev-server
|
||||||
|
[\#884](https://github.com/matrix-org/matrix-react-sdk/pull/884)
|
||||||
|
* various fixes to RoomHeader
|
||||||
|
[\#880](https://github.com/matrix-org/matrix-react-sdk/pull/880)
|
||||||
|
* remove /me whether or not it has a space after it
|
||||||
|
[\#885](https://github.com/matrix-org/matrix-react-sdk/pull/885)
|
||||||
|
* show error if we can't set a filter because no room
|
||||||
|
[\#883](https://github.com/matrix-org/matrix-react-sdk/pull/883)
|
||||||
|
* Fix RM not updating if RR event unpaginated
|
||||||
|
[\#874](https://github.com/matrix-org/matrix-react-sdk/pull/874)
|
||||||
|
* change roomsettings wording
|
||||||
|
[\#878](https://github.com/matrix-org/matrix-react-sdk/pull/878)
|
||||||
|
* make reskindex windows friendly
|
||||||
|
[\#875](https://github.com/matrix-org/matrix-react-sdk/pull/875)
|
||||||
|
* Fixes 2 issues with Dialog closing
|
||||||
|
[\#867](https://github.com/matrix-org/matrix-react-sdk/pull/867)
|
||||||
|
* Automatic Reskindex
|
||||||
|
[\#871](https://github.com/matrix-org/matrix-react-sdk/pull/871)
|
||||||
|
* Put room name in 'leave room' confirmation dialog
|
||||||
|
[\#873](https://github.com/matrix-org/matrix-react-sdk/pull/873)
|
||||||
|
* Fix this/self fail in LeftPanel
|
||||||
|
[\#872](https://github.com/matrix-org/matrix-react-sdk/pull/872)
|
||||||
|
* Don't show null URL previews
|
||||||
|
[\#870](https://github.com/matrix-org/matrix-react-sdk/pull/870)
|
||||||
|
* Fix keys for AddressSelector
|
||||||
|
[\#869](https://github.com/matrix-org/matrix-react-sdk/pull/869)
|
||||||
|
* Make left panel better for new users (mk II)
|
||||||
|
[\#859](https://github.com/matrix-org/matrix-react-sdk/pull/859)
|
||||||
|
* Explicitly save composer content onUnload
|
||||||
|
[\#866](https://github.com/matrix-org/matrix-react-sdk/pull/866)
|
||||||
|
* Warn on unload
|
||||||
|
[\#851](https://github.com/matrix-org/matrix-react-sdk/pull/851)
|
||||||
|
* Log deviceid at login
|
||||||
|
[\#862](https://github.com/matrix-org/matrix-react-sdk/pull/862)
|
||||||
|
* Guests can't send RR so no point trying
|
||||||
|
[\#860](https://github.com/matrix-org/matrix-react-sdk/pull/860)
|
||||||
|
* Remove babelcheck
|
||||||
|
[\#861](https://github.com/matrix-org/matrix-react-sdk/pull/861)
|
||||||
|
* T3chguy/settings versions improvements
|
||||||
|
[\#857](https://github.com/matrix-org/matrix-react-sdk/pull/857)
|
||||||
|
* Change max-len 90->120
|
||||||
|
[\#852](https://github.com/matrix-org/matrix-react-sdk/pull/852)
|
||||||
|
* Remove DM-guessing code
|
||||||
|
[\#829](https://github.com/matrix-org/matrix-react-sdk/pull/829)
|
||||||
|
* Fix jumping to an unread event when in MELS
|
||||||
|
[\#855](https://github.com/matrix-org/matrix-react-sdk/pull/855)
|
||||||
|
* Validate phone number on login
|
||||||
|
[\#856](https://github.com/matrix-org/matrix-react-sdk/pull/856)
|
||||||
|
* Failed to enable HTML5 Notifications Error Dialogs
|
||||||
|
[\#827](https://github.com/matrix-org/matrix-react-sdk/pull/827)
|
||||||
|
* Pin filesize ver to fix break upstream
|
||||||
|
[\#854](https://github.com/matrix-org/matrix-react-sdk/pull/854)
|
||||||
|
* Improve RoomDirectory Look & Feel
|
||||||
|
[\#848](https://github.com/matrix-org/matrix-react-sdk/pull/848)
|
||||||
|
* Only show jumpToReadMarker bar when RM !== RR
|
||||||
|
[\#845](https://github.com/matrix-org/matrix-react-sdk/pull/845)
|
||||||
|
* Allow MELS to have its own RM
|
||||||
|
[\#846](https://github.com/matrix-org/matrix-react-sdk/pull/846)
|
||||||
|
* Use document.onkeydown instead of onkeypress
|
||||||
|
[\#844](https://github.com/matrix-org/matrix-react-sdk/pull/844)
|
||||||
|
* (Room)?Avatar: Request 96x96 avatars on high DPI screens
|
||||||
|
[\#808](https://github.com/matrix-org/matrix-react-sdk/pull/808)
|
||||||
|
* Add mx_EventTile_emote class
|
||||||
|
[\#842](https://github.com/matrix-org/matrix-react-sdk/pull/842)
|
||||||
|
* Fix dialog reappearing after hitting Enter
|
||||||
|
[\#841](https://github.com/matrix-org/matrix-react-sdk/pull/841)
|
||||||
|
* Fix spinner that shows until the first sync
|
||||||
|
[\#840](https://github.com/matrix-org/matrix-react-sdk/pull/840)
|
||||||
|
* Show spinner until first sync has completed
|
||||||
|
[\#839](https://github.com/matrix-org/matrix-react-sdk/pull/839)
|
||||||
|
* Style fixes for LoggedInView
|
||||||
|
[\#838](https://github.com/matrix-org/matrix-react-sdk/pull/838)
|
||||||
|
* Fix specifying custom server for registration
|
||||||
|
[\#834](https://github.com/matrix-org/matrix-react-sdk/pull/834)
|
||||||
|
* Improve country dropdown UX and expose +prefix
|
||||||
|
[\#833](https://github.com/matrix-org/matrix-react-sdk/pull/833)
|
||||||
|
* Fix user settings store
|
||||||
|
[\#836](https://github.com/matrix-org/matrix-react-sdk/pull/836)
|
||||||
|
* show the room name in the UDE Dialog
|
||||||
|
[\#832](https://github.com/matrix-org/matrix-react-sdk/pull/832)
|
||||||
|
* summarise profile changes in MELS
|
||||||
|
[\#826](https://github.com/matrix-org/matrix-react-sdk/pull/826)
|
||||||
|
* Transform h1 and h2 tags to h3 tags
|
||||||
|
[\#820](https://github.com/matrix-org/matrix-react-sdk/pull/820)
|
||||||
|
* limit our keyboard shortcut modifiers correctly
|
||||||
|
[\#825](https://github.com/matrix-org/matrix-react-sdk/pull/825)
|
||||||
|
* Specify cross platform regexes and add olm to noParse
|
||||||
|
[\#823](https://github.com/matrix-org/matrix-react-sdk/pull/823)
|
||||||
|
* Remember element that was in focus before rendering dialog
|
||||||
|
[\#822](https://github.com/matrix-org/matrix-react-sdk/pull/822)
|
||||||
|
* move user settings outward and use built in read receipts disabling
|
||||||
|
[\#824](https://github.com/matrix-org/matrix-react-sdk/pull/824)
|
||||||
|
* File Download Consistency
|
||||||
|
[\#802](https://github.com/matrix-org/matrix-react-sdk/pull/802)
|
||||||
|
* Show Access Token under Advanced in Settings
|
||||||
|
[\#806](https://github.com/matrix-org/matrix-react-sdk/pull/806)
|
||||||
|
* Link tags/commit hashes in the UserSettings version section
|
||||||
|
[\#810](https://github.com/matrix-org/matrix-react-sdk/pull/810)
|
||||||
|
* On return to RoomView from auxPanel, send focus back to Composer
|
||||||
|
[\#813](https://github.com/matrix-org/matrix-react-sdk/pull/813)
|
||||||
|
* Change presence status labels to 'for' instead of 'ago'
|
||||||
|
[\#817](https://github.com/matrix-org/matrix-react-sdk/pull/817)
|
||||||
|
* Disable Scalar Integrations if urls passed to it are falsey
|
||||||
|
[\#816](https://github.com/matrix-org/matrix-react-sdk/pull/816)
|
||||||
|
* Add option to hide other people's read receipts.
|
||||||
|
[\#818](https://github.com/matrix-org/matrix-react-sdk/pull/818)
|
||||||
|
* Add option to not send typing notifications
|
||||||
|
[\#819](https://github.com/matrix-org/matrix-react-sdk/pull/819)
|
||||||
|
* Sync RM across instances of Riot
|
||||||
|
[\#805](https://github.com/matrix-org/matrix-react-sdk/pull/805)
|
||||||
|
* First iteration on improving login UI
|
||||||
|
[\#811](https://github.com/matrix-org/matrix-react-sdk/pull/811)
|
||||||
|
* focus on composer after jumping to bottom
|
||||||
|
[\#809](https://github.com/matrix-org/matrix-react-sdk/pull/809)
|
||||||
|
* Improve RoomList performance via side-stepping React
|
||||||
|
[\#807](https://github.com/matrix-org/matrix-react-sdk/pull/807)
|
||||||
|
* Don't show link preview when link is inside of a quote
|
||||||
|
[\#762](https://github.com/matrix-org/matrix-react-sdk/pull/762)
|
||||||
|
* Escape closes UserSettings
|
||||||
|
[\#765](https://github.com/matrix-org/matrix-react-sdk/pull/765)
|
||||||
|
* Implement user power-level changes in timeline
|
||||||
|
[\#794](https://github.com/matrix-org/matrix-react-sdk/pull/794)
|
||||||
|
|
||||||
Changes in [0.8.8](https://github.com/matrix-org/matrix-react-sdk/releases/tag/v0.8.8) (2017-04-25)
|
Changes in [0.8.8](https://github.com/matrix-org/matrix-react-sdk/releases/tag/v0.8.8) (2017-04-25)
|
||||||
===================================================================================================
|
===================================================================================================
|
||||||
[Full Changelog](https://github.com/matrix-org/matrix-react-sdk/compare/v0.8.8-rc.2...v0.8.8)
|
[Full Changelog](https://github.com/matrix-org/matrix-react-sdk/compare/v0.8.8-rc.2...v0.8.8)
|
||||||
|
|
|
@ -24,6 +24,10 @@ In the interim, `vector-im/riot-web` and `matrix-org/matrix-react-sdk` should
|
||||||
be considered as a single project (for instance, matrix-react-sdk bugs
|
be considered as a single project (for instance, matrix-react-sdk bugs
|
||||||
are currently filed against vector-im/riot-web rather than this project).
|
are currently filed against vector-im/riot-web rather than this project).
|
||||||
|
|
||||||
|
Translation Status
|
||||||
|
==================
|
||||||
|
[![translationsstatus](https://translate.nordgedanken.de/widgets/riot-web/-/multi-auto.svg)](https://translate.nordgedanken.de/engage/riot-web/?utm_source=widget)
|
||||||
|
|
||||||
Developer Guide
|
Developer Guide
|
||||||
===============
|
===============
|
||||||
|
|
||||||
|
@ -190,4 +194,3 @@ Alternative instructions:
|
||||||
* Create an index.html file pulling in your compiled javascript and the
|
* Create an index.html file pulling in your compiled javascript and the
|
||||||
CSS bundle from the skin you use. For now, you'll also need to manually
|
CSS bundle from the skin you use. For now, you'll also need to manually
|
||||||
import CSS from any skins that your skin inherts from.
|
import CSS from any skins that your skin inherts from.
|
||||||
|
|
||||||
|
|
|
@ -69,25 +69,41 @@ General Style
|
||||||
console.log("I am a fish"); // Bad
|
console.log("I am a fish"); // Bad
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
- No new line before else, catch, finally, etc:
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
if (x) {
|
||||||
|
console.log("I am a fish");
|
||||||
|
} else {
|
||||||
|
console.log("I am a chimp"); // Good
|
||||||
|
}
|
||||||
|
|
||||||
|
if (x) {
|
||||||
|
console.log("I am a fish");
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
console.log("I am a chimp"); // Bad
|
||||||
|
}
|
||||||
|
```
|
||||||
- Declare one variable per var statement (consistent with Node). Unless they
|
- Declare one variable per var statement (consistent with Node). Unless they
|
||||||
are simple and closely related. If you put the next declaration on a new line,
|
are simple and closely related. If you put the next declaration on a new line,
|
||||||
treat yourself to another `var`:
|
treat yourself to another `var`:
|
||||||
|
|
||||||
```javascript
|
```javascript
|
||||||
var key = "foo",
|
const key = "foo",
|
||||||
comparator = function(x, y) {
|
comparator = function(x, y) {
|
||||||
return x - y;
|
return x - y;
|
||||||
}; // Bad
|
}; // Bad
|
||||||
|
|
||||||
var key = "foo";
|
const key = "foo";
|
||||||
var comparator = function(x, y) {
|
const comparator = function(x, y) {
|
||||||
return x - y;
|
return x - y;
|
||||||
}; // Good
|
}; // Good
|
||||||
|
|
||||||
var x = 0, y = 0; // Fine
|
let x = 0, y = 0; // Fine
|
||||||
|
|
||||||
var x = 0;
|
let x = 0;
|
||||||
var y = 0; // Also fine
|
let y = 0; // Also fine
|
||||||
```
|
```
|
||||||
- A single line `if` is fine, all others have braces. This prevents errors when adding to the code.:
|
- A single line `if` is fine, all others have braces. This prevents errors when adding to the code.:
|
||||||
|
|
||||||
|
|
1
header
1
header
|
@ -1,5 +1,6 @@
|
||||||
/*
|
/*
|
||||||
Copyright 2015, 2016 OpenMarket Ltd
|
Copyright 2015, 2016 OpenMarket Ltd
|
||||||
|
Copyright 2017 Vector Creations Ltd
|
||||||
|
|
||||||
Licensed under the Apache License, Version 2.0 (the "License");
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
you may not use this file except in compliance with the License.
|
you may not use this file except in compliance with the License.
|
||||||
|
|
|
@ -55,11 +55,18 @@ module.exports = function (config) {
|
||||||
// some images to reduce noise from the tests
|
// some images to reduce noise from the tests
|
||||||
{pattern: 'test/img/*', watched: false, included: false,
|
{pattern: 'test/img/*', watched: false, included: false,
|
||||||
served: true, nocache: false},
|
served: true, nocache: false},
|
||||||
|
// translation files
|
||||||
|
{pattern: 'src/i18n/strings/*', watcheed: false, included: false, served: true},
|
||||||
|
{pattern: 'test/i18n/*', watched: false, included: false, served: true},
|
||||||
],
|
],
|
||||||
|
|
||||||
// redirect img links to the karma server
|
|
||||||
proxies: {
|
proxies: {
|
||||||
|
// redirect img links to the karma server
|
||||||
"/img/": "/base/test/img/",
|
"/img/": "/base/test/img/",
|
||||||
|
// special languages.json file for the tests
|
||||||
|
"/i18n/languages.json": "/base/test/i18n/languages.json",
|
||||||
|
// and redirect i18n requests
|
||||||
|
"/i18n/": "/base/src/i18n/strings/",
|
||||||
},
|
},
|
||||||
|
|
||||||
// list of files to exclude
|
// list of files to exclude
|
||||||
|
@ -135,17 +142,24 @@ module.exports = function (config) {
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
noParse: [
|
noParse: [
|
||||||
|
// for cross platform compatibility use [\\\/] as the path separator
|
||||||
|
// this ensures that the regex trips on both Windows and *nix
|
||||||
|
|
||||||
// don't parse the languages within highlight.js. They
|
// don't parse the languages within highlight.js. They
|
||||||
// cause stack overflows
|
// cause stack overflows
|
||||||
// (https://github.com/webpack/webpack/issues/1721), and
|
// (https://github.com/webpack/webpack/issues/1721), and
|
||||||
// there is no need for webpack to parse them - they can
|
// there is no need for webpack to parse them - they can
|
||||||
// just be included as-is.
|
// just be included as-is.
|
||||||
/highlight\.js\/lib\/languages/,
|
/highlight\.js[\\\/]lib[\\\/]languages/,
|
||||||
|
|
||||||
|
// olm takes ages for webpack to process, and it's already heavily
|
||||||
|
// optimised, so there is little to gain by us uglifying it.
|
||||||
|
/olm[\\\/](javascript[\\\/])?olm\.js$/,
|
||||||
|
|
||||||
// also disable parsing for sinon, because it
|
// also disable parsing for sinon, because it
|
||||||
// tries to do voodoo with 'require' which upsets
|
// tries to do voodoo with 'require' which upsets
|
||||||
// webpack (https://github.com/webpack/webpack/issues/304)
|
// webpack (https://github.com/webpack/webpack/issues/304)
|
||||||
/sinon\/pkg\/sinon\.js$/,
|
/sinon[\\\/]pkg[\\\/]sinon\.js$/,
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
resolve: {
|
resolve: {
|
||||||
|
@ -159,11 +173,15 @@ module.exports = function (config) {
|
||||||
'sinon': 'sinon/pkg/sinon.js',
|
'sinon': 'sinon/pkg/sinon.js',
|
||||||
},
|
},
|
||||||
root: [
|
root: [
|
||||||
path.resolve('./src'),
|
|
||||||
path.resolve('./test'),
|
path.resolve('./test'),
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
devtool: 'inline-source-map',
|
devtool: 'inline-source-map',
|
||||||
|
externals: {
|
||||||
|
// Don't try to bundle electron: leave it as a commonjs dependency
|
||||||
|
// (the 'commonjs' here means it will output a 'require')
|
||||||
|
"electron": "commonjs electron",
|
||||||
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
webpackMiddleware: {
|
webpackMiddleware: {
|
||||||
|
|
17
package.json
17
package.json
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "matrix-react-sdk",
|
"name": "matrix-react-sdk",
|
||||||
"version": "0.8.8",
|
"version": "0.9.2",
|
||||||
"description": "SDK for matrix.org using React",
|
"description": "SDK for matrix.org using React",
|
||||||
"author": "matrix.org",
|
"author": "matrix.org",
|
||||||
"repository": {
|
"repository": {
|
||||||
|
@ -31,9 +31,11 @@
|
||||||
"reskindex": "scripts/reskindex.js"
|
"reskindex": "scripts/reskindex.js"
|
||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"reskindex": "scripts/reskindex.js -h header",
|
"reskindex": "node scripts/reskindex.js -h header",
|
||||||
"build": "node scripts/babelcheck.js && babel src -d lib --source-maps",
|
"reskindex:watch": "node scripts/reskindex.js -h header -w",
|
||||||
"start": "node scripts/babelcheck.js && babel src -w -d lib --source-maps",
|
"build": "npm run reskindex && babel src -d lib --source-maps",
|
||||||
|
"build:watch": "babel src -w -d lib --source-maps",
|
||||||
|
"start": "parallelshell \"npm run build:watch\" \"npm run reskindex:watch\"",
|
||||||
"lint": "eslint src/",
|
"lint": "eslint src/",
|
||||||
"lintall": "eslint src/ test/",
|
"lintall": "eslint src/ test/",
|
||||||
"clean": "rimraf lib",
|
"clean": "rimraf lib",
|
||||||
|
@ -48,12 +50,13 @@
|
||||||
"browser-request": "^0.3.3",
|
"browser-request": "^0.3.3",
|
||||||
"classnames": "^2.1.2",
|
"classnames": "^2.1.2",
|
||||||
"commonmark": "^0.27.0",
|
"commonmark": "^0.27.0",
|
||||||
|
"counterpart": "^0.18.0",
|
||||||
"draft-js": "^0.8.1",
|
"draft-js": "^0.8.1",
|
||||||
"draft-js-export-html": "^0.5.0",
|
"draft-js-export-html": "^0.5.0",
|
||||||
"draft-js-export-markdown": "^0.2.0",
|
"draft-js-export-markdown": "^0.2.0",
|
||||||
"emojione": "2.2.3",
|
"emojione": "2.2.3",
|
||||||
"file-saver": "^1.3.3",
|
"file-saver": "^1.3.3",
|
||||||
"filesize": "^3.1.2",
|
"filesize": "3.5.6",
|
||||||
"flux": "^2.0.3",
|
"flux": "^2.0.3",
|
||||||
"fuse.js": "^2.2.0",
|
"fuse.js": "^2.2.0",
|
||||||
"glob": "^5.0.14",
|
"glob": "^5.0.14",
|
||||||
|
@ -61,7 +64,7 @@
|
||||||
"isomorphic-fetch": "^2.2.1",
|
"isomorphic-fetch": "^2.2.1",
|
||||||
"linkifyjs": "^2.1.3",
|
"linkifyjs": "^2.1.3",
|
||||||
"lodash": "^4.13.1",
|
"lodash": "^4.13.1",
|
||||||
"matrix-js-sdk": "0.7.7",
|
"matrix-js-sdk": "0.7.10",
|
||||||
"optimist": "^0.6.1",
|
"optimist": "^0.6.1",
|
||||||
"q": "^1.4.1",
|
"q": "^1.4.1",
|
||||||
"react": "^15.4.0",
|
"react": "^15.4.0",
|
||||||
|
@ -88,6 +91,7 @@
|
||||||
"babel-preset-es2016": "^6.11.3",
|
"babel-preset-es2016": "^6.11.3",
|
||||||
"babel-preset-es2017": "^6.14.0",
|
"babel-preset-es2017": "^6.14.0",
|
||||||
"babel-preset-react": "^6.11.1",
|
"babel-preset-react": "^6.11.1",
|
||||||
|
"chokidar": "^1.6.1",
|
||||||
"eslint": "^3.13.1",
|
"eslint": "^3.13.1",
|
||||||
"eslint-config-google": "^0.7.1",
|
"eslint-config-google": "^0.7.1",
|
||||||
"eslint-plugin-babel": "^4.0.1",
|
"eslint-plugin-babel": "^4.0.1",
|
||||||
|
@ -104,6 +108,7 @@
|
||||||
"karma-sourcemap-loader": "^0.3.7",
|
"karma-sourcemap-loader": "^0.3.7",
|
||||||
"karma-webpack": "^1.7.0",
|
"karma-webpack": "^1.7.0",
|
||||||
"mocha": "^2.4.5",
|
"mocha": "^2.4.5",
|
||||||
|
"parallelshell": "^1.2.0",
|
||||||
"phantomjs-prebuilt": "^2.1.7",
|
"phantomjs-prebuilt": "^2.1.7",
|
||||||
"react-addons-test-utils": "^15.4.0",
|
"react-addons-test-utils": "^15.4.0",
|
||||||
"require-json": "0.0.1",
|
"require-json": "0.0.1",
|
||||||
|
|
|
@ -1,22 +0,0 @@
|
||||||
#!/usr/bin/env node
|
|
||||||
|
|
||||||
var exec = require('child_process').exec;
|
|
||||||
|
|
||||||
// Makes sure the babel executable in the path is babel 6 (or greater), not
|
|
||||||
// babel 5, which it is if you upgrade from an older version of react-sdk and
|
|
||||||
// run 'npm install' since the package has changed to babel-cli, so 'babel'
|
|
||||||
// remains installed and the executable in node_modules/.bin remains as babel
|
|
||||||
// 5.
|
|
||||||
|
|
||||||
exec("babel -V", function (error, stdout, stderr) {
|
|
||||||
if ((error && error.code) || parseInt(stdout.substr(0,1), 10) < 6) {
|
|
||||||
console.log("\033[31m\033[1m"+
|
|
||||||
'*****************************************\n'+
|
|
||||||
'* matrix-react-sdk has moved to babel 6 *\n'+
|
|
||||||
'* Please "rm -rf node_modules && npm i" *\n'+
|
|
||||||
'* then restore links as appropriate *\n'+
|
|
||||||
'*****************************************\n'+
|
|
||||||
"\033[91m");
|
|
||||||
process.exit(1);
|
|
||||||
}
|
|
||||||
});
|
|
192
scripts/check-i18n.pl
Executable file
192
scripts/check-i18n.pl
Executable file
|
@ -0,0 +1,192 @@
|
||||||
|
#!/usr/bin/perl
|
||||||
|
|
||||||
|
use strict;
|
||||||
|
use warnings;
|
||||||
|
use Cwd 'abs_path';
|
||||||
|
|
||||||
|
# script which checks how out of sync the i18ns are drifting
|
||||||
|
|
||||||
|
# example i18n format:
|
||||||
|
# "%(oneUser)sleft": "%(oneUser)sleft",
|
||||||
|
|
||||||
|
$|=1;
|
||||||
|
|
||||||
|
$0 =~ /^(.*\/)/;
|
||||||
|
my $i18ndir = abs_path($1."/../src/i18n/strings");
|
||||||
|
my $srcdir = abs_path($1."/../src");
|
||||||
|
|
||||||
|
my $en = read_i18n($i18ndir."/en_EN.json");
|
||||||
|
|
||||||
|
my $src_strings = read_src_strings($srcdir);
|
||||||
|
my $src = {};
|
||||||
|
|
||||||
|
print "Checking strings in src\n";
|
||||||
|
foreach my $tuple (@$src_strings) {
|
||||||
|
my ($s, $file) = (@$tuple);
|
||||||
|
$src->{$s} = $file;
|
||||||
|
if (!$en->{$s}) {
|
||||||
|
if ($en->{$s . '.'}) {
|
||||||
|
printf ("%50s %24s\t%s\n", $file, "en_EN has fullstop!", $s);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
$s =~ /^(.*)\.?$/;
|
||||||
|
if ($en->{$1}) {
|
||||||
|
printf ("%50s %24s\t%s\n", $file, "en_EN lacks fullstop!", $s);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
printf ("%50s %24s\t%s\n", $file, "Translation missing!", $s);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
print "\nChecking en_EN\n";
|
||||||
|
my $count = 0;
|
||||||
|
my $remaining_src = {};
|
||||||
|
foreach (keys %$src) { $remaining_src->{$_}++ };
|
||||||
|
|
||||||
|
foreach my $k (sort keys %$en) {
|
||||||
|
# crappy heuristic to ignore country codes for now...
|
||||||
|
next if ($k =~ /^(..|..-..)$/);
|
||||||
|
|
||||||
|
if ($en->{$k} ne $k) {
|
||||||
|
printf ("%50s %24s\t%s\n", "en_EN", "en_EN is not symmetrical", $k);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!$src->{$k}) {
|
||||||
|
if ($src->{$k. '.'}) {
|
||||||
|
printf ("%50s %24s\t%s\n", $src->{$k. '.'}, "src has fullstop!", $k);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
$k =~ /^(.*)\.?$/;
|
||||||
|
if ($src->{$1}) {
|
||||||
|
printf ("%50s %24s\t%s\n", $src->{$1}, "src lacks fullstop!", $k);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
printf ("%50s %24s\t%s\n", '???', "Not present in src?", $k);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
$count++;
|
||||||
|
delete $remaining_src->{$k};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
printf ("$count/" . (scalar keys %$src) . " strings found in src are present in en_EN\n");
|
||||||
|
foreach (keys %$remaining_src) {
|
||||||
|
print "missing: $_\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
opendir(DIR, $i18ndir) || die $!;
|
||||||
|
my @files = readdir(DIR);
|
||||||
|
closedir(DIR);
|
||||||
|
foreach my $lang (grep { -f "$i18ndir/$_" && !/(basefile|en_EN)\.json/ } @files) {
|
||||||
|
print "\nChecking $lang\n";
|
||||||
|
|
||||||
|
my $map = read_i18n($i18ndir."/".$lang);
|
||||||
|
my $count = 0;
|
||||||
|
|
||||||
|
my $remaining_en = {};
|
||||||
|
foreach (keys %$en) { $remaining_en->{$_}++ };
|
||||||
|
|
||||||
|
foreach my $k (sort keys %$map) {
|
||||||
|
{
|
||||||
|
no warnings 'uninitialized';
|
||||||
|
my $vars = {};
|
||||||
|
while ($k =~ /%\((.*?)\)s/g) {
|
||||||
|
$vars->{$1}++;
|
||||||
|
}
|
||||||
|
while ($map->{$k} =~ /%\((.*?)\)s/g) {
|
||||||
|
$vars->{$1}--;
|
||||||
|
}
|
||||||
|
foreach my $var (keys %$vars) {
|
||||||
|
if ($vars->{$var} != 0) {
|
||||||
|
printf ("%10s %24s\t%s\n", $lang, "Broken var ($var)s", $k);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($en->{$k}) {
|
||||||
|
if ($map->{$k} eq $k) {
|
||||||
|
printf ("%10s %24s\t%s\n", $lang, "Untranslated string?", $k);
|
||||||
|
}
|
||||||
|
$count++;
|
||||||
|
delete $remaining_en->{$k};
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
if ($en->{$k . "."}) {
|
||||||
|
printf ("%10s %24s\t%s\n", $lang, "en_EN has fullstop!", $k);
|
||||||
|
next;
|
||||||
|
}
|
||||||
|
|
||||||
|
$k =~ /^(.*)\.?$/;
|
||||||
|
if ($en->{$1}) {
|
||||||
|
printf ("%10s %24s\t%s\n", $lang, "en_EN lacks fullstop!", $k);
|
||||||
|
next;
|
||||||
|
}
|
||||||
|
|
||||||
|
printf ("%10s %24s\t%s\n", $lang, "Not present in en_EN", $k);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (scalar keys %$remaining_en < 100) {
|
||||||
|
foreach (keys %$remaining_en) {
|
||||||
|
printf ("%10s %24s\t%s\n", $lang, "Not yet translated", $_);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
printf ("$count/" . (scalar keys %$en) . " strings translated\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
sub read_i18n {
|
||||||
|
my $path = shift;
|
||||||
|
my $map = {};
|
||||||
|
$path =~ /.*\/(.*)$/;
|
||||||
|
my $lang = $1;
|
||||||
|
|
||||||
|
open(FILE, "<", $path) || die $!;
|
||||||
|
while(<FILE>) {
|
||||||
|
if ($_ =~ m/^(\s+)"(.*?)"(: *)"(.*?)"(,?)$/) {
|
||||||
|
my ($indent, $src, $colon, $dst, $comma) = ($1, $2, $3, $4, $5);
|
||||||
|
$src =~ s/\\"/"/g;
|
||||||
|
$dst =~ s/\\"/"/g;
|
||||||
|
|
||||||
|
if ($map->{$src}) {
|
||||||
|
printf ("%10s %24s\t%s\n", $lang, "Duplicate translation!", $src);
|
||||||
|
}
|
||||||
|
$map->{$src} = $dst;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
close(FILE);
|
||||||
|
|
||||||
|
return $map;
|
||||||
|
}
|
||||||
|
|
||||||
|
sub read_src_strings {
|
||||||
|
my $path = shift;
|
||||||
|
|
||||||
|
use File::Find;
|
||||||
|
use File::Slurp;
|
||||||
|
|
||||||
|
my $strings = [];
|
||||||
|
|
||||||
|
my @files;
|
||||||
|
find( sub { push @files, $File::Find::name if (-f $_ && /\.jsx?$/) }, $path );
|
||||||
|
foreach my $file (@files) {
|
||||||
|
my $src = read_file($file);
|
||||||
|
$src =~ s/'\s*\+\s*'//g;
|
||||||
|
$src =~ s/"\s*\+\s*"//g;
|
||||||
|
|
||||||
|
$file =~ s/^.*\/src/src/;
|
||||||
|
while ($src =~ /_t(?:Jsx)?\(\s*'(.*?[^\\])'/sg) {
|
||||||
|
my $s = $1;
|
||||||
|
$s =~ s/\\'/'/g;
|
||||||
|
push @$strings, [$s, $file];
|
||||||
|
}
|
||||||
|
while ($src =~ /_t(?:Jsx)?\(\s*"(.*?[^\\])"/sg) {
|
||||||
|
push @$strings, [$1, $file];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $strings;
|
||||||
|
}
|
104
scripts/fix-i18n.pl
Executable file
104
scripts/fix-i18n.pl
Executable file
|
@ -0,0 +1,104 @@
|
||||||
|
#!/usr/bin/perl -ni
|
||||||
|
|
||||||
|
use strict;
|
||||||
|
use warnings;
|
||||||
|
|
||||||
|
# script which synchronises i18n strings to include punctuation.
|
||||||
|
# i've cherry-picked ones which seem to have diverged between the different translations
|
||||||
|
# from TextForEvent, causing missing events all over the place
|
||||||
|
|
||||||
|
BEGIN {
|
||||||
|
$::fixups = [split(/\n/, <<EOT
|
||||||
|
%(targetName)s accepted the invitation for %(displayName)s.
|
||||||
|
%(targetName)s accepted an invitation.
|
||||||
|
%(senderName)s requested a VoIP conference.
|
||||||
|
%(senderName)s invited %(targetName)s.
|
||||||
|
%(senderName)s banned %(targetName)s.
|
||||||
|
%(senderName)s changed their display name from %(oldDisplayName)s to %(displayName)s.
|
||||||
|
%(senderName)s set their display name to %(displayName)s.
|
||||||
|
%(senderName)s removed their display name (%(oldDisplayName)s).
|
||||||
|
%(senderName)s removed their profile picture.
|
||||||
|
%(senderName)s changed their profile picture.
|
||||||
|
%(senderName)s set a profile picture.
|
||||||
|
VoIP conference started.
|
||||||
|
%(targetName)s joined the room.
|
||||||
|
VoIP conference finished.
|
||||||
|
%(targetName)s rejected the invitation.
|
||||||
|
%(targetName)s left the room.
|
||||||
|
%(senderName)s unbanned %(targetName)s.
|
||||||
|
%(senderName)s kicked %(targetName)s.
|
||||||
|
%(senderName)s withdrew %(targetName)s's inivitation.
|
||||||
|
%(targetName)s left the room.
|
||||||
|
%(senderDisplayName)s changed the topic to "%(topic)s".
|
||||||
|
%(senderDisplayName)s changed the room name to %(roomName)s.
|
||||||
|
%(senderDisplayName)s sent an image.
|
||||||
|
%(senderName)s answered the call.
|
||||||
|
%(senderName)s ended the call.
|
||||||
|
%(senderName)s placed a %(callType)s call.
|
||||||
|
%(senderName)s sent an invitation to %(targetDisplayName)s to join the room.
|
||||||
|
%(senderName)s turned on end-to-end encryption (algorithm %(algorithm)s).
|
||||||
|
%(senderName)s changed the power level of %(powerLevelDiffText)s.
|
||||||
|
For security, this session has been signed out. Please sign in again.
|
||||||
|
You need to log back in to generate end-to-end encryption keys for this device and submit the public key to your homeserver. This is a once off; sorry for the inconvenience.
|
||||||
|
A new password must be entered.
|
||||||
|
Guests can't set avatars. Please register.
|
||||||
|
Failed to set avatar.
|
||||||
|
Unable to verify email address.
|
||||||
|
Guests can't use labs features. Please register.
|
||||||
|
A new password must be entered.
|
||||||
|
Resetting password will currently reset any 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.
|
||||||
|
Guests cannot join this room even if explicitly invited.
|
||||||
|
Guest users can't invite users. Please register to invite.
|
||||||
|
This room is inaccessible to guests. You may be able to join if you register.
|
||||||
|
delete the alias.
|
||||||
|
remove %(name)s from the directory.
|
||||||
|
Conference call failed.
|
||||||
|
Conference calling is in development and may not be reliable.
|
||||||
|
Guest users can't create new rooms. Please register to create room and start a chat.
|
||||||
|
Server may be unavailable, overloaded, or you hit a bug.
|
||||||
|
Server unavailable, overloaded, or something else went wrong.
|
||||||
|
You are already in a call.
|
||||||
|
You cannot place VoIP calls in this browser.
|
||||||
|
You cannot place a call with yourself.
|
||||||
|
Your email address does not appear to be associated with a Matrix ID on this Homeserver.
|
||||||
|
EOT
|
||||||
|
)];
|
||||||
|
}
|
||||||
|
|
||||||
|
# example i18n format:
|
||||||
|
# "%(oneUser)sleft": "%(oneUser)sleft",
|
||||||
|
|
||||||
|
# script called with the line of the file to be checked
|
||||||
|
my $sub = 0;
|
||||||
|
if ($_ =~ m/^(\s+)"(.*?)"(: *)"(.*?)"(,?)$/) {
|
||||||
|
my ($indent, $src, $colon, $dst, $comma) = ($1, $2, $3, $4, $5);
|
||||||
|
$src =~ s/\\"/"/g;
|
||||||
|
$dst =~ s/\\"/"/g;
|
||||||
|
|
||||||
|
foreach my $fixup (@{$::fixups}) {
|
||||||
|
my $dotless_fixup = substr($fixup, 0, -1);
|
||||||
|
|
||||||
|
if ($src eq $dotless_fixup) {
|
||||||
|
print STDERR "fixing up src: $src\n";
|
||||||
|
$src .= '.';
|
||||||
|
$sub = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($src eq $fixup && $dst !~ /\.$/) {
|
||||||
|
print STDERR "fixing up dst: $dst\n";
|
||||||
|
$dst .= '.';
|
||||||
|
$sub = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($sub) {
|
||||||
|
$src =~ s/"/\\"/g;
|
||||||
|
$dst =~ s/"/\\"/g;
|
||||||
|
print qq($indent"$src"$colon"$dst"$comma\n);
|
||||||
|
last;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!$sub) {
|
||||||
|
print $_;
|
||||||
|
}
|
|
@ -1,53 +1,99 @@
|
||||||
#!/usr/bin/env node
|
#!/usr/bin/env node
|
||||||
|
|
||||||
var fs = require('fs');
|
var fs = require('fs');
|
||||||
var path = require('path');
|
var path = require('path');
|
||||||
var glob = require('glob');
|
var glob = require('glob');
|
||||||
|
|
||||||
var args = require('optimist').argv;
|
var args = require('optimist').argv;
|
||||||
|
var chokidar = require('chokidar');
|
||||||
var header = args.h || args.header;
|
|
||||||
|
|
||||||
var componentsDir = path.join('src', 'components');
|
|
||||||
|
|
||||||
var componentIndex = path.join('src', 'component-index.js');
|
var componentIndex = path.join('src', 'component-index.js');
|
||||||
|
var componentIndexTmp = componentIndex+".tmp";
|
||||||
|
var componentsDir = path.join('src', 'components');
|
||||||
|
var componentGlob = '**/*.js';
|
||||||
|
var prevFiles = [];
|
||||||
|
|
||||||
var packageJson = JSON.parse(fs.readFileSync('./package.json'));
|
function reskindex() {
|
||||||
|
var files = glob.sync(componentGlob, {cwd: componentsDir}).sort();
|
||||||
|
if (!filesHaveChanged(files, prevFiles)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
prevFiles = files;
|
||||||
|
|
||||||
var strm = fs.createWriteStream(componentIndex);
|
var header = args.h || args.header;
|
||||||
|
var packageJson = JSON.parse(fs.readFileSync('./package.json'));
|
||||||
|
|
||||||
if (header) {
|
var strm = fs.createWriteStream(componentIndexTmp);
|
||||||
|
|
||||||
|
if (header) {
|
||||||
strm.write(fs.readFileSync(header));
|
strm.write(fs.readFileSync(header));
|
||||||
strm.write('\n');
|
strm.write('\n');
|
||||||
|
}
|
||||||
|
|
||||||
|
strm.write("/*\n");
|
||||||
|
strm.write(" * THIS FILE IS AUTO-GENERATED\n");
|
||||||
|
strm.write(" * You can edit it you like, but your changes will be overwritten,\n");
|
||||||
|
strm.write(" * so you'd just be trying to swim upstream like a salmon.\n");
|
||||||
|
strm.write(" * You are not a salmon.\n");
|
||||||
|
strm.write(" */\n\n");
|
||||||
|
|
||||||
|
if (packageJson['matrix-react-parent']) {
|
||||||
|
const parentIndex = packageJson['matrix-react-parent'] +
|
||||||
|
'/lib/component-index';
|
||||||
|
strm.write(
|
||||||
|
`let components = require('${parentIndex}').components;
|
||||||
|
if (!components) {
|
||||||
|
throw new Error("'${parentIndex}' didn't export components");
|
||||||
}
|
}
|
||||||
|
`);
|
||||||
|
} else {
|
||||||
|
strm.write("let components = {};\n");
|
||||||
|
}
|
||||||
|
|
||||||
strm.write("/*\n");
|
for (var i = 0; i < files.length; ++i) {
|
||||||
strm.write(" * THIS FILE IS AUTO-GENERATED\n");
|
|
||||||
strm.write(" * You can edit it you like, but your changes will be overwritten,\n");
|
|
||||||
strm.write(" * so you'd just be trying to swim upstream like a salmon.\n");
|
|
||||||
strm.write(" * You are not a salmon.\n");
|
|
||||||
strm.write(" *\n");
|
|
||||||
strm.write(" * To update it, run:\n");
|
|
||||||
strm.write(" * ./reskindex.js -h header\n");
|
|
||||||
strm.write(" */\n\n");
|
|
||||||
|
|
||||||
if (packageJson['matrix-react-parent']) {
|
|
||||||
strm.write("module.exports.components = require('"+packageJson['matrix-react-parent']+"/lib/component-index').components;\n\n");
|
|
||||||
} else {
|
|
||||||
strm.write("module.exports.components = {};\n");
|
|
||||||
}
|
|
||||||
|
|
||||||
var files = glob.sync('**/*.js', {cwd: componentsDir}).sort();
|
|
||||||
for (var i = 0; i < files.length; ++i) {
|
|
||||||
var file = files[i].replace('.js', '');
|
var file = files[i].replace('.js', '');
|
||||||
|
|
||||||
var moduleName = (file.replace(/\//g, '.'));
|
var moduleName = (file.replace(/\//g, '.'));
|
||||||
var importName = moduleName.replace(/\./g, "$");
|
var importName = moduleName.replace(/\./g, "$");
|
||||||
|
|
||||||
strm.write("import " + importName + " from './components/" + file + "';\n");
|
strm.write("import " + importName + " from './components/" + file + "';\n");
|
||||||
strm.write(importName + " && (module.exports.components['"+moduleName+"'] = " + importName + ");");
|
strm.write(importName + " && (components['"+moduleName+"'] = " + importName + ");");
|
||||||
strm.write('\n');
|
strm.write('\n');
|
||||||
strm.uncork();
|
strm.uncork();
|
||||||
|
}
|
||||||
|
|
||||||
|
strm.write("export {components};\n");
|
||||||
|
strm.end();
|
||||||
|
fs.rename(componentIndexTmp, componentIndex, function(err) {
|
||||||
|
if(err) {
|
||||||
|
console.error("Error moving new index into place: " + err);
|
||||||
|
} else {
|
||||||
|
console.log('Reskindex: completed');
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
strm.end();
|
// Expects both arrays of file names to be sorted
|
||||||
|
function filesHaveChanged(files, prevFiles) {
|
||||||
|
if (files.length !== prevFiles.length) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
// Check for name changes
|
||||||
|
for (var i = 0; i < files.length; i++) {
|
||||||
|
if (prevFiles[i] !== files[i]) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// -w indicates watch mode where any FS events will trigger reskindex
|
||||||
|
if (!args.w) {
|
||||||
|
reskindex();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var watchDebouncer = null;
|
||||||
|
chokidar.watch(path.join(componentsDir, componentGlob)).on('all', (event, path) => {
|
||||||
|
if (path === componentIndex) return;
|
||||||
|
if (watchDebouncer) clearTimeout(watchDebouncer);
|
||||||
|
watchDebouncer = setTimeout(reskindex, 1000);
|
||||||
|
});
|
||||||
|
|
|
@ -16,6 +16,7 @@ limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
var MatrixClientPeg = require("./MatrixClientPeg");
|
var MatrixClientPeg = require("./MatrixClientPeg");
|
||||||
|
import { _t } from './languageHandler';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Allows a user to add a third party identifier to their Home Server and,
|
* Allows a user to add a third party identifier to their Home Server and,
|
||||||
|
@ -44,7 +45,7 @@ class AddThreepid {
|
||||||
return res;
|
return res;
|
||||||
}, function(err) {
|
}, function(err) {
|
||||||
if (err.errcode == 'M_THREEPID_IN_USE') {
|
if (err.errcode == 'M_THREEPID_IN_USE') {
|
||||||
err.message = "This email address is already in use";
|
err.message = _t('This email address is already in use');
|
||||||
} else if (err.httpStatus) {
|
} else if (err.httpStatus) {
|
||||||
err.message = err.message + ` (Status ${err.httpStatus})`;
|
err.message = err.message + ` (Status ${err.httpStatus})`;
|
||||||
}
|
}
|
||||||
|
@ -69,7 +70,7 @@ class AddThreepid {
|
||||||
return res;
|
return res;
|
||||||
}, function(err) {
|
}, function(err) {
|
||||||
if (err.errcode == 'M_THREEPID_IN_USE') {
|
if (err.errcode == 'M_THREEPID_IN_USE') {
|
||||||
err.message = "This phone number is already in use";
|
err.message = _t('This phone number is already in use');
|
||||||
} else if (err.httpStatus) {
|
} else if (err.httpStatus) {
|
||||||
err.message = err.message + ` (Status ${err.httpStatus})`;
|
err.message = err.message + ` (Status ${err.httpStatus})`;
|
||||||
}
|
}
|
||||||
|
@ -91,7 +92,7 @@ class AddThreepid {
|
||||||
id_server: identityServerDomain
|
id_server: identityServerDomain
|
||||||
}, this.bind).catch(function(err) {
|
}, this.bind).catch(function(err) {
|
||||||
if (err.httpStatus === 401) {
|
if (err.httpStatus === 401) {
|
||||||
err.message = "Failed to verify email address: make sure you clicked the link in the email";
|
err.message = _t('Failed to verify email address: make sure you clicked the link in the email');
|
||||||
}
|
}
|
||||||
else if (err.httpStatus) {
|
else if (err.httpStatus) {
|
||||||
err.message += ` (Status ${err.httpStatus})`;
|
err.message += ` (Status ${err.httpStatus})`;
|
||||||
|
|
145
src/Analytics.js
Normal file
145
src/Analytics.js
Normal file
|
@ -0,0 +1,145 @@
|
||||||
|
/*
|
||||||
|
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 { getCurrentLanguage } from './languageHandler';
|
||||||
|
import MatrixClientPeg from './MatrixClientPeg';
|
||||||
|
import PlatformPeg from './PlatformPeg';
|
||||||
|
import SdkConfig from './SdkConfig';
|
||||||
|
|
||||||
|
function redact(str) {
|
||||||
|
return str.replace(/#\/(room|user)\/(.+)/, "#/$1/<redacted>");
|
||||||
|
}
|
||||||
|
|
||||||
|
const customVariables = {
|
||||||
|
'App Platform': 1,
|
||||||
|
'App Version': 2,
|
||||||
|
'User Type': 3,
|
||||||
|
'Chosen Language': 4,
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
class Analytics {
|
||||||
|
constructor() {
|
||||||
|
this._paq = null;
|
||||||
|
this.disabled = true;
|
||||||
|
this.firstPage = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Enable Analytics if initialized but disabled
|
||||||
|
* otherwise try and initalize, no-op if piwik config missing
|
||||||
|
*/
|
||||||
|
enable() {
|
||||||
|
if (this._paq || this._init()) {
|
||||||
|
this.disabled = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Disable Analytics calls, will not fully unload Piwik until a refresh,
|
||||||
|
* but this is second best, Piwik should not pull anything implicitly.
|
||||||
|
*/
|
||||||
|
disable() {
|
||||||
|
this.disabled = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
_init() {
|
||||||
|
const config = SdkConfig.get();
|
||||||
|
if (!config || !config.piwik || !config.piwik.url || !config.piwik.siteId) return;
|
||||||
|
|
||||||
|
const url = config.piwik.url;
|
||||||
|
const siteId = config.piwik.siteId;
|
||||||
|
const self = this;
|
||||||
|
|
||||||
|
window._paq = this._paq = window._paq || [];
|
||||||
|
|
||||||
|
this._paq.push(['setTrackerUrl', url+'piwik.php']);
|
||||||
|
this._paq.push(['setSiteId', siteId]);
|
||||||
|
|
||||||
|
this._paq.push(['trackAllContentImpressions']);
|
||||||
|
this._paq.push(['discardHashTag', false]);
|
||||||
|
this._paq.push(['enableHeartBeatTimer']);
|
||||||
|
this._paq.push(['enableLinkTracking', true]);
|
||||||
|
|
||||||
|
const platform = PlatformPeg.get();
|
||||||
|
this._setVisitVariable('App Platform', platform.getHumanReadableName());
|
||||||
|
platform.getAppVersion().then((version) => {
|
||||||
|
this._setVisitVariable('App Version', version);
|
||||||
|
}).catch(() => {
|
||||||
|
this._setVisitVariable('App Version', 'unknown');
|
||||||
|
});
|
||||||
|
|
||||||
|
this._setVisitVariable('Chosen Language', getCurrentLanguage());
|
||||||
|
|
||||||
|
(function() {
|
||||||
|
const g = document.createElement('script');
|
||||||
|
const s = document.getElementsByTagName('script')[0];
|
||||||
|
g.type='text/javascript'; g.async=true; g.defer=true; g.src=url+'piwik.js';
|
||||||
|
|
||||||
|
g.onload = function() {
|
||||||
|
console.log('Initialised anonymous analytics');
|
||||||
|
self._paq = window._paq;
|
||||||
|
};
|
||||||
|
|
||||||
|
s.parentNode.insertBefore(g, s);
|
||||||
|
})();
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
trackPageChange() {
|
||||||
|
if (this.disabled) return;
|
||||||
|
if (this.firstPage) {
|
||||||
|
// De-duplicate first page
|
||||||
|
// router seems to hit the fn twice
|
||||||
|
this.firstPage = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this._paq.push(['setCustomUrl', redact(window.location.href)]);
|
||||||
|
this._paq.push(['trackPageView']);
|
||||||
|
}
|
||||||
|
|
||||||
|
trackEvent(category, action, name) {
|
||||||
|
if (this.disabled) return;
|
||||||
|
this._paq.push(['trackEvent', category, action, name]);
|
||||||
|
}
|
||||||
|
|
||||||
|
logout() {
|
||||||
|
if (this.disabled) return;
|
||||||
|
this._paq.push(['deleteCookies']);
|
||||||
|
}
|
||||||
|
|
||||||
|
login() { // not used currently
|
||||||
|
const cli = MatrixClientPeg.get();
|
||||||
|
if (this.disabled || !cli) return;
|
||||||
|
|
||||||
|
this._paq.push(['setUserId', `@${cli.getUserIdLocalpart()}:${cli.getDomain()}`]);
|
||||||
|
}
|
||||||
|
|
||||||
|
_setVisitVariable(key, value) {
|
||||||
|
this._paq.push(['setCustomVariable', customVariables[key], key, value, 'visit']);
|
||||||
|
}
|
||||||
|
|
||||||
|
setGuest(guest) {
|
||||||
|
if (this.disabled) return;
|
||||||
|
this._setVisitVariable('User Type', guest ? 'Guest' : 'Logged In');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!global.mxAnalytics) {
|
||||||
|
global.mxAnalytics = new Analytics();
|
||||||
|
}
|
||||||
|
module.exports = global.mxAnalytics;
|
|
@ -22,8 +22,8 @@ module.exports = {
|
||||||
avatarUrlForMember: function(member, width, height, resizeMethod) {
|
avatarUrlForMember: function(member, width, height, resizeMethod) {
|
||||||
var url = member.getAvatarUrl(
|
var url = member.getAvatarUrl(
|
||||||
MatrixClientPeg.get().getHomeserverUrl(),
|
MatrixClientPeg.get().getHomeserverUrl(),
|
||||||
width,
|
Math.floor(width * window.devicePixelRatio),
|
||||||
height,
|
Math.floor(height * window.devicePixelRatio),
|
||||||
resizeMethod,
|
resizeMethod,
|
||||||
false,
|
false,
|
||||||
false
|
false
|
||||||
|
@ -40,7 +40,9 @@ module.exports = {
|
||||||
avatarUrlForUser: function(user, width, height, resizeMethod) {
|
avatarUrlForUser: function(user, width, height, resizeMethod) {
|
||||||
var url = ContentRepo.getHttpUriForMxc(
|
var url = ContentRepo.getHttpUriForMxc(
|
||||||
MatrixClientPeg.get().getHomeserverUrl(), user.avatarUrl,
|
MatrixClientPeg.get().getHomeserverUrl(), user.avatarUrl,
|
||||||
width, height, resizeMethod
|
Math.floor(width * window.devicePixelRatio),
|
||||||
|
Math.floor(height * window.devicePixelRatio),
|
||||||
|
resizeMethod
|
||||||
);
|
);
|
||||||
if (!url || url.length === 0) {
|
if (!url || url.length === 0) {
|
||||||
return null;
|
return null;
|
||||||
|
@ -57,4 +59,3 @@ module.exports = {
|
||||||
return 'img/' + images[total % images.length] + '.png';
|
return 'img/' + images[total % images.length] + '.png';
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -29,6 +29,11 @@ export default class BasePlatform {
|
||||||
this.errorDidOccur = false;
|
this.errorDidOccur = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Used primarily for Analytics
|
||||||
|
getHumanReadableName(): string {
|
||||||
|
return 'Base Platform';
|
||||||
|
}
|
||||||
|
|
||||||
setNotificationCount(count: number) {
|
setNotificationCount(count: number) {
|
||||||
this.notificationCount = count;
|
this.notificationCount = count;
|
||||||
}
|
}
|
||||||
|
@ -66,11 +71,14 @@ export default class BasePlatform {
|
||||||
displayNotification(title: string, msg: string, avatarUrl: string, room: Object) {
|
displayNotification(title: string, msg: string, avatarUrl: string, room: Object) {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
loudNotification(ev: Event, room: Object) {
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns a promise that resolves to a string representing
|
* Returns a promise that resolves to a string representing
|
||||||
* the current version of the application.
|
* the current version of the application.
|
||||||
*/
|
*/
|
||||||
getAppVersion() {
|
getAppVersion(): Promise<string> {
|
||||||
throw new Error("getAppVersion not implemented!");
|
throw new Error("getAppVersion not implemented!");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -79,10 +87,12 @@ export default class BasePlatform {
|
||||||
* with getUserMedia, return a string explaining why not.
|
* with getUserMedia, return a string explaining why not.
|
||||||
* Otherwise, return null.
|
* Otherwise, return null.
|
||||||
*/
|
*/
|
||||||
screenCaptureErrorString() {
|
screenCaptureErrorString(): string {
|
||||||
return "Not implemented";
|
return "Not implemented";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
isElectron(): boolean { return false; }
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Restarts the application, without neccessarily reloading
|
* Restarts the application, without neccessarily reloading
|
||||||
* any application code
|
* any application code
|
||||||
|
|
|
@ -55,6 +55,7 @@ var MatrixClientPeg = require('./MatrixClientPeg');
|
||||||
var PlatformPeg = require("./PlatformPeg");
|
var PlatformPeg = require("./PlatformPeg");
|
||||||
var Modal = require('./Modal');
|
var Modal = require('./Modal');
|
||||||
var sdk = require('./index');
|
var sdk = require('./index');
|
||||||
|
import { _t } from './languageHandler';
|
||||||
var Matrix = require("matrix-js-sdk");
|
var Matrix = require("matrix-js-sdk");
|
||||||
var dis = require("./dispatcher");
|
var dis = require("./dispatcher");
|
||||||
|
|
||||||
|
@ -142,8 +143,8 @@ function _setCallListeners(call) {
|
||||||
play("busyAudio");
|
play("busyAudio");
|
||||||
var ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
|
var ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
|
||||||
Modal.createDialog(ErrorDialog, {
|
Modal.createDialog(ErrorDialog, {
|
||||||
title: "Call Timeout",
|
title: _t('Call Timeout'),
|
||||||
description: "The remote side failed to pick up."
|
description: _t('The remote side failed to pick up') + '.',
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
else if (oldState === "invite_sent") {
|
else if (oldState === "invite_sent") {
|
||||||
|
@ -179,7 +180,8 @@ function _setCallState(call, roomId, status) {
|
||||||
}
|
}
|
||||||
dis.dispatch({
|
dis.dispatch({
|
||||||
action: 'call_state',
|
action: 'call_state',
|
||||||
room_id: roomId
|
room_id: roomId,
|
||||||
|
state: status,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -203,8 +205,8 @@ function _onAction(payload) {
|
||||||
console.log("Can't capture screen: " + screenCapErrorString);
|
console.log("Can't capture screen: " + screenCapErrorString);
|
||||||
const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
|
const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
|
||||||
Modal.createDialog(ErrorDialog, {
|
Modal.createDialog(ErrorDialog, {
|
||||||
title: "Unable to capture screen",
|
title: _t('Unable to capture screen'),
|
||||||
description: screenCapErrorString
|
description: screenCapErrorString,
|
||||||
});
|
});
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -223,8 +225,8 @@ function _onAction(payload) {
|
||||||
if (module.exports.getAnyActiveCall()) {
|
if (module.exports.getAnyActiveCall()) {
|
||||||
const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
|
const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
|
||||||
Modal.createDialog(ErrorDialog, {
|
Modal.createDialog(ErrorDialog, {
|
||||||
title: "Existing Call",
|
title: _t('Existing Call'),
|
||||||
description: "You are already in a call."
|
description: _t('You are already in a call.'),
|
||||||
});
|
});
|
||||||
return; // don't allow >1 call to be placed.
|
return; // don't allow >1 call to be placed.
|
||||||
}
|
}
|
||||||
|
@ -233,8 +235,8 @@ function _onAction(payload) {
|
||||||
if (!MatrixClientPeg.get().supportsVoip()) {
|
if (!MatrixClientPeg.get().supportsVoip()) {
|
||||||
const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
|
const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
|
||||||
Modal.createDialog(ErrorDialog, {
|
Modal.createDialog(ErrorDialog, {
|
||||||
title: "VoIP is unsupported",
|
title: _t('VoIP is unsupported'),
|
||||||
description: "You cannot place VoIP calls in this browser."
|
description: _t('You cannot place VoIP calls in this browser.'),
|
||||||
});
|
});
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -249,7 +251,7 @@ function _onAction(payload) {
|
||||||
if (members.length <= 1) {
|
if (members.length <= 1) {
|
||||||
const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
|
const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
|
||||||
Modal.createDialog(ErrorDialog, {
|
Modal.createDialog(ErrorDialog, {
|
||||||
description: "You cannot place a call with yourself."
|
description: _t('You cannot place a call with yourself.'),
|
||||||
});
|
});
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -275,14 +277,14 @@ function _onAction(payload) {
|
||||||
if (!ConferenceHandler) {
|
if (!ConferenceHandler) {
|
||||||
const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
|
const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
|
||||||
Modal.createDialog(ErrorDialog, {
|
Modal.createDialog(ErrorDialog, {
|
||||||
description: "Conference calls are not supported in this client"
|
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");
|
const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
|
||||||
Modal.createDialog(ErrorDialog, {
|
Modal.createDialog(ErrorDialog, {
|
||||||
title: "VoIP is unsupported",
|
title: _t('VoIP is unsupported'),
|
||||||
description: "You cannot place VoIP calls in this browser."
|
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)) {
|
||||||
|
@ -294,14 +296,14 @@ function _onAction(payload) {
|
||||||
// Therefore we disable conference calling in E2E rooms.
|
// Therefore we disable conference calling in E2E rooms.
|
||||||
const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
|
const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
|
||||||
Modal.createDialog(ErrorDialog, {
|
Modal.createDialog(ErrorDialog, {
|
||||||
description: "Conference calls are not supported in encrypted rooms",
|
description: _t('Conference calls are not supported in encrypted rooms'),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
var QuestionDialog = sdk.getComponent("dialogs.QuestionDialog");
|
var QuestionDialog = sdk.getComponent("dialogs.QuestionDialog");
|
||||||
Modal.createDialog(QuestionDialog, {
|
Modal.createDialog(QuestionDialog, {
|
||||||
title: "Warning!",
|
title: _t('Warning!'),
|
||||||
description: "Conference calling is in development and may not be reliable.",
|
description: _t('Conference calling is in development and may not be reliable.'),
|
||||||
onFinished: confirm=>{
|
onFinished: confirm=>{
|
||||||
if (confirm) {
|
if (confirm) {
|
||||||
ConferenceHandler.createNewMatrixCall(
|
ConferenceHandler.createNewMatrixCall(
|
||||||
|
@ -312,8 +314,8 @@ function _onAction(payload) {
|
||||||
const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
|
const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
|
||||||
console.error("Conference call failed: " + err);
|
console.error("Conference call failed: " + err);
|
||||||
Modal.createDialog(ErrorDialog, {
|
Modal.createDialog(ErrorDialog, {
|
||||||
title: "Failed to set up conference call",
|
title: _t('Failed to set up conference call'),
|
||||||
description: "Conference call failed.",
|
description: _t('Conference call failed.') + ' ' + ((err && err.message) ? err.message : ''),
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
65
src/CallMediaHandler.js
Normal file
65
src/CallMediaHandler.js
Normal file
|
@ -0,0 +1,65 @@
|
||||||
|
/*
|
||||||
|
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 UserSettingsStore from './UserSettingsStore';
|
||||||
|
import * as Matrix from 'matrix-js-sdk';
|
||||||
|
import q from 'q';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
getDevices: function() {
|
||||||
|
// Only needed for Electron atm, though should work in modern browsers
|
||||||
|
// once permission has been granted to the webapp
|
||||||
|
return navigator.mediaDevices.enumerateDevices().then(function(devices) {
|
||||||
|
const audioIn = [];
|
||||||
|
const videoIn = [];
|
||||||
|
|
||||||
|
if (devices.some((device) => !device.label)) return false;
|
||||||
|
|
||||||
|
devices.forEach((device) => {
|
||||||
|
switch (device.kind) {
|
||||||
|
case 'audioinput': audioIn.push(device); break;
|
||||||
|
case 'videoinput': videoIn.push(device); break;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// console.log("Loaded WebRTC Devices", mediaDevices);
|
||||||
|
return {
|
||||||
|
audioinput: audioIn,
|
||||||
|
videoinput: videoIn,
|
||||||
|
};
|
||||||
|
}, (error) => { console.log('Unable to refresh WebRTC Devices: ', error); });
|
||||||
|
},
|
||||||
|
|
||||||
|
loadDevices: function() {
|
||||||
|
// this.getDevices().then((devices) => {
|
||||||
|
const localSettings = UserSettingsStore.getLocalSettings();
|
||||||
|
// // if deviceId is not found, automatic fallback is in spec
|
||||||
|
// // recall previously stored inputs if any
|
||||||
|
Matrix.setMatrixCallAudioInput(localSettings['webrtc_audioinput']);
|
||||||
|
Matrix.setMatrixCallVideoInput(localSettings['webrtc_videoinput']);
|
||||||
|
// });
|
||||||
|
},
|
||||||
|
|
||||||
|
setAudioInput: function(deviceId) {
|
||||||
|
UserSettingsStore.setLocalSetting('webrtc_audioinput', deviceId);
|
||||||
|
Matrix.setMatrixCallAudioInput(deviceId);
|
||||||
|
},
|
||||||
|
|
||||||
|
setVideoInput: function(deviceId) {
|
||||||
|
UserSettingsStore.setLocalSetting('webrtc_videoinput', deviceId);
|
||||||
|
Matrix.setMatrixCallVideoInput(deviceId);
|
||||||
|
},
|
||||||
|
};
|
|
@ -21,6 +21,7 @@ var extend = require('./extend');
|
||||||
var dis = require('./dispatcher');
|
var dis = require('./dispatcher');
|
||||||
var MatrixClientPeg = require('./MatrixClientPeg');
|
var MatrixClientPeg = require('./MatrixClientPeg');
|
||||||
var sdk = require('./index');
|
var sdk = require('./index');
|
||||||
|
import { _t } from './languageHandler';
|
||||||
var Modal = require('./Modal');
|
var Modal = require('./Modal');
|
||||||
|
|
||||||
var encrypt = require("browser-encrypt-attachment");
|
var encrypt = require("browser-encrypt-attachment");
|
||||||
|
@ -347,14 +348,14 @@ class ContentMessages {
|
||||||
}, function(err) {
|
}, function(err) {
|
||||||
error = err;
|
error = err;
|
||||||
if (!upload.canceled) {
|
if (!upload.canceled) {
|
||||||
var desc = "The file '"+upload.fileName+"' failed to upload.";
|
var desc = _t('The file \'%(fileName)s\' failed to upload', {fileName: upload.fileName}) + '.';
|
||||||
if (err.http_status == 413) {
|
if (err.http_status == 413) {
|
||||||
desc = "The file '"+upload.fileName+"' exceeds this home server's size limit for uploads";
|
desc = _t('The file \'%(fileName)s\' exceeds this home server\'s size limit for uploads', {fileName: upload.fileName});
|
||||||
}
|
}
|
||||||
var ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
|
var ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
|
||||||
Modal.createDialog(ErrorDialog, {
|
Modal.createDialog(ErrorDialog, {
|
||||||
title: "Upload Failed",
|
title: _t('Upload Failed'),
|
||||||
description: desc
|
description: desc,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}).finally(() => {
|
}).finally(() => {
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
/*
|
/*
|
||||||
Copyright 2015, 2016 OpenMarket Ltd
|
Copyright 2015, 2016 OpenMarket Ltd
|
||||||
|
Copyright 2017 Vector Creations Ltd
|
||||||
|
|
||||||
Licensed under the Apache License, Version 2.0 (the "License");
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
you may not use this file except in compliance with the License.
|
you may not use this file except in compliance with the License.
|
||||||
|
@ -15,38 +16,89 @@ limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
'use strict';
|
'use strict';
|
||||||
|
import { _t } from './languageHandler';
|
||||||
|
|
||||||
var days = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"];
|
function getDaysArray() {
|
||||||
var months = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"];
|
return [
|
||||||
|
_t('Sun'),
|
||||||
|
_t('Mon'),
|
||||||
|
_t('Tue'),
|
||||||
|
_t('Wed'),
|
||||||
|
_t('Thu'),
|
||||||
|
_t('Fri'),
|
||||||
|
_t('Sat'),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
function getMonthsArray() {
|
||||||
|
return [
|
||||||
|
_t('Jan'),
|
||||||
|
_t('Feb'),
|
||||||
|
_t('Mar'),
|
||||||
|
_t('Apr'),
|
||||||
|
_t('May'),
|
||||||
|
_t('Jun'),
|
||||||
|
_t('Jul'),
|
||||||
|
_t('Aug'),
|
||||||
|
_t('Sep'),
|
||||||
|
_t('Oct'),
|
||||||
|
_t('Nov'),
|
||||||
|
_t('Dec'),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
function pad(n) {
|
||||||
|
return (n < 10 ? '0' : '') + n;
|
||||||
|
}
|
||||||
|
|
||||||
|
function twelveHourTime(date) {
|
||||||
|
let hours = date.getHours() % 12;
|
||||||
|
const minutes = pad(date.getMinutes());
|
||||||
|
const ampm = date.getHours() >= 12 ? 'PM' : 'AM';
|
||||||
|
hours = pad(hours ? hours : 12);
|
||||||
|
return `${hours}:${minutes}${ampm}`;
|
||||||
|
}
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
formatDate: function(date) {
|
formatDate: function(date) {
|
||||||
// date.toLocaleTimeString is completely system dependent.
|
|
||||||
// just go 24h for now
|
|
||||||
function pad(n) {
|
|
||||||
return (n < 10 ? '0' : '') + n;
|
|
||||||
}
|
|
||||||
|
|
||||||
var now = new Date();
|
var now = new Date();
|
||||||
|
const days = getDaysArray();
|
||||||
|
const months = getMonthsArray();
|
||||||
if (date.toDateString() === now.toDateString()) {
|
if (date.toDateString() === now.toDateString()) {
|
||||||
return pad(date.getHours()) + ':' + pad(date.getMinutes());
|
return this.formatTime(date);
|
||||||
}
|
}
|
||||||
else if (now.getTime() - date.getTime() < 6 * 24 * 60 * 60 * 1000) {
|
else if (now.getTime() - date.getTime() < 6 * 24 * 60 * 60 * 1000) {
|
||||||
return days[date.getDay()] + " " + pad(date.getHours()) + ':' + pad(date.getMinutes());
|
// TODO: use standard date localize function provided in counterpart
|
||||||
|
return _t('%(weekDayName)s %(time)s', {weekDayName: days[date.getDay()], time: this.formatTime(date)});
|
||||||
}
|
}
|
||||||
else /* if (now.getFullYear() === date.getFullYear()) */ {
|
else if (now.getFullYear() === date.getFullYear()) {
|
||||||
return days[date.getDay()] + ", " + months[date.getMonth()] + " " + date.getDate() + " " + pad(date.getHours()) + ':' + pad(date.getMinutes());
|
// TODO: use standard date localize function provided in counterpart
|
||||||
|
return _t('%(weekDayName)s, %(monthName)s %(day)s %(time)s', {
|
||||||
|
weekDayName: days[date.getDay()],
|
||||||
|
monthName: months[date.getMonth()],
|
||||||
|
day: date.getDate(),
|
||||||
|
time: this.formatTime(date),
|
||||||
|
});
|
||||||
}
|
}
|
||||||
/*
|
return this.formatFullDate(date);
|
||||||
else {
|
|
||||||
return days[date.getDay()] + ", " + months[date.getMonth()] + " " + date.getDate() + " " + date.getFullYear() + " " + pad(date.getHours()) + ':' + pad(date.getMinutes());
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
},
|
},
|
||||||
|
|
||||||
formatTime: function(date) {
|
formatFullDate: function(date, showTwelveHour=false) {
|
||||||
//return pad(date.getHours()) + ':' + pad(date.getMinutes());
|
const days = getDaysArray();
|
||||||
return ('00' + date.getHours()).slice(-2) + ':' + ('00' + date.getMinutes()).slice(-2);
|
const months = getMonthsArray();
|
||||||
}
|
return _t('%(weekDayName)s, %(monthName)s %(day)s %(fullYear)s %(time)s', {
|
||||||
};
|
weekDayName: days[date.getDay()],
|
||||||
|
monthName: months[date.getMonth()],
|
||||||
|
day: date.getDate(),
|
||||||
|
fullYear: date.getFullYear(),
|
||||||
|
time: showTwelveHour ? twelveHourTime(date) : this.formatTime(date),
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
formatTime: function(date, showTwelveHour=false) {
|
||||||
|
if (showTwelveHour) {
|
||||||
|
return twelveHourTime(date);
|
||||||
|
}
|
||||||
|
return pad(date.getHours()) + ':' + pad(date.getMinutes());
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
|
@ -25,6 +25,9 @@ import emojione from 'emojione';
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
|
|
||||||
emojione.imagePathSVG = 'emojione/svg/';
|
emojione.imagePathSVG = 'emojione/svg/';
|
||||||
|
// Store PNG path for displaying many flags at once (for increased performance over SVG)
|
||||||
|
emojione.imagePathPNG = 'emojione/png/';
|
||||||
|
// Use SVGs for emojis
|
||||||
emojione.imageType = 'svg';
|
emojione.imageType = 'svg';
|
||||||
|
|
||||||
const EMOJI_REGEX = new RegExp(emojione.unicodeRegexp+"+", "gi");
|
const EMOJI_REGEX = new RegExp(emojione.unicodeRegexp+"+", "gi");
|
||||||
|
@ -64,16 +67,23 @@ export function unicodeToImage(str) {
|
||||||
* emoji.
|
* emoji.
|
||||||
*
|
*
|
||||||
* @param alt {string} String to use for the image alt text
|
* @param alt {string} String to use for the image alt text
|
||||||
|
* @param useSvg {boolean} Whether to use SVG image src. If False, PNG will be used.
|
||||||
* @param unicode {integer} One or more integers representing unicode characters
|
* @param unicode {integer} One or more integers representing unicode characters
|
||||||
* @returns A img node with the corresponding emoji
|
* @returns A img node with the corresponding emoji
|
||||||
*/
|
*/
|
||||||
export function charactersToImageNode(alt, ...unicode) {
|
export function charactersToImageNode(alt, useSvg, ...unicode) {
|
||||||
const fileName = unicode.map((u) => {
|
const fileName = unicode.map((u) => {
|
||||||
return u.toString(16);
|
return u.toString(16);
|
||||||
}).join('-');
|
}).join('-');
|
||||||
return <img alt={alt} src={`${emojione.imagePathSVG}${fileName}.svg${emojione.cacheBustParam}`}/>;
|
const path = useSvg ? emojione.imagePathSVG : emojione.imagePathPNG;
|
||||||
|
const fileType = useSvg ? 'svg' : 'png';
|
||||||
|
return <img
|
||||||
|
alt={alt}
|
||||||
|
src={`${path}${fileName}.${fileType}${emojione.cacheBustParam}`}
|
||||||
|
/>;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
export function stripParagraphs(html: string): string {
|
export function stripParagraphs(html: string): string {
|
||||||
const contentDiv = document.createElement('div');
|
const contentDiv = document.createElement('div');
|
||||||
contentDiv.innerHTML = html;
|
contentDiv.innerHTML = html;
|
||||||
|
@ -101,8 +111,7 @@ var sanitizeHtmlParams = {
|
||||||
allowedTags: [
|
allowedTags: [
|
||||||
'font', // custom to matrix for IRC-style font coloring
|
'font', // custom to matrix for IRC-style font coloring
|
||||||
'del', // for markdown
|
'del', // for markdown
|
||||||
// deliberately no h1/h2 to stop people shouting.
|
'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'blockquote', 'p', 'a', 'ul', 'ol',
|
||||||
'h3', 'h4', 'h5', 'h6', 'blockquote', 'p', 'a', 'ul', 'ol',
|
|
||||||
'nl', 'li', 'b', 'i', 'u', 'strong', 'em', 'strike', 'code', 'hr', 'br', 'div',
|
'nl', 'li', 'b', 'i', 'u', 'strong', 'em', 'strike', 'code', 'hr', 'br', 'div',
|
||||||
'table', 'thead', 'caption', 'tbody', 'tr', 'th', 'td', 'pre', 'span',
|
'table', 'thead', 'caption', 'tbody', 'tr', 'th', 'td', 'pre', 'span',
|
||||||
],
|
],
|
||||||
|
@ -139,7 +148,7 @@ var sanitizeHtmlParams = {
|
||||||
attribs.href = m[1];
|
attribs.href = m[1];
|
||||||
delete attribs.target;
|
delete attribs.target;
|
||||||
}
|
}
|
||||||
|
else {
|
||||||
m = attribs.href.match(linkifyMatrix.MATRIXTO_URL_PATTERN);
|
m = attribs.href.match(linkifyMatrix.MATRIXTO_URL_PATTERN);
|
||||||
if (m) {
|
if (m) {
|
||||||
var entity = m[1];
|
var entity = m[1];
|
||||||
|
@ -152,6 +161,7 @@ var sanitizeHtmlParams = {
|
||||||
delete attribs.target;
|
delete attribs.target;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
attribs.rel = 'noopener'; // https://mathiasbynens.github.io/rel-noopener/
|
attribs.rel = 'noopener'; // https://mathiasbynens.github.io/rel-noopener/
|
||||||
return { tagName: tagName, attribs : attribs };
|
return { tagName: tagName, attribs : attribs };
|
||||||
},
|
},
|
||||||
|
@ -350,7 +360,7 @@ export function bodyToHtml(content, highlights, opts) {
|
||||||
'mx_EventTile_bigEmoji': emojiBody,
|
'mx_EventTile_bigEmoji': emojiBody,
|
||||||
'markdown-body': isHtml,
|
'markdown-body': isHtml,
|
||||||
});
|
});
|
||||||
return <span className={className} dangerouslySetInnerHTML={{ __html: safeBody }} />;
|
return <span className={className} dangerouslySetInnerHTML={{ __html: safeBody }} dir="auto" />;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function emojifyText(text) {
|
export function emojifyText(text) {
|
||||||
|
|
|
@ -19,6 +19,7 @@ import q from 'q';
|
||||||
import Matrix from 'matrix-js-sdk';
|
import Matrix from 'matrix-js-sdk';
|
||||||
|
|
||||||
import MatrixClientPeg from './MatrixClientPeg';
|
import MatrixClientPeg from './MatrixClientPeg';
|
||||||
|
import Analytics from './Analytics';
|
||||||
import Notifier from './Notifier';
|
import Notifier from './Notifier';
|
||||||
import UserActivity from './UserActivity';
|
import UserActivity from './UserActivity';
|
||||||
import Presence from './Presence';
|
import Presence from './Presence';
|
||||||
|
@ -27,6 +28,7 @@ import DMRoomMap from './utils/DMRoomMap';
|
||||||
import RtsClient from './RtsClient';
|
import RtsClient from './RtsClient';
|
||||||
import Modal from './Modal';
|
import Modal from './Modal';
|
||||||
import sdk from './index';
|
import sdk from './index';
|
||||||
|
import { _t } from './languageHandler';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Called at startup, to attempt to build a logged-in Matrix session. It tries
|
* Called at startup, to attempt to build a logged-in Matrix session. It tries
|
||||||
|
@ -49,7 +51,7 @@ import sdk from './index';
|
||||||
* If any of steps 1-4 are successful, it will call {setLoggedIn}, which in
|
* If any of steps 1-4 are successful, it will call {setLoggedIn}, which in
|
||||||
* turn will raise on_logged_in and will_start_client events.
|
* turn will raise on_logged_in and will_start_client events.
|
||||||
*
|
*
|
||||||
* It returns a promise which resolves when the above process completes.
|
* @param {object} opts
|
||||||
*
|
*
|
||||||
* @param {object} opts.realQueryParams: string->string map of the
|
* @param {object} opts.realQueryParams: string->string map of the
|
||||||
* query-parameters extracted from the real query-string of the starting
|
* query-parameters extracted from the real query-string of the starting
|
||||||
|
@ -67,6 +69,7 @@ import sdk from './index';
|
||||||
* @params {string} opts.guestIsUrl: homeserver URL. Only used if enableGuest is
|
* @params {string} opts.guestIsUrl: homeserver URL. Only used if enableGuest is
|
||||||
* true; defines the IS to use.
|
* true; defines the IS to use.
|
||||||
*
|
*
|
||||||
|
* @returns {Promise} a promise which resolves when the above process completes.
|
||||||
*/
|
*/
|
||||||
export function loadSession(opts) {
|
export function loadSession(opts) {
|
||||||
const realQueryParams = opts.realQueryParams || {};
|
const realQueryParams = opts.realQueryParams || {};
|
||||||
|
@ -127,7 +130,7 @@ export function loadSession(opts) {
|
||||||
|
|
||||||
function _loginWithToken(queryParams, defaultDeviceDisplayName) {
|
function _loginWithToken(queryParams, defaultDeviceDisplayName) {
|
||||||
// create a temporary MatrixClient to do the login
|
// create a temporary MatrixClient to do the login
|
||||||
var client = Matrix.createClient({
|
const client = Matrix.createClient({
|
||||||
baseUrl: queryParams.homeserver,
|
baseUrl: queryParams.homeserver,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -159,7 +162,7 @@ function _registerAsGuest(hsUrl, isUrl, defaultDeviceDisplayName) {
|
||||||
// Not really sure where the right home for it is.
|
// Not really sure where the right home for it is.
|
||||||
|
|
||||||
// create a temporary MatrixClient to do the login
|
// create a temporary MatrixClient to do the login
|
||||||
var client = Matrix.createClient({
|
const client = Matrix.createClient({
|
||||||
baseUrl: hsUrl,
|
baseUrl: hsUrl,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -188,30 +191,30 @@ function _restoreFromLocalStorage() {
|
||||||
if (!localStorage) {
|
if (!localStorage) {
|
||||||
return q(false);
|
return q(false);
|
||||||
}
|
}
|
||||||
const hs_url = localStorage.getItem("mx_hs_url");
|
const hsUrl = localStorage.getItem("mx_hs_url");
|
||||||
const is_url = localStorage.getItem("mx_is_url") || 'https://matrix.org';
|
const isUrl = localStorage.getItem("mx_is_url") || 'https://matrix.org';
|
||||||
const access_token = localStorage.getItem("mx_access_token");
|
const accessToken = localStorage.getItem("mx_access_token");
|
||||||
const user_id = localStorage.getItem("mx_user_id");
|
const userId = localStorage.getItem("mx_user_id");
|
||||||
const device_id = localStorage.getItem("mx_device_id");
|
const deviceId = localStorage.getItem("mx_device_id");
|
||||||
|
|
||||||
let is_guest;
|
let isGuest;
|
||||||
if (localStorage.getItem("mx_is_guest") !== null) {
|
if (localStorage.getItem("mx_is_guest") !== null) {
|
||||||
is_guest = localStorage.getItem("mx_is_guest") === "true";
|
isGuest = localStorage.getItem("mx_is_guest") === "true";
|
||||||
} else {
|
} else {
|
||||||
// legacy key name
|
// legacy key name
|
||||||
is_guest = localStorage.getItem("matrix-is-guest") === "true";
|
isGuest = localStorage.getItem("matrix-is-guest") === "true";
|
||||||
}
|
}
|
||||||
|
|
||||||
if (access_token && user_id && hs_url) {
|
if (accessToken && userId && hsUrl) {
|
||||||
console.log("Restoring session for %s", user_id);
|
console.log("Restoring session for %s", userId);
|
||||||
try {
|
try {
|
||||||
setLoggedIn({
|
setLoggedIn({
|
||||||
userId: user_id,
|
userId: userId,
|
||||||
deviceId: device_id,
|
deviceId: deviceId,
|
||||||
accessToken: access_token,
|
accessToken: accessToken,
|
||||||
homeserverUrl: hs_url,
|
homeserverUrl: hsUrl,
|
||||||
identityServerUrl: is_url,
|
identityServerUrl: isUrl,
|
||||||
guest: is_guest,
|
guest: isGuest,
|
||||||
});
|
});
|
||||||
return q(true);
|
return q(true);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
|
@ -228,14 +231,16 @@ function _handleRestoreFailure(e) {
|
||||||
|
|
||||||
let msg = e.message;
|
let msg = e.message;
|
||||||
if (msg == "OLM.BAD_LEGACY_ACCOUNT_PICKLE") {
|
if (msg == "OLM.BAD_LEGACY_ACCOUNT_PICKLE") {
|
||||||
msg = "You need to log back in to generate end-to-end encryption keys "
|
msg = _t(
|
||||||
+ "for this device and submit the public key to your homeserver. "
|
'You need to log back in to generate end-to-end encryption keys'
|
||||||
+ "This is a once off; sorry for the inconvenience.";
|
+ ' for this device and submit the public key to your homeserver.'
|
||||||
|
+ ' This is a once off; sorry for the inconvenience.',
|
||||||
|
);
|
||||||
|
|
||||||
_clearLocalStorage();
|
_clearLocalStorage();
|
||||||
|
|
||||||
return q.reject(new Error(
|
return q.reject(new Error(
|
||||||
"Unable to restore previous session: " + msg,
|
_t('Unable to restore previous session') + ': ' + msg,
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -273,9 +278,15 @@ export function initRtsClient(url) {
|
||||||
*/
|
*/
|
||||||
export function setLoggedIn(credentials) {
|
export function setLoggedIn(credentials) {
|
||||||
credentials.guest = Boolean(credentials.guest);
|
credentials.guest = Boolean(credentials.guest);
|
||||||
console.log("setLoggedIn => %s (guest=%s) hs=%s",
|
|
||||||
credentials.userId, credentials.guest,
|
Analytics.setGuest(credentials.guest);
|
||||||
credentials.homeserverUrl);
|
|
||||||
|
console.log(
|
||||||
|
"setLoggedIn: mxid:", credentials.userId,
|
||||||
|
"deviceId:", credentials.deviceId,
|
||||||
|
"guest:", credentials.guest,
|
||||||
|
"hs:", credentials.homeserverUrl,
|
||||||
|
);
|
||||||
// This is dispatched to indicate that the user is still in the process of logging in
|
// This is dispatched to indicate that the user is still in the process of logging in
|
||||||
// because `teamPromise` may take some time to resolve, breaking the assumption that
|
// because `teamPromise` may take some time to resolve, breaking the assumption that
|
||||||
// `setLoggedIn` takes an "instant" to complete, and dispatch `on_logged_in` a few ms
|
// `setLoggedIn` takes an "instant" to complete, and dispatch `on_logged_in` a few ms
|
||||||
|
@ -352,7 +363,7 @@ export function logout() {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
return MatrixClientPeg.get().logout().then(onLoggedOut,
|
MatrixClientPeg.get().logout().then(onLoggedOut,
|
||||||
(err) => {
|
(err) => {
|
||||||
// Just throwing an error here is going to be very unhelpful
|
// Just throwing an error here is going to be very unhelpful
|
||||||
// if you're trying to log out because your server's down and
|
// if you're trying to log out because your server's down and
|
||||||
|
@ -363,8 +374,8 @@ export function logout() {
|
||||||
// change your password).
|
// change your password).
|
||||||
console.log("Failed to call logout API: token will not be invalidated");
|
console.log("Failed to call logout API: token will not be invalidated");
|
||||||
onLoggedOut();
|
onLoggedOut();
|
||||||
}
|
},
|
||||||
);
|
).done();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -397,6 +408,7 @@ export function onLoggedOut() {
|
||||||
}
|
}
|
||||||
|
|
||||||
function _clearLocalStorage() {
|
function _clearLocalStorage() {
|
||||||
|
Analytics.logout();
|
||||||
if (!window.localStorage) {
|
if (!window.localStorage) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -420,7 +432,7 @@ export function stopMatrixClient() {
|
||||||
UserActivity.stop();
|
UserActivity.stop();
|
||||||
Presence.stop();
|
Presence.stop();
|
||||||
if (DMRoomMap.shared()) DMRoomMap.shared().stop();
|
if (DMRoomMap.shared()) DMRoomMap.shared().stop();
|
||||||
var cli = MatrixClientPeg.get();
|
const cli = MatrixClientPeg.get();
|
||||||
if (cli) {
|
if (cli) {
|
||||||
cli.stopClient();
|
cli.stopClient();
|
||||||
cli.removeAllListeners();
|
cli.removeAllListeners();
|
||||||
|
|
11
src/Login.js
11
src/Login.js
|
@ -16,6 +16,7 @@ limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import Matrix from "matrix-js-sdk";
|
import Matrix from "matrix-js-sdk";
|
||||||
|
import { _t } from "./languageHandler";
|
||||||
|
|
||||||
import q from 'q';
|
import q from 'q';
|
||||||
import url from 'url';
|
import url from 'url';
|
||||||
|
@ -97,9 +98,9 @@ export default class Login {
|
||||||
};
|
};
|
||||||
}, (error) => {
|
}, (error) => {
|
||||||
if (error.httpStatus === 403) {
|
if (error.httpStatus === 403) {
|
||||||
error.friendlyText = "Guest access is disabled on this Home Server.";
|
error.friendlyText = _t("Guest access is disabled on this Home Server.");
|
||||||
} else {
|
} else {
|
||||||
error.friendlyText = "Failed to register as guest: " + error.data;
|
error.friendlyText = _t("Failed to register as guest:") + ' ' + error.data;
|
||||||
}
|
}
|
||||||
throw error;
|
throw error;
|
||||||
});
|
});
|
||||||
|
@ -158,12 +159,12 @@ export default class Login {
|
||||||
}, function(error) {
|
}, function(error) {
|
||||||
if (error.httpStatus == 400 && loginParams.medium) {
|
if (error.httpStatus == 400 && loginParams.medium) {
|
||||||
error.friendlyText = (
|
error.friendlyText = (
|
||||||
'This Home Server does not support login using email address.'
|
_t('This Home Server does not support login using email address.')
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
else if (error.httpStatus === 403) {
|
else if (error.httpStatus === 403) {
|
||||||
error.friendlyText = (
|
error.friendlyText = (
|
||||||
'Incorrect username and/or password.'
|
_t('Incorrect username and/or password.')
|
||||||
);
|
);
|
||||||
if (self._fallbackHsUrl) {
|
if (self._fallbackHsUrl) {
|
||||||
var fbClient = Matrix.createClient({
|
var fbClient = Matrix.createClient({
|
||||||
|
@ -187,7 +188,7 @@ export default class Login {
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
error.friendlyText = (
|
error.friendlyText = (
|
||||||
'There was a problem logging in. (HTTP ' + error.httpStatus + ")"
|
_t("There was a problem logging in.") + ' (HTTP ' + error.httpStatus + ")"
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
throw error;
|
throw error;
|
||||||
|
|
|
@ -19,6 +19,7 @@ limitations under the License.
|
||||||
|
|
||||||
var React = require('react');
|
var React = require('react');
|
||||||
var ReactDOM = require('react-dom');
|
var ReactDOM = require('react-dom');
|
||||||
|
import Analytics from './Analytics';
|
||||||
import sdk from './index';
|
import sdk from './index';
|
||||||
|
|
||||||
const DIALOG_CONTAINER_ID = "mx_Dialog_Container";
|
const DIALOG_CONTAINER_ID = "mx_Dialog_Container";
|
||||||
|
@ -104,6 +105,9 @@ class ModalManager {
|
||||||
}
|
}
|
||||||
|
|
||||||
createDialog(Element, props, className) {
|
createDialog(Element, props, className) {
|
||||||
|
if (props && props.title) {
|
||||||
|
Analytics.trackEvent('Modal', props.title, 'createDialog');
|
||||||
|
}
|
||||||
return this.createDialogAsync((cb) => {cb(Element);}, props, className);
|
return this.createDialogAsync((cb) => {cb(Element);}, props, className);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -15,11 +15,15 @@ See the License for the specific language governing permissions and
|
||||||
limitations under the License.
|
limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
var MatrixClientPeg = require("./MatrixClientPeg");
|
import MatrixClientPeg from './MatrixClientPeg';
|
||||||
var PlatformPeg = require("./PlatformPeg");
|
import PlatformPeg from './PlatformPeg';
|
||||||
var TextForEvent = require('./TextForEvent');
|
import TextForEvent from './TextForEvent';
|
||||||
var Avatar = require('./Avatar');
|
import Analytics from './Analytics';
|
||||||
var dis = require("./dispatcher");
|
import Avatar from './Avatar';
|
||||||
|
import dis from './dispatcher';
|
||||||
|
import sdk from './index';
|
||||||
|
import { _t } from './languageHandler';
|
||||||
|
import Modal from './Modal';
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Dispatches:
|
* Dispatches:
|
||||||
|
@ -29,7 +33,7 @@ var dis = require("./dispatcher");
|
||||||
* }
|
* }
|
||||||
*/
|
*/
|
||||||
|
|
||||||
var Notifier = {
|
const Notifier = {
|
||||||
notifsByRoom: {},
|
notifsByRoom: {},
|
||||||
|
|
||||||
notificationMessageForEvent: function(ev) {
|
notificationMessageForEvent: function(ev) {
|
||||||
|
@ -48,16 +52,16 @@ var Notifier = {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
var msg = this.notificationMessageForEvent(ev);
|
let msg = this.notificationMessageForEvent(ev);
|
||||||
if (!msg) return;
|
if (!msg) return;
|
||||||
|
|
||||||
var title;
|
let title;
|
||||||
if (!ev.sender || room.name == ev.sender.name) {
|
if (!ev.sender || room.name === ev.sender.name) {
|
||||||
title = room.name;
|
title = room.name;
|
||||||
// notificationMessageForEvent includes sender,
|
// notificationMessageForEvent includes sender,
|
||||||
// but we already have the sender here
|
// but we already have the sender here
|
||||||
if (ev.getContent().body) msg = ev.getContent().body;
|
if (ev.getContent().body) msg = ev.getContent().body;
|
||||||
} else if (ev.getType() == 'm.room.member') {
|
} else if (ev.getType() === 'm.room.member') {
|
||||||
// context is all in the message here, we don't need
|
// context is all in the message here, we don't need
|
||||||
// to display sender info
|
// to display sender info
|
||||||
title = room.name;
|
title = room.name;
|
||||||
|
@ -68,7 +72,7 @@ var Notifier = {
|
||||||
if (ev.getContent().body) msg = ev.getContent().body;
|
if (ev.getContent().body) msg = ev.getContent().body;
|
||||||
}
|
}
|
||||||
|
|
||||||
var avatarUrl = ev.sender ? Avatar.avatarUrlForMember(
|
const avatarUrl = ev.sender ? Avatar.avatarUrlForMember(
|
||||||
ev.sender, 40, 40, 'crop'
|
ev.sender, 40, 40, 'crop'
|
||||||
) : null;
|
) : null;
|
||||||
|
|
||||||
|
@ -83,7 +87,7 @@ var Notifier = {
|
||||||
},
|
},
|
||||||
|
|
||||||
_playAudioNotification: function(ev, room) {
|
_playAudioNotification: function(ev, room) {
|
||||||
var e = document.getElementById("messageAudio");
|
const e = document.getElementById("messageAudio");
|
||||||
if (e) {
|
if (e) {
|
||||||
e.load();
|
e.load();
|
||||||
e.play();
|
e.play();
|
||||||
|
@ -95,7 +99,7 @@ var Notifier = {
|
||||||
this.boundOnSyncStateChange = this.onSyncStateChange.bind(this);
|
this.boundOnSyncStateChange = this.onSyncStateChange.bind(this);
|
||||||
this.boundOnRoomReceipt = this.onRoomReceipt.bind(this);
|
this.boundOnRoomReceipt = this.onRoomReceipt.bind(this);
|
||||||
MatrixClientPeg.get().on('Room.timeline', this.boundOnRoomTimeline);
|
MatrixClientPeg.get().on('Room.timeline', this.boundOnRoomTimeline);
|
||||||
MatrixClientPeg.get().on("Room.receipt", this.boundOnRoomReceipt);
|
MatrixClientPeg.get().on('Room.receipt', this.boundOnRoomReceipt);
|
||||||
MatrixClientPeg.get().on("sync", this.boundOnSyncStateChange);
|
MatrixClientPeg.get().on("sync", this.boundOnSyncStateChange);
|
||||||
this.toolbarHidden = false;
|
this.toolbarHidden = false;
|
||||||
this.isSyncing = false;
|
this.isSyncing = false;
|
||||||
|
@ -104,7 +108,7 @@ var Notifier = {
|
||||||
stop: function() {
|
stop: function() {
|
||||||
if (MatrixClientPeg.get() && this.boundOnRoomTimeline) {
|
if (MatrixClientPeg.get() && this.boundOnRoomTimeline) {
|
||||||
MatrixClientPeg.get().removeListener('Room.timeline', this.boundOnRoomTimeline);
|
MatrixClientPeg.get().removeListener('Room.timeline', this.boundOnRoomTimeline);
|
||||||
MatrixClientPeg.get().removeListener("Room.receipt", this.boundOnRoomReceipt);
|
MatrixClientPeg.get().removeListener('Room.receipt', this.boundOnRoomReceipt);
|
||||||
MatrixClientPeg.get().removeListener('sync', this.boundOnSyncStateChange);
|
MatrixClientPeg.get().removeListener('sync', this.boundOnSyncStateChange);
|
||||||
}
|
}
|
||||||
this.isSyncing = false;
|
this.isSyncing = false;
|
||||||
|
@ -118,10 +122,13 @@ var Notifier = {
|
||||||
setEnabled: function(enable, callback) {
|
setEnabled: function(enable, callback) {
|
||||||
const plaf = PlatformPeg.get();
|
const plaf = PlatformPeg.get();
|
||||||
if (!plaf) return;
|
if (!plaf) return;
|
||||||
|
|
||||||
|
Analytics.trackEvent('Notifier', 'Set Enabled', enable);
|
||||||
|
|
||||||
// make sure that we persist the current setting audio_enabled setting
|
// make sure that we persist the current setting audio_enabled setting
|
||||||
// before changing anything
|
// before changing anything
|
||||||
if (global.localStorage) {
|
if (global.localStorage) {
|
||||||
if(global.localStorage.getItem('audio_notifications_enabled') == null) {
|
if (global.localStorage.getItem('audio_notifications_enabled') === null) {
|
||||||
this.setAudioEnabled(this.isEnabled());
|
this.setAudioEnabled(this.isEnabled());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -131,6 +138,14 @@ var Notifier = {
|
||||||
plaf.requestNotificationPermission().done((result) => {
|
plaf.requestNotificationPermission().done((result) => {
|
||||||
if (result !== 'granted') {
|
if (result !== 'granted') {
|
||||||
// The permission request was dismissed or denied
|
// The permission request was dismissed or denied
|
||||||
|
const description = result === 'denied'
|
||||||
|
? _t('Riot does not have permission to send you notifications - please check your browser settings')
|
||||||
|
: _t('Riot was not given permission to send notifications - please try again');
|
||||||
|
const ErrorDialog = sdk.getComponent('dialogs.ErrorDialog');
|
||||||
|
Modal.createDialog(ErrorDialog, {
|
||||||
|
title: _t('Unable to enable Notifications'),
|
||||||
|
description,
|
||||||
|
});
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -141,7 +156,7 @@ var Notifier = {
|
||||||
if (callback) callback();
|
if (callback) callback();
|
||||||
dis.dispatch({
|
dis.dispatch({
|
||||||
action: "notifier_enabled",
|
action: "notifier_enabled",
|
||||||
value: true
|
value: true,
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
// clear the notifications_hidden flag, so that if notifications are
|
// clear the notifications_hidden flag, so that if notifications are
|
||||||
|
@ -152,7 +167,7 @@ var Notifier = {
|
||||||
global.localStorage.setItem('notifications_enabled', 'false');
|
global.localStorage.setItem('notifications_enabled', 'false');
|
||||||
dis.dispatch({
|
dis.dispatch({
|
||||||
action: "notifier_enabled",
|
action: "notifier_enabled",
|
||||||
value: false
|
value: false,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -165,7 +180,7 @@ var Notifier = {
|
||||||
|
|
||||||
if (!global.localStorage) return true;
|
if (!global.localStorage) return true;
|
||||||
|
|
||||||
var enabled = global.localStorage.getItem('notifications_enabled');
|
const enabled = global.localStorage.getItem('notifications_enabled');
|
||||||
if (enabled === null) return true;
|
if (enabled === null) return true;
|
||||||
return enabled === 'true';
|
return enabled === 'true';
|
||||||
},
|
},
|
||||||
|
@ -178,7 +193,7 @@ var Notifier = {
|
||||||
|
|
||||||
isAudioEnabled: function(enable) {
|
isAudioEnabled: function(enable) {
|
||||||
if (!global.localStorage) return true;
|
if (!global.localStorage) return true;
|
||||||
var enabled = global.localStorage.getItem(
|
const enabled = global.localStorage.getItem(
|
||||||
'audio_notifications_enabled');
|
'audio_notifications_enabled');
|
||||||
// default to true if the popups are enabled
|
// default to true if the popups are enabled
|
||||||
if (enabled === null) return this.isEnabled();
|
if (enabled === null) return this.isEnabled();
|
||||||
|
@ -188,11 +203,13 @@ var Notifier = {
|
||||||
setToolbarHidden: function(hidden, persistent = true) {
|
setToolbarHidden: function(hidden, persistent = true) {
|
||||||
this.toolbarHidden = hidden;
|
this.toolbarHidden = hidden;
|
||||||
|
|
||||||
|
Analytics.trackEvent('Notifier', 'Set Toolbar Hidden', hidden);
|
||||||
|
|
||||||
// XXX: why are we dispatching this here?
|
// XXX: why are we dispatching this here?
|
||||||
// this is nothing to do with notifier_enabled
|
// this is nothing to do with notifier_enabled
|
||||||
dis.dispatch({
|
dis.dispatch({
|
||||||
action: "notifier_enabled",
|
action: "notifier_enabled",
|
||||||
value: this.isEnabled()
|
value: this.isEnabled(),
|
||||||
});
|
});
|
||||||
|
|
||||||
// update the info to localStorage for persistent settings
|
// update the info to localStorage for persistent settings
|
||||||
|
@ -215,8 +232,7 @@ var Notifier = {
|
||||||
onSyncStateChange: function(state) {
|
onSyncStateChange: function(state) {
|
||||||
if (state === "SYNCING") {
|
if (state === "SYNCING") {
|
||||||
this.isSyncing = true;
|
this.isSyncing = true;
|
||||||
}
|
} else if (state === "STOPPED" || state === "ERROR") {
|
||||||
else if (state === "STOPPED" || state === "ERROR") {
|
|
||||||
this.isSyncing = false;
|
this.isSyncing = false;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -225,22 +241,23 @@ var Notifier = {
|
||||||
if (toStartOfTimeline) return;
|
if (toStartOfTimeline) return;
|
||||||
if (!room) return;
|
if (!room) return;
|
||||||
if (!this.isSyncing) return; // don't alert for any messages initially
|
if (!this.isSyncing) return; // don't alert for any messages initially
|
||||||
if (ev.sender && ev.sender.userId == MatrixClientPeg.get().credentials.userId) return;
|
if (ev.sender && ev.sender.userId === MatrixClientPeg.get().credentials.userId) return;
|
||||||
if (data.timeline.getTimelineSet() !== room.getUnfilteredTimelineSet()) return;
|
if (data.timeline.getTimelineSet() !== room.getUnfilteredTimelineSet()) return;
|
||||||
|
|
||||||
var actions = MatrixClientPeg.get().getPushActionsForEvent(ev);
|
const actions = MatrixClientPeg.get().getPushActionsForEvent(ev);
|
||||||
if (actions && actions.notify) {
|
if (actions && actions.notify) {
|
||||||
if (this.isEnabled()) {
|
if (this.isEnabled()) {
|
||||||
this._displayPopupNotification(ev, room);
|
this._displayPopupNotification(ev, room);
|
||||||
}
|
}
|
||||||
if (actions.tweaks.sound && this.isAudioEnabled()) {
|
if (actions.tweaks.sound && this.isAudioEnabled()) {
|
||||||
|
PlatformPeg.get().loudNotification(ev, room);
|
||||||
this._playAudioNotification(ev, room);
|
this._playAudioNotification(ev, room);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
onRoomReceipt: function(ev, room) {
|
onRoomReceipt: function(ev, room) {
|
||||||
if (room.getUnreadNotificationCount() == 0) {
|
if (room.getUnreadNotificationCount() === 0) {
|
||||||
// ideally we would clear each notification when it was read,
|
// ideally we would clear each notification when it was read,
|
||||||
// but we have no way, given a read receipt, to know whether
|
// but we have no way, given a read receipt, to know whether
|
||||||
// the receipt comes before or after an event, so we can't
|
// the receipt comes before or after an event, so we can't
|
||||||
|
@ -255,7 +272,7 @@ var Notifier = {
|
||||||
}
|
}
|
||||||
delete this.notifsByRoom[room.roomId];
|
delete this.notifsByRoom[room.roomId];
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
if (!global.mxNotifier) {
|
if (!global.mxNotifier) {
|
||||||
|
|
|
@ -15,6 +15,7 @@ limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
var Matrix = require("matrix-js-sdk");
|
var Matrix = require("matrix-js-sdk");
|
||||||
|
import { _t } from './languageHandler';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Allows a user to reset their password on a homeserver.
|
* Allows a user to reset their password on a homeserver.
|
||||||
|
@ -53,7 +54,7 @@ class PasswordReset {
|
||||||
return res;
|
return res;
|
||||||
}, function(err) {
|
}, function(err) {
|
||||||
if (err.errcode == 'M_THREEPID_NOT_FOUND') {
|
if (err.errcode == 'M_THREEPID_NOT_FOUND') {
|
||||||
err.message = "This email address was not found";
|
err.message = _t('This email address was not found');
|
||||||
} else if (err.httpStatus) {
|
} else if (err.httpStatus) {
|
||||||
err.message = err.message + ` (Status ${err.httpStatus})`;
|
err.message = err.message + ` (Status ${err.httpStatus})`;
|
||||||
}
|
}
|
||||||
|
@ -78,10 +79,10 @@ class PasswordReset {
|
||||||
}
|
}
|
||||||
}, this.password).catch(function(err) {
|
}, this.password).catch(function(err) {
|
||||||
if (err.httpStatus === 401) {
|
if (err.httpStatus === 401) {
|
||||||
err.message = "Failed to verify email address: make sure you clicked the link in the email";
|
err.message = _t('Failed to verify email address: make sure you clicked the link in the email');
|
||||||
}
|
}
|
||||||
else if (err.httpStatus === 404) {
|
else if (err.httpStatus === 404) {
|
||||||
err.message = "Your email address does not appear to be associated with a Matrix ID on this Homeserver.";
|
err.message = _t('Your email address does not appear to be associated with a Matrix ID on this Homeserver.');
|
||||||
}
|
}
|
||||||
else if (err.httpStatus) {
|
else if (err.httpStatus) {
|
||||||
err.message += ` (Status ${err.httpStatus})`;
|
err.message += ` (Status ${err.httpStatus})`;
|
||||||
|
|
34
src/Roles.js
Normal file
34
src/Roles.js
Normal file
|
@ -0,0 +1,34 @@
|
||||||
|
/*
|
||||||
|
Copyright 2017 Vector Creations 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 { _t } from './languageHandler';
|
||||||
|
|
||||||
|
export function levelRoleMap() {
|
||||||
|
return {
|
||||||
|
undefined: _t('Default'),
|
||||||
|
0: _t('User'),
|
||||||
|
50: _t('Moderator'),
|
||||||
|
100: _t('Admin'),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export function textualPowerLevel(level, userDefault) {
|
||||||
|
const LEVEL_ROLE_MAP = this.levelRoleMap();
|
||||||
|
if (LEVEL_ROLE_MAP[level]) {
|
||||||
|
return LEVEL_ROLE_MAP[level] + (level !== undefined ? ` (${level})` : ` (${userDefault})`);
|
||||||
|
} else {
|
||||||
|
return level;
|
||||||
|
}
|
||||||
|
}
|
|
@ -125,6 +125,7 @@ const SdkConfig = require('./SdkConfig');
|
||||||
const MatrixClientPeg = require("./MatrixClientPeg");
|
const MatrixClientPeg = require("./MatrixClientPeg");
|
||||||
const MatrixEvent = require("matrix-js-sdk").MatrixEvent;
|
const MatrixEvent = require("matrix-js-sdk").MatrixEvent;
|
||||||
const dis = require("./dispatcher");
|
const dis = require("./dispatcher");
|
||||||
|
import { _t } from './languageHandler';
|
||||||
|
|
||||||
function sendResponse(event, res) {
|
function sendResponse(event, res) {
|
||||||
const data = JSON.parse(JSON.stringify(event.data));
|
const data = JSON.parse(JSON.stringify(event.data));
|
||||||
|
@ -150,7 +151,7 @@ function inviteUser(event, roomId, userId) {
|
||||||
console.log(`Received request to invite ${userId} into room ${roomId}`);
|
console.log(`Received request to invite ${userId} into room ${roomId}`);
|
||||||
const client = MatrixClientPeg.get();
|
const client = MatrixClientPeg.get();
|
||||||
if (!client) {
|
if (!client) {
|
||||||
sendError(event, "You need to be logged in.");
|
sendError(event, _t('You need to be logged in.'));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const room = client.getRoom(roomId);
|
const room = client.getRoom(roomId);
|
||||||
|
@ -170,7 +171,7 @@ function inviteUser(event, roomId, userId) {
|
||||||
success: true,
|
success: true,
|
||||||
});
|
});
|
||||||
}, function(err) {
|
}, function(err) {
|
||||||
sendError(event, "You need to be able to invite users to do that.", err);
|
sendError(event, _t('You need to be able to invite users to do that.'), err);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -181,7 +182,7 @@ function setPlumbingState(event, roomId, status) {
|
||||||
console.log(`Received request to set plumbing state to status "${status}" in room ${roomId}`);
|
console.log(`Received request to set plumbing state to status "${status}" in room ${roomId}`);
|
||||||
const client = MatrixClientPeg.get();
|
const client = MatrixClientPeg.get();
|
||||||
if (!client) {
|
if (!client) {
|
||||||
sendError(event, "You need to be logged in.");
|
sendError(event, _t('You need to be logged in.'));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
client.sendStateEvent(roomId, "m.room.plumbing", { status : status }).done(() => {
|
client.sendStateEvent(roomId, "m.room.plumbing", { status : status }).done(() => {
|
||||||
|
@ -189,7 +190,7 @@ function setPlumbingState(event, roomId, status) {
|
||||||
success: true,
|
success: true,
|
||||||
});
|
});
|
||||||
}, (err) => {
|
}, (err) => {
|
||||||
sendError(event, err.message ? err.message : "Failed to send request.", err);
|
sendError(event, err.message ? err.message : _t('Failed to send request.'), err);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -197,7 +198,7 @@ function setBotOptions(event, roomId, userId) {
|
||||||
console.log(`Received request to set options for bot ${userId} in room ${roomId}`);
|
console.log(`Received request to set options for bot ${userId} in room ${roomId}`);
|
||||||
const client = MatrixClientPeg.get();
|
const client = MatrixClientPeg.get();
|
||||||
if (!client) {
|
if (!client) {
|
||||||
sendError(event, "You need to be logged in.");
|
sendError(event, _t('You need to be logged in.'));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
client.sendStateEvent(roomId, "m.room.bot.options", event.data.content, "_" + userId).done(() => {
|
client.sendStateEvent(roomId, "m.room.bot.options", event.data.content, "_" + userId).done(() => {
|
||||||
|
@ -205,20 +206,20 @@ function setBotOptions(event, roomId, userId) {
|
||||||
success: true,
|
success: true,
|
||||||
});
|
});
|
||||||
}, (err) => {
|
}, (err) => {
|
||||||
sendError(event, err.message ? err.message : "Failed to send request.", err);
|
sendError(event, err.message ? err.message : _t('Failed to send request.'), err);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function setBotPower(event, roomId, userId, level) {
|
function setBotPower(event, roomId, userId, level) {
|
||||||
if (!(Number.isInteger(level) && level >= 0)) {
|
if (!(Number.isInteger(level) && level >= 0)) {
|
||||||
sendError(event, "Power level must be positive integer.");
|
sendError(event, _t('Power level must be positive integer.'));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log(`Received request to set power level to ${level} for bot ${userId} in room ${roomId}.`);
|
console.log(`Received request to set power level to ${level} for bot ${userId} in room ${roomId}.`);
|
||||||
const client = MatrixClientPeg.get();
|
const client = MatrixClientPeg.get();
|
||||||
if (!client) {
|
if (!client) {
|
||||||
sendError(event, "You need to be logged in.");
|
sendError(event, _t('You need to be logged in.'));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -235,7 +236,7 @@ function setBotPower(event, roomId, userId, level) {
|
||||||
success: true,
|
success: true,
|
||||||
});
|
});
|
||||||
}, (err) => {
|
}, (err) => {
|
||||||
sendError(event, err.message ? err.message : "Failed to send request.", err);
|
sendError(event, err.message ? err.message : _t('Failed to send request.'), err);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -258,12 +259,12 @@ function botOptions(event, roomId, userId) {
|
||||||
function returnStateEvent(event, roomId, eventType, stateKey) {
|
function returnStateEvent(event, roomId, eventType, stateKey) {
|
||||||
const client = MatrixClientPeg.get();
|
const client = MatrixClientPeg.get();
|
||||||
if (!client) {
|
if (!client) {
|
||||||
sendError(event, "You need to be logged in.");
|
sendError(event, _t('You need to be logged in.'));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const room = client.getRoom(roomId);
|
const room = client.getRoom(roomId);
|
||||||
if (!room) {
|
if (!room) {
|
||||||
sendError(event, "This room is not recognised.");
|
sendError(event, _t('This room is not recognised.'));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const stateEvent = room.currentState.getStateEvents(eventType, stateKey);
|
const stateEvent = room.currentState.getStateEvents(eventType, stateKey);
|
||||||
|
@ -313,13 +314,13 @@ const onMessage = function(event) {
|
||||||
const roomId = event.data.room_id;
|
const roomId = event.data.room_id;
|
||||||
const userId = event.data.user_id;
|
const userId = event.data.user_id;
|
||||||
if (!roomId) {
|
if (!roomId) {
|
||||||
sendError(event, "Missing room_id in request");
|
sendError(event, _t('Missing room_id in request'));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
let promise = Promise.resolve(currentRoomId);
|
let promise = Promise.resolve(currentRoomId);
|
||||||
if (!currentRoomId) {
|
if (!currentRoomId) {
|
||||||
if (!currentRoomAlias) {
|
if (!currentRoomAlias) {
|
||||||
sendError(event, "Must be viewing a room");
|
sendError(event, _t('Must be viewing a room'));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
// no room ID but there is an alias, look it up.
|
// no room ID but there is an alias, look it up.
|
||||||
|
@ -331,7 +332,7 @@ const onMessage = function(event) {
|
||||||
|
|
||||||
promise.then((viewingRoomId) => {
|
promise.then((viewingRoomId) => {
|
||||||
if (roomId !== viewingRoomId) {
|
if (roomId !== viewingRoomId) {
|
||||||
sendError(event, "Room " + roomId + " not visible");
|
sendError(event, _t('Room %(roomId)s not visible', {roomId: roomId}));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -345,7 +346,7 @@ const onMessage = function(event) {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!userId) {
|
if (!userId) {
|
||||||
sendError(event, "Missing user_id in request");
|
sendError(event, _t('Missing user_id in request'));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
switch (event.data.action) {
|
switch (event.data.action) {
|
||||||
|
@ -370,7 +371,7 @@ const onMessage = function(event) {
|
||||||
}
|
}
|
||||||
}, (err) => {
|
}, (err) => {
|
||||||
console.error(err);
|
console.error(err);
|
||||||
sendError(event, "Failed to lookup current room.");
|
sendError(event, _t('Failed to lookup current room') + '.');
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -23,24 +23,30 @@ class Skinner {
|
||||||
if (this.components === null) {
|
if (this.components === null) {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
"Attempted to get a component before a skin has been loaded."+
|
"Attempted to get a component before a skin has been loaded."+
|
||||||
"This is probably because either:"+
|
" This is probably because either:"+
|
||||||
" a) Your app has not called sdk.loadSkin(), or"+
|
" a) Your app has not called sdk.loadSkin(), or"+
|
||||||
" b) A component has called getComponent at the root level"
|
" b) A component has called getComponent at the root level",
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
var comp = this.components[name];
|
let comp = this.components[name];
|
||||||
if (comp) {
|
|
||||||
return comp;
|
|
||||||
}
|
|
||||||
// XXX: Temporarily also try 'views.' as we're currently
|
// XXX: Temporarily also try 'views.' as we're currently
|
||||||
// leaving the 'views.' off views.
|
// leaving the 'views.' off views.
|
||||||
var comp = this.components['views.'+name];
|
if (!comp) {
|
||||||
if (comp) {
|
comp = this.components['views.'+name];
|
||||||
return comp;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!comp) {
|
||||||
throw new Error("No such component: "+name);
|
throw new Error("No such component: "+name);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// components have to be functions.
|
||||||
|
const validType = typeof comp === 'function';
|
||||||
|
if (!validType) {
|
||||||
|
throw new Error(`Not a valid component: ${name}.`);
|
||||||
|
}
|
||||||
|
return comp;
|
||||||
|
}
|
||||||
|
|
||||||
load(skinObject) {
|
load(skinObject) {
|
||||||
if (this.components !== null) {
|
if (this.components !== null) {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
|
|
|
@ -14,10 +14,11 @@ See the License for the specific language governing permissions and
|
||||||
limitations under the License.
|
limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
var MatrixClientPeg = require("./MatrixClientPeg");
|
import MatrixClientPeg from "./MatrixClientPeg";
|
||||||
var dis = require("./dispatcher");
|
import dis from "./dispatcher";
|
||||||
var Tinter = require("./Tinter");
|
import Tinter from "./Tinter";
|
||||||
import sdk from './index';
|
import sdk from './index';
|
||||||
|
import { _t } from './languageHandler';
|
||||||
import Modal from './Modal';
|
import Modal from './Modal';
|
||||||
|
|
||||||
|
|
||||||
|
@ -41,58 +42,64 @@ class Command {
|
||||||
}
|
}
|
||||||
|
|
||||||
getUsage() {
|
getUsage() {
|
||||||
return "Usage: " + this.getCommandWithArgs();
|
return _t('Usage') + ': ' + this.getCommandWithArgs();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var reject = function(msg) {
|
function reject(msg) {
|
||||||
return {
|
return {
|
||||||
error: msg
|
error: msg,
|
||||||
};
|
};
|
||||||
};
|
}
|
||||||
|
|
||||||
var success = function(promise) {
|
function success(promise) {
|
||||||
return {
|
return {
|
||||||
promise: promise
|
promise: promise,
|
||||||
};
|
};
|
||||||
};
|
}
|
||||||
|
|
||||||
var commands = {
|
/* Disable the "unexpected this" error for these commands - all of the run
|
||||||
|
* functions are called with `this` bound to the Command instance.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/* eslint-disable babel/no-invalid-this */
|
||||||
|
|
||||||
|
const commands = {
|
||||||
ddg: new Command("ddg", "<query>", function(roomId, args) {
|
ddg: new Command("ddg", "<query>", function(roomId, args) {
|
||||||
const ErrorDialog = sdk.getComponent('dialogs.ErrorDialog');
|
const ErrorDialog = sdk.getComponent('dialogs.ErrorDialog');
|
||||||
// TODO Don't explain this away, actually show a search UI here.
|
// TODO Don't explain this away, actually show a search UI here.
|
||||||
Modal.createDialog(ErrorDialog, {
|
Modal.createDialog(ErrorDialog, {
|
||||||
title: "/ddg is not a command",
|
title: _t('/ddg is not a command'),
|
||||||
description: "To use it, just wait for autocomplete results to load and tab through them.",
|
description: _t('To use it, just wait for autocomplete results to load and tab through them.'),
|
||||||
});
|
});
|
||||||
return success();
|
return success();
|
||||||
}),
|
}),
|
||||||
|
|
||||||
// Change your nickname
|
// Change your nickname
|
||||||
nick: new Command("nick", "<display_name>", function(room_id, args) {
|
nick: new Command("nick", "<display_name>", function(roomId, args) {
|
||||||
if (args) {
|
if (args) {
|
||||||
return success(
|
return success(
|
||||||
MatrixClientPeg.get().setDisplayName(args)
|
MatrixClientPeg.get().setDisplayName(args),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
return reject(this.getUsage());
|
return reject(this.getUsage());
|
||||||
}),
|
}),
|
||||||
|
|
||||||
// Changes the colorscheme of your current room
|
// Changes the colorscheme of your current room
|
||||||
tint: new Command("tint", "<color1> [<color2>]", function(room_id, args) {
|
tint: new Command("tint", "<color1> [<color2>]", function(roomId, args) {
|
||||||
if (args) {
|
if (args) {
|
||||||
var matches = args.match(/^(#([0-9a-fA-F]{3}|[0-9a-fA-F]{6}))( +(#([0-9a-fA-F]{3}|[0-9a-fA-F]{6})))?$/);
|
const matches = args.match(/^(#([0-9a-fA-F]{3}|[0-9a-fA-F]{6}))( +(#([0-9a-fA-F]{3}|[0-9a-fA-F]{6})))?$/);
|
||||||
if (matches) {
|
if (matches) {
|
||||||
Tinter.tint(matches[1], matches[4]);
|
Tinter.tint(matches[1], matches[4]);
|
||||||
var colorScheme = {};
|
const colorScheme = {};
|
||||||
colorScheme.primary_color = matches[1];
|
colorScheme.primary_color = matches[1];
|
||||||
if (matches[4]) {
|
if (matches[4]) {
|
||||||
colorScheme.secondary_color = matches[4];
|
colorScheme.secondary_color = matches[4];
|
||||||
}
|
}
|
||||||
return success(
|
return success(
|
||||||
MatrixClientPeg.get().setRoomAccountData(
|
MatrixClientPeg.get().setRoomAccountData(
|
||||||
room_id, "org.matrix.room.color_scheme", colorScheme
|
roomId, "org.matrix.room.color_scheme", colorScheme,
|
||||||
)
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -100,22 +107,22 @@ var commands = {
|
||||||
}),
|
}),
|
||||||
|
|
||||||
// Change the room topic
|
// Change the room topic
|
||||||
topic: new Command("topic", "<topic>", function(room_id, args) {
|
topic: new Command("topic", "<topic>", function(roomId, args) {
|
||||||
if (args) {
|
if (args) {
|
||||||
return success(
|
return success(
|
||||||
MatrixClientPeg.get().setRoomTopic(room_id, args)
|
MatrixClientPeg.get().setRoomTopic(roomId, args),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
return reject(this.getUsage());
|
return reject(this.getUsage());
|
||||||
}),
|
}),
|
||||||
|
|
||||||
// Invite a user
|
// Invite a user
|
||||||
invite: new Command("invite", "<userId>", function(room_id, args) {
|
invite: new Command("invite", "<userId>", function(roomId, args) {
|
||||||
if (args) {
|
if (args) {
|
||||||
var matches = args.match(/^(\S+)$/);
|
const matches = args.match(/^(\S+)$/);
|
||||||
if (matches) {
|
if (matches) {
|
||||||
return success(
|
return success(
|
||||||
MatrixClientPeg.get().invite(room_id, matches[1])
|
MatrixClientPeg.get().invite(roomId, matches[1]),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -123,21 +130,21 @@ var commands = {
|
||||||
}),
|
}),
|
||||||
|
|
||||||
// Join a room
|
// Join a room
|
||||||
join: new Command("join", "#alias:domain", function(room_id, args) {
|
join: new Command("join", "#alias:domain", function(roomId, args) {
|
||||||
if (args) {
|
if (args) {
|
||||||
var matches = args.match(/^(\S+)$/);
|
const matches = args.match(/^(\S+)$/);
|
||||||
if (matches) {
|
if (matches) {
|
||||||
var room_alias = matches[1];
|
let roomAlias = matches[1];
|
||||||
if (room_alias[0] !== '#') {
|
if (roomAlias[0] !== '#') {
|
||||||
return reject(this.getUsage());
|
return reject(this.getUsage());
|
||||||
}
|
}
|
||||||
if (!room_alias.match(/:/)) {
|
if (!roomAlias.match(/:/)) {
|
||||||
room_alias += ':' + MatrixClientPeg.get().getDomain();
|
roomAlias += ':' + MatrixClientPeg.get().getDomain();
|
||||||
}
|
}
|
||||||
|
|
||||||
dis.dispatch({
|
dis.dispatch({
|
||||||
action: 'view_room',
|
action: 'view_room',
|
||||||
room_alias: room_alias,
|
room_alias: roomAlias,
|
||||||
auto_join: true,
|
auto_join: true,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -147,29 +154,29 @@ var commands = {
|
||||||
return reject(this.getUsage());
|
return reject(this.getUsage());
|
||||||
}),
|
}),
|
||||||
|
|
||||||
part: new Command("part", "[#alias:domain]", function(room_id, args) {
|
part: new Command("part", "[#alias:domain]", function(roomId, args) {
|
||||||
var targetRoomId;
|
let targetRoomId;
|
||||||
if (args) {
|
if (args) {
|
||||||
var matches = args.match(/^(\S+)$/);
|
const matches = args.match(/^(\S+)$/);
|
||||||
if (matches) {
|
if (matches) {
|
||||||
var room_alias = matches[1];
|
let roomAlias = matches[1];
|
||||||
if (room_alias[0] !== '#') {
|
if (roomAlias[0] !== '#') {
|
||||||
return reject(this.getUsage());
|
return reject(this.getUsage());
|
||||||
}
|
}
|
||||||
if (!room_alias.match(/:/)) {
|
if (!roomAlias.match(/:/)) {
|
||||||
room_alias += ':' + MatrixClientPeg.get().getDomain();
|
roomAlias += ':' + MatrixClientPeg.get().getDomain();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Try to find a room with this alias
|
// Try to find a room with this alias
|
||||||
var rooms = MatrixClientPeg.get().getRooms();
|
const rooms = MatrixClientPeg.get().getRooms();
|
||||||
for (var i = 0; i < rooms.length; i++) {
|
for (let i = 0; i < rooms.length; i++) {
|
||||||
var aliasEvents = rooms[i].currentState.getStateEvents(
|
const aliasEvents = rooms[i].currentState.getStateEvents(
|
||||||
"m.room.aliases"
|
"m.room.aliases",
|
||||||
);
|
);
|
||||||
for (var j = 0; j < aliasEvents.length; j++) {
|
for (let j = 0; j < aliasEvents.length; j++) {
|
||||||
var aliases = aliasEvents[j].getContent().aliases || [];
|
const aliases = aliasEvents[j].getContent().aliases || [];
|
||||||
for (var k = 0; k < aliases.length; k++) {
|
for (let k = 0; k < aliases.length; k++) {
|
||||||
if (aliases[k] === room_alias) {
|
if (aliases[k] === roomAlias) {
|
||||||
targetRoomId = rooms[i].roomId;
|
targetRoomId = rooms[i].roomId;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
@ -178,27 +185,28 @@ var commands = {
|
||||||
}
|
}
|
||||||
if (targetRoomId) { break; }
|
if (targetRoomId) { break; }
|
||||||
}
|
}
|
||||||
}
|
|
||||||
if (!targetRoomId) {
|
if (!targetRoomId) {
|
||||||
return reject("Unrecognised room alias: " + room_alias);
|
return reject(_t("Unrecognised room alias:") + ' ' + roomAlias);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (!targetRoomId) targetRoomId = room_id;
|
}
|
||||||
|
if (!targetRoomId) targetRoomId = roomId;
|
||||||
return success(
|
return success(
|
||||||
MatrixClientPeg.get().leave(targetRoomId).then(
|
MatrixClientPeg.get().leave(targetRoomId).then(
|
||||||
function() {
|
function() {
|
||||||
dis.dispatch({action: 'view_next_room'});
|
dis.dispatch({action: 'view_next_room'});
|
||||||
})
|
},
|
||||||
|
),
|
||||||
);
|
);
|
||||||
}),
|
}),
|
||||||
|
|
||||||
// Kick a user from the room with an optional reason
|
// Kick a user from the room with an optional reason
|
||||||
kick: new Command("kick", "<userId> [<reason>]", function(room_id, args) {
|
kick: new Command("kick", "<userId> [<reason>]", function(roomId, args) {
|
||||||
if (args) {
|
if (args) {
|
||||||
var matches = args.match(/^(\S+?)( +(.*))?$/);
|
const matches = args.match(/^(\S+?)( +(.*))?$/);
|
||||||
if (matches) {
|
if (matches) {
|
||||||
return success(
|
return success(
|
||||||
MatrixClientPeg.get().kick(room_id, matches[1], matches[3])
|
MatrixClientPeg.get().kick(roomId, matches[1], matches[3]),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -206,12 +214,12 @@ var commands = {
|
||||||
}),
|
}),
|
||||||
|
|
||||||
// Ban a user from the room with an optional reason
|
// Ban a user from the room with an optional reason
|
||||||
ban: new Command("ban", "<userId> [<reason>]", function(room_id, args) {
|
ban: new Command("ban", "<userId> [<reason>]", function(roomId, args) {
|
||||||
if (args) {
|
if (args) {
|
||||||
var matches = args.match(/^(\S+?)( +(.*))?$/);
|
const matches = args.match(/^(\S+?)( +(.*))?$/);
|
||||||
if (matches) {
|
if (matches) {
|
||||||
return success(
|
return success(
|
||||||
MatrixClientPeg.get().ban(room_id, matches[1], matches[3])
|
MatrixClientPeg.get().ban(roomId, matches[1], matches[3]),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -219,13 +227,13 @@ var commands = {
|
||||||
}),
|
}),
|
||||||
|
|
||||||
// Unban a user from the room
|
// Unban a user from the room
|
||||||
unban: new Command("unban", "<userId>", function(room_id, args) {
|
unban: new Command("unban", "<userId>", function(roomId, args) {
|
||||||
if (args) {
|
if (args) {
|
||||||
var matches = args.match(/^(\S+)$/);
|
const matches = args.match(/^(\S+)$/);
|
||||||
if (matches) {
|
if (matches) {
|
||||||
// Reset the user membership to "leave" to unban him
|
// Reset the user membership to "leave" to unban him
|
||||||
return success(
|
return success(
|
||||||
MatrixClientPeg.get().unban(room_id, matches[1])
|
MatrixClientPeg.get().unban(roomId, matches[1]),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -233,27 +241,27 @@ var commands = {
|
||||||
}),
|
}),
|
||||||
|
|
||||||
// Define the power level of a user
|
// Define the power level of a user
|
||||||
op: new Command("op", "<userId> [<power level>]", function(room_id, args) {
|
op: new Command("op", "<userId> [<power level>]", function(roomId, args) {
|
||||||
if (args) {
|
if (args) {
|
||||||
var matches = args.match(/^(\S+?)( +(\d+))?$/);
|
const matches = args.match(/^(\S+?)( +(\d+))?$/);
|
||||||
var powerLevel = 50; // default power level for op
|
let powerLevel = 50; // default power level for op
|
||||||
if (matches) {
|
if (matches) {
|
||||||
var user_id = matches[1];
|
const userId = matches[1];
|
||||||
if (matches.length === 4 && undefined !== matches[3]) {
|
if (matches.length === 4 && undefined !== matches[3]) {
|
||||||
powerLevel = parseInt(matches[3]);
|
powerLevel = parseInt(matches[3]);
|
||||||
}
|
}
|
||||||
if (powerLevel !== NaN) {
|
if (!isNaN(powerLevel)) {
|
||||||
var room = MatrixClientPeg.get().getRoom(room_id);
|
const room = MatrixClientPeg.get().getRoom(roomId);
|
||||||
if (!room) {
|
if (!room) {
|
||||||
return reject("Bad room ID: " + room_id);
|
return reject("Bad room ID: " + roomId);
|
||||||
}
|
}
|
||||||
var powerLevelEvent = room.currentState.getStateEvents(
|
const powerLevelEvent = room.currentState.getStateEvents(
|
||||||
"m.room.power_levels", ""
|
"m.room.power_levels", "",
|
||||||
);
|
);
|
||||||
return success(
|
return success(
|
||||||
MatrixClientPeg.get().setPowerLevel(
|
MatrixClientPeg.get().setPowerLevel(
|
||||||
room_id, user_id, powerLevel, powerLevelEvent
|
roomId, userId, powerLevel, powerLevelEvent,
|
||||||
)
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -262,32 +270,94 @@ var commands = {
|
||||||
}),
|
}),
|
||||||
|
|
||||||
// Reset the power level of a user
|
// Reset the power level of a user
|
||||||
deop: new Command("deop", "<userId>", function(room_id, args) {
|
deop: new Command("deop", "<userId>", function(roomId, args) {
|
||||||
if (args) {
|
if (args) {
|
||||||
var matches = args.match(/^(\S+)$/);
|
const matches = args.match(/^(\S+)$/);
|
||||||
if (matches) {
|
if (matches) {
|
||||||
var room = MatrixClientPeg.get().getRoom(room_id);
|
const room = MatrixClientPeg.get().getRoom(roomId);
|
||||||
if (!room) {
|
if (!room) {
|
||||||
return reject("Bad room ID: " + room_id);
|
return reject("Bad room ID: " + roomId);
|
||||||
}
|
}
|
||||||
|
|
||||||
var powerLevelEvent = room.currentState.getStateEvents(
|
const powerLevelEvent = room.currentState.getStateEvents(
|
||||||
"m.room.power_levels", ""
|
"m.room.power_levels", "",
|
||||||
);
|
);
|
||||||
return success(
|
return success(
|
||||||
MatrixClientPeg.get().setPowerLevel(
|
MatrixClientPeg.get().setPowerLevel(
|
||||||
room_id, args, undefined, powerLevelEvent
|
roomId, args, undefined, powerLevelEvent,
|
||||||
)
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return reject(this.getUsage());
|
return reject(this.getUsage());
|
||||||
})
|
}),
|
||||||
|
|
||||||
|
// Verify a user, device, and pubkey tuple
|
||||||
|
verify: new Command("verify", "<userId> <deviceId> <deviceSigningKey>", function(roomId, args) {
|
||||||
|
if (args) {
|
||||||
|
const matches = args.match(/^(\S+) +(\S+) +(\S+)$/);
|
||||||
|
if (matches) {
|
||||||
|
const userId = matches[1];
|
||||||
|
const deviceId = matches[2];
|
||||||
|
const fingerprint = matches[3];
|
||||||
|
|
||||||
|
const device = MatrixClientPeg.get().getStoredDevice(userId, deviceId);
|
||||||
|
if (!device) {
|
||||||
|
return reject(_t(`Unknown (user, device) pair:`) + ` (${userId}, ${deviceId})`);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (device.isVerified()) {
|
||||||
|
if (device.getFingerprint() === fingerprint) {
|
||||||
|
return reject(_t(`Device already verified!`));
|
||||||
|
} else {
|
||||||
|
return reject(_t(`WARNING: Device already verified, but keys do NOT MATCH!`));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (device.getFingerprint() === fingerprint) {
|
||||||
|
MatrixClientPeg.get().setDeviceVerified(
|
||||||
|
userId, deviceId, true,
|
||||||
|
);
|
||||||
|
|
||||||
|
// Tell the user we verified everything!
|
||||||
|
const QuestionDialog = sdk.getComponent("dialogs.QuestionDialog");
|
||||||
|
Modal.createDialog(QuestionDialog, {
|
||||||
|
title: _t("Verified key"),
|
||||||
|
description: (
|
||||||
|
<div>
|
||||||
|
<p>
|
||||||
|
{
|
||||||
|
_t("The signing key you provided matches the signing key you received " +
|
||||||
|
"from %(userId)s's device %(deviceId)s. Device marked as verified.",
|
||||||
|
{userId: userId, deviceId: deviceId})
|
||||||
|
}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
),
|
||||||
|
hasCancelButton: false,
|
||||||
|
});
|
||||||
|
|
||||||
|
return success();
|
||||||
|
} else {
|
||||||
|
const fprint = device.getFingerprint();
|
||||||
|
return reject(
|
||||||
|
_t('WARNING: KEY VERIFICATION FAILED! The signing key for %(userId)s and device' +
|
||||||
|
' %(deviceId)s is "%(fprint)s" which does not match the provided key' +
|
||||||
|
' "%(fingerprint)s". This could mean your communications are being intercepted!',
|
||||||
|
{deviceId: deviceId, fprint: fprint, userId: userId, fingerprint: fingerprint})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return reject(this.getUsage());
|
||||||
|
}),
|
||||||
};
|
};
|
||||||
|
/* eslint-enable babel/no-invalid-this */
|
||||||
|
|
||||||
|
|
||||||
// helpful aliases
|
// helpful aliases
|
||||||
var aliases = {
|
const aliases = {
|
||||||
j: "join"
|
j: "join",
|
||||||
};
|
};
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
|
@ -304,13 +374,13 @@ module.exports = {
|
||||||
// IRC-style commands
|
// IRC-style commands
|
||||||
input = input.replace(/\s+$/, "");
|
input = input.replace(/\s+$/, "");
|
||||||
if (input[0] === "/" && input[1] !== "/") {
|
if (input[0] === "/" && input[1] !== "/") {
|
||||||
var bits = input.match(/^(\S+?)( +((.|\n)*))?$/);
|
const bits = input.match(/^(\S+?)( +((.|\n)*))?$/);
|
||||||
var cmd, args;
|
let cmd;
|
||||||
|
let args;
|
||||||
if (bits) {
|
if (bits) {
|
||||||
cmd = bits[1].substring(1).toLowerCase();
|
cmd = bits[1].substring(1).toLowerCase();
|
||||||
args = bits[3];
|
args = bits[3];
|
||||||
}
|
} else {
|
||||||
else {
|
|
||||||
cmd = input;
|
cmd = input;
|
||||||
}
|
}
|
||||||
if (cmd === "me") return null;
|
if (cmd === "me") return null;
|
||||||
|
@ -319,9 +389,8 @@ module.exports = {
|
||||||
}
|
}
|
||||||
if (commands[cmd]) {
|
if (commands[cmd]) {
|
||||||
return commands[cmd].run(roomId, args);
|
return commands[cmd].run(roomId, args);
|
||||||
}
|
} else {
|
||||||
else {
|
return reject(_t("Unrecognised command:") + ' ' + input);
|
||||||
return reject("Unrecognised command: " + input);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return null; // not a command
|
return null; // not a command
|
||||||
|
@ -329,12 +398,12 @@ module.exports = {
|
||||||
|
|
||||||
getCommandList: function() {
|
getCommandList: function() {
|
||||||
// Return all the commands plus /me and /markdown which aren't handled like normal commands
|
// Return all the commands plus /me and /markdown which aren't handled like normal commands
|
||||||
var cmds = Object.keys(commands).sort().map(function(cmdKey) {
|
const cmds = Object.keys(commands).sort().map(function(cmdKey) {
|
||||||
return commands[cmdKey];
|
return commands[cmdKey];
|
||||||
});
|
});
|
||||||
cmds.push(new Command("me", "<action>", function() {}));
|
cmds.push(new Command("me", "<action>", function() {}));
|
||||||
cmds.push(new Command("markdown", "<on|off>", function() {}));
|
cmds.push(new Command("markdown", "<on|off>", function() {}));
|
||||||
|
|
||||||
return cmds;
|
return cmds;
|
||||||
}
|
},
|
||||||
};
|
};
|
||||||
|
|
|
@ -16,6 +16,8 @@ limitations under the License.
|
||||||
|
|
||||||
var MatrixClientPeg = require("./MatrixClientPeg");
|
var MatrixClientPeg = require("./MatrixClientPeg");
|
||||||
var CallHandler = require("./CallHandler");
|
var CallHandler = require("./CallHandler");
|
||||||
|
import { _t } from './languageHandler';
|
||||||
|
import * as Roles from './Roles';
|
||||||
|
|
||||||
function textForMemberEvent(ev) {
|
function textForMemberEvent(ev) {
|
||||||
// XXX: SYJS-16 "sender is sometimes null for join messages"
|
// XXX: SYJS-16 "sender is sometimes null for join messages"
|
||||||
|
@ -23,95 +25,103 @@ function textForMemberEvent(ev) {
|
||||||
var targetName = ev.target ? ev.target.name : ev.getStateKey();
|
var targetName = ev.target ? ev.target.name : ev.getStateKey();
|
||||||
var ConferenceHandler = CallHandler.getConferenceHandler();
|
var ConferenceHandler = CallHandler.getConferenceHandler();
|
||||||
var reason = ev.getContent().reason ? (
|
var reason = ev.getContent().reason ? (
|
||||||
" Reason: " + ev.getContent().reason
|
_t('Reason') + ': ' + ev.getContent().reason
|
||||||
) : "";
|
) : "";
|
||||||
switch (ev.getContent().membership) {
|
switch (ev.getContent().membership) {
|
||||||
case 'invite':
|
case 'invite':
|
||||||
var threePidContent = ev.getContent().third_party_invite;
|
var threePidContent = ev.getContent().third_party_invite;
|
||||||
if (threePidContent) {
|
if (threePidContent) {
|
||||||
if (threePidContent.display_name) {
|
if (threePidContent.display_name) {
|
||||||
return targetName + " accepted the invitation for " +
|
return _t('%(targetName)s accepted the invitation for %(displayName)s.', {targetName: targetName, displayName: threePidContent.display_name});
|
||||||
threePidContent.display_name + ".";
|
|
||||||
} else {
|
} else {
|
||||||
return targetName + " accepted an invitation.";
|
return _t('%(targetName)s accepted an invitation.', {targetName: targetName});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
if (ConferenceHandler && ConferenceHandler.isConferenceUser(ev.getStateKey())) {
|
if (ConferenceHandler && ConferenceHandler.isConferenceUser(ev.getStateKey())) {
|
||||||
return senderName + " requested a VoIP conference";
|
return _t('%(senderName)s requested a VoIP conference.', {senderName: senderName});
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
return senderName + " invited " + targetName + ".";
|
return _t('%(senderName)s invited %(targetName)s.', {senderName: senderName, targetName: targetName});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
case 'ban':
|
case 'ban':
|
||||||
return senderName + " banned " + targetName + "." + reason;
|
return _t(
|
||||||
|
'%(senderName)s banned %(targetName)s.',
|
||||||
|
{senderName: senderName, targetName: targetName}
|
||||||
|
) + ' ' + reason;
|
||||||
case 'join':
|
case 'join':
|
||||||
if (ev.getPrevContent() && ev.getPrevContent().membership == 'join') {
|
if (ev.getPrevContent() && ev.getPrevContent().membership == 'join') {
|
||||||
if (ev.getPrevContent().displayname && ev.getContent().displayname && ev.getPrevContent().displayname != ev.getContent().displayname) {
|
if (ev.getPrevContent().displayname && ev.getContent().displayname && ev.getPrevContent().displayname != ev.getContent().displayname) {
|
||||||
return ev.getSender() + " changed their display name from " +
|
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});
|
||||||
ev.getPrevContent().displayname + " to " +
|
|
||||||
ev.getContent().displayname;
|
|
||||||
} else if (!ev.getPrevContent().displayname && ev.getContent().displayname) {
|
} else if (!ev.getPrevContent().displayname && ev.getContent().displayname) {
|
||||||
return ev.getSender() + " set their display name to " + 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) {
|
} else if (ev.getPrevContent().displayname && !ev.getContent().displayname) {
|
||||||
return ev.getSender() + " removed their display name (" + ev.getPrevContent().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) {
|
} else if (ev.getPrevContent().avatar_url && !ev.getContent().avatar_url) {
|
||||||
return senderName + " removed their profile picture";
|
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) {
|
} else if (ev.getPrevContent().avatar_url && ev.getContent().avatar_url && ev.getPrevContent().avatar_url != ev.getContent().avatar_url) {
|
||||||
return senderName + " changed their profile picture";
|
return _t('%(senderName)s changed their profile picture.', {senderName: senderName});
|
||||||
} else if (!ev.getPrevContent().avatar_url && ev.getContent().avatar_url) {
|
} else if (!ev.getPrevContent().avatar_url && ev.getContent().avatar_url) {
|
||||||
return senderName + " set a profile picture";
|
return _t('%(senderName)s set a profile picture.', {senderName: senderName});
|
||||||
} else {
|
} else {
|
||||||
// hacky hack for https://github.com/vector-im/vector-web/issues/2020
|
// suppress null rejoins
|
||||||
return senderName + " rejoined the room.";
|
return '';
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if (!ev.target) console.warn("Join message has no target! -- " + ev.getContent().state_key);
|
if (!ev.target) console.warn("Join message has no target! -- " + ev.getContent().state_key);
|
||||||
if (ConferenceHandler && ConferenceHandler.isConferenceUser(ev.getStateKey())) {
|
if (ConferenceHandler && ConferenceHandler.isConferenceUser(ev.getStateKey())) {
|
||||||
return "VoIP conference started";
|
return _t('VoIP conference started.');
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
return targetName + " joined the room.";
|
return _t('%(targetName)s joined the room.', {targetName: targetName});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
case 'leave':
|
case 'leave':
|
||||||
if (ev.getSender() === ev.getStateKey()) {
|
if (ev.getSender() === ev.getStateKey()) {
|
||||||
if (ConferenceHandler && ConferenceHandler.isConferenceUser(ev.getStateKey())) {
|
if (ConferenceHandler && ConferenceHandler.isConferenceUser(ev.getStateKey())) {
|
||||||
return "VoIP conference finished";
|
return _t('VoIP conference finished.');
|
||||||
}
|
}
|
||||||
else if (ev.getPrevContent().membership === "invite") {
|
else if (ev.getPrevContent().membership === "invite") {
|
||||||
return targetName + " rejected the invitation.";
|
return _t('%(targetName)s rejected the invitation.', {targetName: targetName});
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
return targetName + " left the room.";
|
return _t('%(targetName)s left the room.', {targetName: targetName});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if (ev.getPrevContent().membership === "ban") {
|
else if (ev.getPrevContent().membership === "ban") {
|
||||||
return senderName + " unbanned " + targetName + ".";
|
return _t('%(senderName)s unbanned %(targetName)s.', {senderName: senderName, targetName: targetName});
|
||||||
}
|
}
|
||||||
else if (ev.getPrevContent().membership === "join") {
|
else if (ev.getPrevContent().membership === "join") {
|
||||||
return senderName + " kicked " + targetName + "." + reason;
|
return _t(
|
||||||
|
'%(senderName)s kicked %(targetName)s.',
|
||||||
|
{senderName: senderName, targetName: targetName}
|
||||||
|
) + ' ' + reason;
|
||||||
}
|
}
|
||||||
else if (ev.getPrevContent().membership === "invite") {
|
else if (ev.getPrevContent().membership === "invite") {
|
||||||
return senderName + " withdrew " + targetName + "'s invitation." + reason;
|
return _t(
|
||||||
|
'%(senderName)s withdrew %(targetName)s\'s invitation.',
|
||||||
|
{senderName: senderName, targetName: targetName}
|
||||||
|
) + ' ' + reason;
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
return targetName + " left the room.";
|
return _t('%(targetName)s left the room.', {targetName: targetName});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function textForTopicEvent(ev) {
|
function textForTopicEvent(ev) {
|
||||||
var senderDisplayName = ev.sender && ev.sender.name ? ev.sender.name : ev.getSender();
|
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});
|
||||||
return senderDisplayName + ' changed the topic to "' + ev.getContent().topic + '"';
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function textForRoomNameEvent(ev) {
|
function textForRoomNameEvent(ev) {
|
||||||
var senderDisplayName = ev.sender && ev.sender.name ? ev.sender.name : ev.getSender();
|
var senderDisplayName = ev.sender && ev.sender.name ? ev.sender.name : ev.getSender();
|
||||||
|
|
||||||
return senderDisplayName + ' changed the room name to "' + ev.getContent().name + '"';
|
if (!ev.getContent().name || ev.getContent().name.trim().length === 0) {
|
||||||
|
return _t('%(senderDisplayName)s removed the room name.', {senderDisplayName: senderDisplayName});
|
||||||
|
}
|
||||||
|
return _t('%(senderDisplayName)s changed the room name to %(roomName)s.', {senderDisplayName: senderDisplayName, roomName: ev.getContent().name});
|
||||||
}
|
}
|
||||||
|
|
||||||
function textForMessageEvent(ev) {
|
function textForMessageEvent(ev) {
|
||||||
|
@ -120,66 +130,111 @@ function textForMessageEvent(ev) {
|
||||||
if (ev.getContent().msgtype === "m.emote") {
|
if (ev.getContent().msgtype === "m.emote") {
|
||||||
message = "* " + senderDisplayName + " " + message;
|
message = "* " + senderDisplayName + " " + message;
|
||||||
} else if (ev.getContent().msgtype === "m.image") {
|
} else if (ev.getContent().msgtype === "m.image") {
|
||||||
message = senderDisplayName + " sent an image.";
|
message = _t('%(senderDisplayName)s sent an image.', {senderDisplayName: senderDisplayName});
|
||||||
}
|
}
|
||||||
return message;
|
return message;
|
||||||
}
|
}
|
||||||
|
|
||||||
function textForCallAnswerEvent(event) {
|
function textForCallAnswerEvent(event) {
|
||||||
var senderName = event.sender ? event.sender.name : "Someone";
|
var senderName = event.sender ? event.sender.name : _t('Someone');
|
||||||
var supported = MatrixClientPeg.get().supportsVoip() ? "" : " (not supported by this browser)";
|
var supported = MatrixClientPeg.get().supportsVoip() ? "" : _t('(not supported by this browser)');
|
||||||
return senderName + " answered the call." + supported;
|
return _t('%(senderName)s answered the call.', {senderName: senderName}) + ' ' + supported;
|
||||||
}
|
}
|
||||||
|
|
||||||
function textForCallHangupEvent(event) {
|
function textForCallHangupEvent(event) {
|
||||||
var senderName = event.sender ? event.sender.name : "Someone";
|
var senderName = event.sender ? event.sender.name : _t('Someone');
|
||||||
var supported = MatrixClientPeg.get().supportsVoip() ? "" : " (not supported by this browser)";
|
var supported = MatrixClientPeg.get().supportsVoip() ? "" : _t('(not supported by this browser)');
|
||||||
return senderName + " ended the call." + supported;
|
return _t('%(senderName)s ended the call.', {senderName: senderName}) + ' ' + supported;
|
||||||
}
|
}
|
||||||
|
|
||||||
function textForCallInviteEvent(event) {
|
function textForCallInviteEvent(event) {
|
||||||
var senderName = event.sender ? event.sender.name : "Someone";
|
var senderName = event.sender ? event.sender.name : _t('Someone');
|
||||||
// FIXME: Find a better way to determine this from the event?
|
// FIXME: Find a better way to determine this from the event?
|
||||||
var type = "voice";
|
var type = "voice";
|
||||||
if (event.getContent().offer && event.getContent().offer.sdp &&
|
if (event.getContent().offer && event.getContent().offer.sdp &&
|
||||||
event.getContent().offer.sdp.indexOf('m=video') !== -1) {
|
event.getContent().offer.sdp.indexOf('m=video') !== -1) {
|
||||||
type = "video";
|
type = "video";
|
||||||
}
|
}
|
||||||
var supported = MatrixClientPeg.get().supportsVoip() ? "" : " (not supported by this browser)";
|
var supported = MatrixClientPeg.get().supportsVoip() ? "" : _t('(not supported by this browser)');
|
||||||
return senderName + " placed a " + type + " call." + supported;
|
return _t('%(senderName)s placed a %(callType)s call.', {senderName: senderName, callType: type}) + ' ' + supported;
|
||||||
}
|
}
|
||||||
|
|
||||||
function textForThreePidInviteEvent(event) {
|
function textForThreePidInviteEvent(event) {
|
||||||
var senderName = event.sender ? event.sender.name : event.getSender();
|
var senderName = event.sender ? event.sender.name : event.getSender();
|
||||||
return senderName + " sent an invitation to " + event.getContent().display_name +
|
return _t('%(senderName)s sent an invitation to %(targetDisplayName)s to join the room.', {senderName: senderName, targetDisplayName: event.getContent().display_name});
|
||||||
" to join the room.";
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function textForHistoryVisibilityEvent(event) {
|
function textForHistoryVisibilityEvent(event) {
|
||||||
var senderName = event.sender ? event.sender.name : event.getSender();
|
var senderName = event.sender ? event.sender.name : event.getSender();
|
||||||
var vis = event.getContent().history_visibility;
|
var vis = event.getContent().history_visibility;
|
||||||
var text = senderName + " made future room history visible to ";
|
// 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") {
|
if (vis === "invited") {
|
||||||
text += "all room members, from the point they are invited.";
|
text += _t('all room members, from the point they are invited') + '.';
|
||||||
}
|
}
|
||||||
else if (vis === "joined") {
|
else if (vis === "joined") {
|
||||||
text += "all room members, from the point they joined.";
|
text += _t('all room members, from the point they joined') + '.';
|
||||||
}
|
}
|
||||||
else if (vis === "shared") {
|
else if (vis === "shared") {
|
||||||
text += "all room members.";
|
text += _t('all room members') + '.';
|
||||||
}
|
}
|
||||||
else if (vis === "world_readable") {
|
else if (vis === "world_readable") {
|
||||||
text += "anyone.";
|
text += _t('anyone') + '.';
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
text += " unknown (" + vis + ")";
|
text += ' ' + _t('unknown') + ' (' + vis + ').';
|
||||||
}
|
}
|
||||||
return text;
|
return text;
|
||||||
}
|
}
|
||||||
|
|
||||||
function textForEncryptionEvent(event) {
|
function textForEncryptionEvent(event) {
|
||||||
var senderName = event.sender ? event.sender.name : event.getSender();
|
var senderName = event.sender ? event.sender.name : event.getSender();
|
||||||
return senderName + " turned on end-to-end encryption (algorithm " + event.getContent().algorithm + ")";
|
return _t('%(senderName)s turned on end-to-end encryption (algorithm %(algorithm)s).', {senderName: senderName, algorithm: event.getContent().algorithm});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Currently will only display a change if a user's power level is changed
|
||||||
|
function textForPowerEvent(event) {
|
||||||
|
const senderName = event.sender ? event.sender.name : event.getSender();
|
||||||
|
if (!event.getPrevContent() || !event.getPrevContent().users) {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
const userDefault = event.getContent().users_default || 0;
|
||||||
|
// Construct set of userIds
|
||||||
|
let 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 = [];
|
||||||
|
// XXX: This is also surely broken for i18n
|
||||||
|
users.forEach((userId) => {
|
||||||
|
// Previous power level
|
||||||
|
const from = event.getPrevContent().users[userId];
|
||||||
|
// Current power level
|
||||||
|
const to = event.getContent().users[userId];
|
||||||
|
if (to !== from) {
|
||||||
|
diff.push(
|
||||||
|
_t('%(userId)s from %(fromPowerLevel)s to %(toPowerLevel)s', {
|
||||||
|
userId: userId,
|
||||||
|
fromPowerLevel: Roles.textualPowerLevel(from, userDefault),
|
||||||
|
toPowerLevel: Roles.textualPowerLevel(to, userDefault)
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
if (!diff.length) {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
return _t('%(senderName)s changed the power level of %(powerLevelDiffText)s.', {
|
||||||
|
senderName: senderName,
|
||||||
|
powerLevelDiffText: diff.join(", ")
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
var handlers = {
|
var handlers = {
|
||||||
|
@ -193,6 +248,7 @@ var handlers = {
|
||||||
'm.room.third_party_invite': textForThreePidInviteEvent,
|
'm.room.third_party_invite': textForThreePidInviteEvent,
|
||||||
'm.room.history_visibility': textForHistoryVisibilityEvent,
|
'm.room.history_visibility': textForHistoryVisibilityEvent,
|
||||||
'm.room.encryption': textForEncryptionEvent,
|
'm.room.encryption': textForEncryptionEvent,
|
||||||
|
'm.room.power_levels': textForPowerEvent,
|
||||||
};
|
};
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
|
|
|
@ -22,7 +22,7 @@ let isDialogOpen = false;
|
||||||
|
|
||||||
const onAction = function(payload) {
|
const onAction = function(payload) {
|
||||||
if (payload.action === 'unknown_device_error' && !isDialogOpen) {
|
if (payload.action === 'unknown_device_error' && !isDialogOpen) {
|
||||||
var UnknownDeviceDialog = sdk.getComponent("dialogs.UnknownDeviceDialog");
|
const UnknownDeviceDialog = sdk.getComponent('dialogs.UnknownDeviceDialog');
|
||||||
isDialogOpen = true;
|
isDialogOpen = true;
|
||||||
Modal.createDialog(UnknownDeviceDialog, {
|
Modal.createDialog(UnknownDeviceDialog, {
|
||||||
devices: payload.err.devices,
|
devices: payload.err.devices,
|
||||||
|
@ -33,17 +33,17 @@ const onAction = function(payload) {
|
||||||
// https://github.com/vector-im/riot-web/issues/3148
|
// https://github.com/vector-im/riot-web/issues/3148
|
||||||
console.log('UnknownDeviceDialog closed with '+r);
|
console.log('UnknownDeviceDialog closed with '+r);
|
||||||
},
|
},
|
||||||
}, "mx_Dialog_unknownDevice");
|
}, 'mx_Dialog_unknownDevice');
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
|
|
||||||
let ref = null;
|
let ref = null;
|
||||||
|
|
||||||
export function startListening () {
|
export function startListening() {
|
||||||
ref = dis.register(onAction);
|
ref = dis.register(onAction);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function stopListening () {
|
export function stopListening() {
|
||||||
if (ref) {
|
if (ref) {
|
||||||
dis.unregister(ref);
|
dis.unregister(ref);
|
||||||
ref = null;
|
ref = null;
|
||||||
|
|
|
@ -25,7 +25,9 @@ module.exports = {
|
||||||
eventTriggersUnreadCount: function(ev) {
|
eventTriggersUnreadCount: function(ev) {
|
||||||
if (ev.sender && ev.sender.userId == MatrixClientPeg.get().credentials.userId) {
|
if (ev.sender && ev.sender.userId == MatrixClientPeg.get().credentials.userId) {
|
||||||
return false;
|
return false;
|
||||||
} else if (ev.getType() == "m.room.member") {
|
} else if (ev.getType() == 'm.room.member') {
|
||||||
|
return false;
|
||||||
|
} else if (ev.getType() == 'm.call.answer' || ev.getType() == 'm.call.hangup') {
|
||||||
return false;
|
return false;
|
||||||
} else if (ev.getType == 'm.room.message' && ev.getContent().msgtype == 'm.notify') {
|
} else if (ev.getType == 'm.room.message' && ev.getContent().msgtype == 'm.notify') {
|
||||||
return false;
|
return false;
|
||||||
|
|
|
@ -32,7 +32,7 @@ class UserActivity {
|
||||||
start() {
|
start() {
|
||||||
document.onmousedown = this._onUserActivity.bind(this);
|
document.onmousedown = this._onUserActivity.bind(this);
|
||||||
document.onmousemove = this._onUserActivity.bind(this);
|
document.onmousemove = this._onUserActivity.bind(this);
|
||||||
document.onkeypress = this._onUserActivity.bind(this);
|
document.onkeydown = this._onUserActivity.bind(this);
|
||||||
// can't use document.scroll here because that's only the document
|
// can't use document.scroll here because that's only the document
|
||||||
// itself being scrolled. Need to use addEventListener's useCapture.
|
// itself being scrolled. Need to use addEventListener's useCapture.
|
||||||
// also this needs to be the wheel event, not scroll, as scroll is
|
// also this needs to be the wheel event, not scroll, as scroll is
|
||||||
|
@ -50,7 +50,7 @@ class UserActivity {
|
||||||
stop() {
|
stop() {
|
||||||
document.onmousedown = undefined;
|
document.onmousedown = undefined;
|
||||||
document.onmousemove = undefined;
|
document.onmousemove = undefined;
|
||||||
document.onkeypress = undefined;
|
document.onkeydown = undefined;
|
||||||
window.removeEventListener('wheel', this._onUserActivity.bind(this),
|
window.removeEventListener('wheel', this._onUserActivity.bind(this),
|
||||||
{ passive: true, capture: true });
|
{ passive: true, capture: true });
|
||||||
}
|
}
|
||||||
|
|
|
@ -14,26 +14,31 @@ See the License for the specific language governing permissions and
|
||||||
limitations under the License.
|
limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
'use strict';
|
import q from 'q';
|
||||||
var q = require("q");
|
import MatrixClientPeg from './MatrixClientPeg';
|
||||||
var MatrixClientPeg = require("./MatrixClientPeg");
|
import Notifier from './Notifier';
|
||||||
var Notifier = require("./Notifier");
|
import { _t } from './languageHandler';
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* TODO: Make things use this. This is all WIP - see UserSettings.js for usage.
|
* TODO: Make things use this. This is all WIP - see UserSettings.js for usage.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
module.exports = {
|
export default {
|
||||||
LABS_FEATURES: [
|
LABS_FEATURES: [
|
||||||
{
|
{
|
||||||
name: 'New Composer & Autocomplete',
|
name: "-",
|
||||||
id: 'rich_text_editor',
|
id: 'rich_text_editor',
|
||||||
default: false,
|
default: false,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
|
||||||
|
// horrible but it works. The locality makes this somewhat more palatable.
|
||||||
|
doTranslations: function() {
|
||||||
|
this.LABS_FEATURES[0].name = _t("New Composer & Autocomplete");
|
||||||
|
},
|
||||||
|
|
||||||
loadProfileInfo: function() {
|
loadProfileInfo: function() {
|
||||||
var cli = MatrixClientPeg.get();
|
const cli = MatrixClientPeg.get();
|
||||||
return cli.getProfileInfo(cli.credentials.userId);
|
return cli.getProfileInfo(cli.credentials.userId);
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -44,7 +49,7 @@ module.exports = {
|
||||||
loadThreePids: function() {
|
loadThreePids: function() {
|
||||||
if (MatrixClientPeg.get().isGuest()) {
|
if (MatrixClientPeg.get().isGuest()) {
|
||||||
return q({
|
return q({
|
||||||
threepids: []
|
threepids: [],
|
||||||
}); // guests can't poke 3pid endpoint
|
}); // guests can't poke 3pid endpoint
|
||||||
}
|
}
|
||||||
return MatrixClientPeg.get().getThreePids();
|
return MatrixClientPeg.get().getThreePids();
|
||||||
|
@ -73,19 +78,19 @@ module.exports = {
|
||||||
Notifier.setAudioEnabled(enable);
|
Notifier.setAudioEnabled(enable);
|
||||||
},
|
},
|
||||||
|
|
||||||
changePassword: function(old_password, new_password) {
|
changePassword: function(oldPassword, newPassword) {
|
||||||
var cli = MatrixClientPeg.get();
|
const cli = MatrixClientPeg.get();
|
||||||
|
|
||||||
var authDict = {
|
const authDict = {
|
||||||
type: 'm.login.password',
|
type: 'm.login.password',
|
||||||
user: cli.credentials.userId,
|
user: cli.credentials.userId,
|
||||||
password: old_password
|
password: oldPassword,
|
||||||
};
|
};
|
||||||
|
|
||||||
return cli.setPassword(authDict, new_password);
|
return cli.setPassword(authDict, newPassword);
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/*
|
||||||
* Returns the email pusher (pusher of type 'email') for a given
|
* Returns the email pusher (pusher of type 'email') for a given
|
||||||
* email address. Email pushers all have the same app ID, so since
|
* email address. Email pushers all have the same app ID, so since
|
||||||
* pushers are unique over (app ID, pushkey), there will be at most
|
* pushers are unique over (app ID, pushkey), there will be at most
|
||||||
|
@ -95,8 +100,8 @@ module.exports = {
|
||||||
if (pushers === undefined) {
|
if (pushers === undefined) {
|
||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
for (var i = 0; i < pushers.length; ++i) {
|
for (let i = 0; i < pushers.length; ++i) {
|
||||||
if (pushers[i].kind == 'email' && pushers[i].pushkey == address) {
|
if (pushers[i].kind === 'email' && pushers[i].pushkey === address) {
|
||||||
return pushers[i];
|
return pushers[i];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -110,7 +115,7 @@ module.exports = {
|
||||||
addEmailPusher: function(address, data) {
|
addEmailPusher: function(address, data) {
|
||||||
return MatrixClientPeg.get().setPusher({
|
return MatrixClientPeg.get().setPusher({
|
||||||
kind: 'email',
|
kind: 'email',
|
||||||
app_id: "m.email",
|
app_id: 'm.email',
|
||||||
pushkey: address,
|
pushkey: address,
|
||||||
app_display_name: 'Email Notifications',
|
app_display_name: 'Email Notifications',
|
||||||
device_display_name: address,
|
device_display_name: address,
|
||||||
|
@ -121,46 +126,46 @@ module.exports = {
|
||||||
},
|
},
|
||||||
|
|
||||||
getUrlPreviewsDisabled: function() {
|
getUrlPreviewsDisabled: function() {
|
||||||
var event = MatrixClientPeg.get().getAccountData("org.matrix.preview_urls");
|
const event = MatrixClientPeg.get().getAccountData('org.matrix.preview_urls');
|
||||||
return (event && event.getContent().disable);
|
return (event && event.getContent().disable);
|
||||||
},
|
},
|
||||||
|
|
||||||
setUrlPreviewsDisabled: function(disabled) {
|
setUrlPreviewsDisabled: function(disabled) {
|
||||||
// FIXME: handle errors
|
// FIXME: handle errors
|
||||||
return MatrixClientPeg.get().setAccountData("org.matrix.preview_urls", {
|
return MatrixClientPeg.get().setAccountData('org.matrix.preview_urls', {
|
||||||
disable: disabled
|
disable: disabled,
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
getSyncedSettings: function() {
|
getSyncedSettings: function() {
|
||||||
var event = MatrixClientPeg.get().getAccountData("im.vector.web.settings");
|
const event = MatrixClientPeg.get().getAccountData('im.vector.web.settings');
|
||||||
return event ? event.getContent() : {};
|
return event ? event.getContent() : {};
|
||||||
},
|
},
|
||||||
|
|
||||||
getSyncedSetting: function(type, defaultValue = null) {
|
getSyncedSetting: function(type, defaultValue = null) {
|
||||||
var settings = this.getSyncedSettings();
|
const settings = this.getSyncedSettings();
|
||||||
return settings.hasOwnProperty(type) ? settings[type] : null;
|
return settings.hasOwnProperty(type) ? settings[type] : defaultValue;
|
||||||
},
|
},
|
||||||
|
|
||||||
setSyncedSetting: function(type, value) {
|
setSyncedSetting: function(type, value) {
|
||||||
var settings = this.getSyncedSettings();
|
const settings = this.getSyncedSettings();
|
||||||
settings[type] = value;
|
settings[type] = value;
|
||||||
// FIXME: handle errors
|
// FIXME: handle errors
|
||||||
return MatrixClientPeg.get().setAccountData("im.vector.web.settings", settings);
|
return MatrixClientPeg.get().setAccountData('im.vector.web.settings', settings);
|
||||||
},
|
},
|
||||||
|
|
||||||
getLocalSettings: function() {
|
getLocalSettings: function() {
|
||||||
var localSettingsString = localStorage.getItem('mx_local_settings') || '{}';
|
const localSettingsString = localStorage.getItem('mx_local_settings') || '{}';
|
||||||
return JSON.parse(localSettingsString);
|
return JSON.parse(localSettingsString);
|
||||||
},
|
},
|
||||||
|
|
||||||
getLocalSetting: function(type, defaultValue = null) {
|
getLocalSetting: function(type, defaultValue = null) {
|
||||||
var settings = this.getLocalSettings();
|
const settings = this.getLocalSettings();
|
||||||
return settings.hasOwnProperty(type) ? settings[type] : null;
|
return settings.hasOwnProperty(type) ? settings[type] : defaultValue;
|
||||||
},
|
},
|
||||||
|
|
||||||
setLocalSetting: function(type, value) {
|
setLocalSetting: function(type, value) {
|
||||||
var settings = this.getLocalSettings();
|
const settings = this.getLocalSettings();
|
||||||
settings[type] = value;
|
settings[type] = value;
|
||||||
// FIXME: handle errors
|
// FIXME: handle errors
|
||||||
localStorage.setItem('mx_local_settings', JSON.stringify(settings));
|
localStorage.setItem('mx_local_settings', JSON.stringify(settings));
|
||||||
|
@ -171,8 +176,8 @@ module.exports = {
|
||||||
if (MatrixClientPeg.get().isGuest()) return false;
|
if (MatrixClientPeg.get().isGuest()) return false;
|
||||||
|
|
||||||
if (localStorage.getItem(`mx_labs_feature_${feature}`) === null) {
|
if (localStorage.getItem(`mx_labs_feature_${feature}`) === null) {
|
||||||
for (var i = 0; i < this.LABS_FEATURES.length; i++) {
|
for (let i = 0; i < this.LABS_FEATURES.length; i++) {
|
||||||
var f = this.LABS_FEATURES[i];
|
const f = this.LABS_FEATURES[i];
|
||||||
if (f.id === feature) {
|
if (f.id === feature) {
|
||||||
return f.default;
|
return f.default;
|
||||||
}
|
}
|
||||||
|
@ -183,5 +188,5 @@ module.exports = {
|
||||||
|
|
||||||
setFeatureEnabled: function(feature: string, enabled: boolean) {
|
setFeatureEnabled: function(feature: string, enabled: boolean) {
|
||||||
localStorage.setItem(`mx_labs_feature_${feature}`, enabled);
|
localStorage.setItem(`mx_labs_feature_${feature}`, enabled);
|
||||||
}
|
},
|
||||||
};
|
};
|
||||||
|
|
|
@ -15,6 +15,7 @@ limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
var MatrixClientPeg = require("./MatrixClientPeg");
|
var MatrixClientPeg = require("./MatrixClientPeg");
|
||||||
|
import { _t } from './languageHandler';
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
usersTypingApartFromMe: function(room) {
|
usersTypingApartFromMe: function(room) {
|
||||||
|
@ -56,18 +57,18 @@ module.exports = {
|
||||||
if (whoIsTyping.length == 0) {
|
if (whoIsTyping.length == 0) {
|
||||||
return '';
|
return '';
|
||||||
} else if (whoIsTyping.length == 1) {
|
} else if (whoIsTyping.length == 1) {
|
||||||
return whoIsTyping[0].name + ' is typing';
|
return _t('%(displayName)s is typing', {displayName: whoIsTyping[0].name});
|
||||||
}
|
}
|
||||||
const names = whoIsTyping.map(function(m) {
|
const names = whoIsTyping.map(function(m) {
|
||||||
return m.name;
|
return m.name;
|
||||||
});
|
});
|
||||||
if (othersCount) {
|
if (othersCount==1) {
|
||||||
const other = ' other' + (othersCount > 1 ? 's' : '');
|
return _t('%(names)s and one other are typing', {names: names.slice(0, limit - 1).join(', ')});
|
||||||
return names.slice(0, limit - 1).join(', ') + ' and ' +
|
} else if (othersCount>1) {
|
||||||
othersCount + other + ' are typing';
|
return _t('%(names)s and %(count)s others are typing', {names: names.slice(0, limit - 1).join(', '), count: othersCount});
|
||||||
} else {
|
} else {
|
||||||
const lastPerson = names.pop();
|
const lastPerson = names.pop();
|
||||||
return names.join(', ') + ' and ' + lastPerson + ' are typing';
|
return _t('%(names)s and %(lastPerson)s are typing', {names: names.join(', '), lastPerson: lastPerson});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
|
@ -15,6 +15,7 @@ limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
var React = require("react");
|
var React = require("react");
|
||||||
|
import { _t } from '../../../languageHandler';
|
||||||
var sdk = require('../../../index');
|
var sdk = require('../../../index');
|
||||||
var MatrixClientPeg = require("../../../MatrixClientPeg");
|
var MatrixClientPeg = require("../../../MatrixClientPeg");
|
||||||
|
|
||||||
|
@ -78,33 +79,33 @@ module.exports = React.createClass({
|
||||||
_renderDeviceInfo: function() {
|
_renderDeviceInfo: function() {
|
||||||
var device = this.state.device;
|
var device = this.state.device;
|
||||||
if (!device) {
|
if (!device) {
|
||||||
return (<i>unknown device</i>);
|
return (<i>{ _t('unknown device') }</i>);
|
||||||
}
|
}
|
||||||
|
|
||||||
var verificationStatus = (<b>NOT verified</b>);
|
var verificationStatus = (<b>{ _t('NOT verified') }</b>);
|
||||||
if (device.isBlocked()) {
|
if (device.isBlocked()) {
|
||||||
verificationStatus = (<b>Blacklisted</b>);
|
verificationStatus = (<b>{ _t('Blacklisted') }</b>);
|
||||||
} else if (device.isVerified()) {
|
} else if (device.isVerified()) {
|
||||||
verificationStatus = "verified";
|
verificationStatus = _t('verified');
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<table>
|
<table>
|
||||||
<tbody>
|
<tbody>
|
||||||
<tr>
|
<tr>
|
||||||
<td>Name</td>
|
<td>{ _t('Name') }</td>
|
||||||
<td>{ device.getDisplayName() }</td>
|
<td>{ device.getDisplayName() }</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td>Device ID</td>
|
<td>{ _t('Device ID') }</td>
|
||||||
<td><code>{ device.deviceId }</code></td>
|
<td><code>{ device.deviceId }</code></td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td>Verification</td>
|
<td>{ _t('Verification') }</td>
|
||||||
<td>{ verificationStatus }</td>
|
<td>{ verificationStatus }</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td>Ed25519 fingerprint</td>
|
<td>{ _t('Ed25519 fingerprint') }</td>
|
||||||
<td><code>{device.getFingerprint()}</code></td>
|
<td><code>{device.getFingerprint()}</code></td>
|
||||||
</tr>
|
</tr>
|
||||||
</tbody>
|
</tbody>
|
||||||
|
@ -119,32 +120,32 @@ module.exports = React.createClass({
|
||||||
<table>
|
<table>
|
||||||
<tbody>
|
<tbody>
|
||||||
<tr>
|
<tr>
|
||||||
<td>User ID</td>
|
<td>{ _t('User ID') }</td>
|
||||||
<td>{ event.getSender() }</td>
|
<td>{ event.getSender() }</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td>Curve25519 identity key</td>
|
<td>{ _t('Curve25519 identity key') }</td>
|
||||||
<td><code>{ event.getSenderKey() || <i>none</i> }</code></td>
|
<td><code>{ event.getSenderKey() || <i>{ _t('none') }</i> }</code></td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td>Claimed Ed25519 fingerprint key</td>
|
<td>{ _t('Claimed Ed25519 fingerprint key') }</td>
|
||||||
<td><code>{ event.getKeysClaimed().ed25519 || <i>none</i> }</code></td>
|
<td><code>{ event.getKeysClaimed().ed25519 || <i>{ _t('none') }</i> }</code></td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td>Algorithm</td>
|
<td>{ _t('Algorithm') }</td>
|
||||||
<td>{ event.getWireContent().algorithm || <i>unencrypted</i> }</td>
|
<td>{ event.getWireContent().algorithm || <i>{ _t('unencrypted') }</i> }</td>
|
||||||
</tr>
|
</tr>
|
||||||
{
|
{
|
||||||
event.getContent().msgtype === 'm.bad.encrypted' ? (
|
event.getContent().msgtype === 'm.bad.encrypted' ? (
|
||||||
<tr>
|
<tr>
|
||||||
<td>Decryption error</td>
|
<td>{ _t('Decryption error') }</td>
|
||||||
<td>{ event.getContent().body }</td>
|
<td>{ event.getContent().body }</td>
|
||||||
</tr>
|
</tr>
|
||||||
) : null
|
) : null
|
||||||
}
|
}
|
||||||
<tr>
|
<tr>
|
||||||
<td>Session ID</td>
|
<td>{ _t('Session ID') }</td>
|
||||||
<td><code>{ event.getWireContent().session_id || <i>none</i> }</code></td>
|
<td><code>{ event.getWireContent().session_id || <i>{ _t('none') }</i> }</code></td>
|
||||||
</tr>
|
</tr>
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
|
@ -166,18 +167,18 @@ module.exports = React.createClass({
|
||||||
return (
|
return (
|
||||||
<div className="mx_EncryptedEventDialog" onKeyDown={ this.onKeyDown }>
|
<div className="mx_EncryptedEventDialog" onKeyDown={ this.onKeyDown }>
|
||||||
<div className="mx_Dialog_title">
|
<div className="mx_Dialog_title">
|
||||||
End-to-end encryption information
|
{ _t('End-to-end encryption information') }
|
||||||
</div>
|
</div>
|
||||||
<div className="mx_Dialog_content">
|
<div className="mx_Dialog_content">
|
||||||
<h4>Event information</h4>
|
<h4>{ _t('Event information') }</h4>
|
||||||
{this._renderEventInfo()}
|
{this._renderEventInfo()}
|
||||||
|
|
||||||
<h4>Sender device information</h4>
|
<h4>{ _t('Sender device information') }</h4>
|
||||||
{this._renderDeviceInfo()}
|
{this._renderDeviceInfo()}
|
||||||
</div>
|
</div>
|
||||||
<div className="mx_Dialog_buttons">
|
<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 }>
|
||||||
OK
|
{ _t('OK') }
|
||||||
</button>
|
</button>
|
||||||
{buttons}
|
{buttons}
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -16,6 +16,7 @@ limitations under the License.
|
||||||
|
|
||||||
import FileSaver from 'file-saver';
|
import FileSaver from 'file-saver';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
import { _t } from '../../../languageHandler';
|
||||||
|
|
||||||
import * as Matrix from 'matrix-js-sdk';
|
import * as Matrix from 'matrix-js-sdk';
|
||||||
import * as MegolmExportEncryption from '../../../utils/MegolmExportEncryption';
|
import * as MegolmExportEncryption from '../../../utils/MegolmExportEncryption';
|
||||||
|
@ -52,11 +53,11 @@ export default React.createClass({
|
||||||
|
|
||||||
const passphrase = this.refs.passphrase1.value;
|
const passphrase = this.refs.passphrase1.value;
|
||||||
if (passphrase !== this.refs.passphrase2.value) {
|
if (passphrase !== this.refs.passphrase2.value) {
|
||||||
this.setState({errStr: 'Passphrases must match'});
|
this.setState({errStr: _t('Passphrases must match')});
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if (!passphrase) {
|
if (!passphrase) {
|
||||||
this.setState({errStr: 'Passphrase must not be empty'});
|
this.setState({errStr: _t('Passphrase must not be empty')});
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -109,24 +110,28 @@ export default React.createClass({
|
||||||
return (
|
return (
|
||||||
<BaseDialog className='mx_exportE2eKeysDialog'
|
<BaseDialog className='mx_exportE2eKeysDialog'
|
||||||
onFinished={this.props.onFinished}
|
onFinished={this.props.onFinished}
|
||||||
title="Export room keys"
|
title={_t("Export room keys")}
|
||||||
>
|
>
|
||||||
<form onSubmit={this._onPassphraseFormSubmit}>
|
<form onSubmit={this._onPassphraseFormSubmit}>
|
||||||
<div className="mx_Dialog_content">
|
<div className="mx_Dialog_content">
|
||||||
<p>
|
<p>
|
||||||
This process allows you to export the keys for messages
|
{ _t(
|
||||||
you have received in encrypted rooms to a local file. You
|
'This process allows you to export the keys for messages ' +
|
||||||
will then be able to import the file into another Matrix
|
'you have received in encrypted rooms to a local file. You ' +
|
||||||
client in the future, so that client will also be able to
|
'will then be able to import the file into another Matrix ' +
|
||||||
decrypt these messages.
|
'client in the future, so that client will also be able to ' +
|
||||||
|
'decrypt these messages.'
|
||||||
|
) }
|
||||||
</p>
|
</p>
|
||||||
<p>
|
<p>
|
||||||
The exported file will allow anyone who can read it to decrypt
|
{ _t(
|
||||||
any encrypted messages that you can see, so you should be
|
'The exported file will allow anyone who can read it to decrypt ' +
|
||||||
careful to keep it secure. To help with this, you should enter
|
'any encrypted messages that you can see, so you should be ' +
|
||||||
a passphrase below, which will be used to encrypt the exported
|
'careful to keep it secure. To help with this, you should enter ' +
|
||||||
data. It will only be possible to import the data by using the
|
'a passphrase below, which will be used to encrypt the exported ' +
|
||||||
same passphrase.
|
'data. It will only be possible to import the data by using the ' +
|
||||||
|
'same passphrase.'
|
||||||
|
) }
|
||||||
</p>
|
</p>
|
||||||
<div className='error'>
|
<div className='error'>
|
||||||
{this.state.errStr}
|
{this.state.errStr}
|
||||||
|
@ -135,7 +140,7 @@ export default React.createClass({
|
||||||
<div className='mx_E2eKeysDialog_inputRow'>
|
<div className='mx_E2eKeysDialog_inputRow'>
|
||||||
<div className='mx_E2eKeysDialog_inputLabel'>
|
<div className='mx_E2eKeysDialog_inputLabel'>
|
||||||
<label htmlFor='passphrase1'>
|
<label htmlFor='passphrase1'>
|
||||||
Enter passphrase
|
{_t("Enter passphrase")}
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
<div className='mx_E2eKeysDialog_inputCell'>
|
<div className='mx_E2eKeysDialog_inputCell'>
|
||||||
|
@ -148,7 +153,7 @@ export default React.createClass({
|
||||||
<div className='mx_E2eKeysDialog_inputRow'>
|
<div className='mx_E2eKeysDialog_inputRow'>
|
||||||
<div className='mx_E2eKeysDialog_inputLabel'>
|
<div className='mx_E2eKeysDialog_inputLabel'>
|
||||||
<label htmlFor='passphrase2'>
|
<label htmlFor='passphrase2'>
|
||||||
Confirm passphrase
|
{_t("Confirm passphrase")}
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
<div className='mx_E2eKeysDialog_inputCell'>
|
<div className='mx_E2eKeysDialog_inputCell'>
|
||||||
|
@ -161,11 +166,11 @@ export default React.createClass({
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className='mx_Dialog_buttons'>
|
<div className='mx_Dialog_buttons'>
|
||||||
<input className='mx_Dialog_primary' type='submit' value='Export'
|
<input className='mx_Dialog_primary' type='submit' value={_t('Export')}
|
||||||
disabled={disableForm}
|
disabled={disableForm}
|
||||||
/>
|
/>
|
||||||
<button onClick={this._onCancelClick} disabled={disableForm}>
|
<button onClick={this._onCancelClick} disabled={disableForm}>
|
||||||
Cancel
|
{_t("Cancel")}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
|
|
|
@ -19,6 +19,7 @@ import React from 'react';
|
||||||
import * as Matrix from 'matrix-js-sdk';
|
import * as Matrix from 'matrix-js-sdk';
|
||||||
import * as MegolmExportEncryption from '../../../utils/MegolmExportEncryption';
|
import * as MegolmExportEncryption from '../../../utils/MegolmExportEncryption';
|
||||||
import sdk from '../../../index';
|
import sdk from '../../../index';
|
||||||
|
import { _t } from '../../../languageHandler';
|
||||||
|
|
||||||
function readFileAsArrayBuffer(file) {
|
function readFileAsArrayBuffer(file) {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
|
@ -112,20 +113,23 @@ export default React.createClass({
|
||||||
return (
|
return (
|
||||||
<BaseDialog className='mx_importE2eKeysDialog'
|
<BaseDialog className='mx_importE2eKeysDialog'
|
||||||
onFinished={this.props.onFinished}
|
onFinished={this.props.onFinished}
|
||||||
title="Import room keys"
|
title={_t("Import room keys")}
|
||||||
>
|
>
|
||||||
<form onSubmit={this._onFormSubmit}>
|
<form onSubmit={this._onFormSubmit}>
|
||||||
<div className="mx_Dialog_content">
|
<div className="mx_Dialog_content">
|
||||||
<p>
|
<p>
|
||||||
This process allows you to import encryption keys
|
{ _t(
|
||||||
that you had previously exported from another Matrix
|
'This process allows you to import encryption keys ' +
|
||||||
client. You will then be able to decrypt any
|
'that you had previously exported from another Matrix ' +
|
||||||
messages that the other client could decrypt.
|
'client. You will then be able to decrypt any ' +
|
||||||
|
'messages that the other client could decrypt.'
|
||||||
|
) }
|
||||||
</p>
|
</p>
|
||||||
<p>
|
<p>
|
||||||
The export file will be protected with a passphrase.
|
{ _t(
|
||||||
You should enter the passphrase here, to decrypt the
|
'The export file will be protected with a passphrase. ' +
|
||||||
file.
|
'You should enter the passphrase here, to decrypt the file.'
|
||||||
|
) }
|
||||||
</p>
|
</p>
|
||||||
<div className='error'>
|
<div className='error'>
|
||||||
{this.state.errStr}
|
{this.state.errStr}
|
||||||
|
@ -134,7 +138,7 @@ export default React.createClass({
|
||||||
<div className='mx_E2eKeysDialog_inputRow'>
|
<div className='mx_E2eKeysDialog_inputRow'>
|
||||||
<div className='mx_E2eKeysDialog_inputLabel'>
|
<div className='mx_E2eKeysDialog_inputLabel'>
|
||||||
<label htmlFor='importFile'>
|
<label htmlFor='importFile'>
|
||||||
File to import
|
{_t("File to import")}
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
<div className='mx_E2eKeysDialog_inputCell'>
|
<div className='mx_E2eKeysDialog_inputCell'>
|
||||||
|
@ -147,7 +151,7 @@ export default React.createClass({
|
||||||
<div className='mx_E2eKeysDialog_inputRow'>
|
<div className='mx_E2eKeysDialog_inputRow'>
|
||||||
<div className='mx_E2eKeysDialog_inputLabel'>
|
<div className='mx_E2eKeysDialog_inputLabel'>
|
||||||
<label htmlFor='passphrase'>
|
<label htmlFor='passphrase'>
|
||||||
Enter passphrase
|
{_t("Enter passphrase")}
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
<div className='mx_E2eKeysDialog_inputCell'>
|
<div className='mx_E2eKeysDialog_inputCell'>
|
||||||
|
@ -160,11 +164,11 @@ export default React.createClass({
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className='mx_Dialog_buttons'>
|
<div className='mx_Dialog_buttons'>
|
||||||
<input className='mx_Dialog_primary' type='submit' value='Import'
|
<input className='mx_Dialog_primary' type='submit' value={_t('Import')}
|
||||||
disabled={!this.state.enableSubmit || disableForm}
|
disabled={!this.state.enableSubmit || disableForm}
|
||||||
/>
|
/>
|
||||||
<button onClick={this._onCancelClick} disabled={disableForm}>
|
<button onClick={this._onCancelClick} disabled={disableForm}>
|
||||||
Cancel
|
{_t("Cancel")}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
|
|
|
@ -1,3 +1,20 @@
|
||||||
|
/*
|
||||||
|
Copyright 2016 Aviral Dasgupta
|
||||||
|
Copyright 2017 Vector Creations 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 React from 'react';
|
||||||
import type {Completion, SelectionRange} from './Autocompleter';
|
import type {Completion, SelectionRange} from './Autocompleter';
|
||||||
|
|
||||||
|
|
|
@ -1,3 +1,19 @@
|
||||||
|
/*
|
||||||
|
Copyright 2016 Aviral Dasgupta
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
// @flow
|
// @flow
|
||||||
|
|
||||||
import type {Component} from 'react';
|
import type {Component} from 'react';
|
||||||
|
|
|
@ -1,8 +1,27 @@
|
||||||
|
/*
|
||||||
|
Copyright 2016 Aviral Dasgupta
|
||||||
|
Copyright 2017 Vector Creations 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 React from 'react';
|
||||||
|
import { _t } from '../languageHandler';
|
||||||
import AutocompleteProvider from './AutocompleteProvider';
|
import AutocompleteProvider from './AutocompleteProvider';
|
||||||
import Fuse from 'fuse.js';
|
import Fuse from 'fuse.js';
|
||||||
import {TextualCompletion} from './Components';
|
import {TextualCompletion} from './Components';
|
||||||
|
|
||||||
|
// Warning: Since the description string will be translated in _t(result.description), all these strings below must be in i18n/strings/en_EN.json file
|
||||||
const COMMANDS = [
|
const COMMANDS = [
|
||||||
{
|
{
|
||||||
command: '/me',
|
command: '/me',
|
||||||
|
@ -43,10 +62,10 @@ const COMMANDS = [
|
||||||
command: '/ddg',
|
command: '/ddg',
|
||||||
args: '<query>',
|
args: '<query>',
|
||||||
description: 'Searches DuckDuckGo for results',
|
description: 'Searches DuckDuckGo for results',
|
||||||
}
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
let COMMAND_RE = /(^\/\w*)/g;
|
const COMMAND_RE = /(^\/\w*)/g;
|
||||||
|
|
||||||
let instance = null;
|
let instance = null;
|
||||||
|
|
||||||
|
@ -60,15 +79,15 @@ export default class CommandProvider extends AutocompleteProvider {
|
||||||
|
|
||||||
async getCompletions(query: string, selection: {start: number, end: number}) {
|
async getCompletions(query: string, selection: {start: number, end: number}) {
|
||||||
let completions = [];
|
let completions = [];
|
||||||
let {command, range} = this.getCurrentCommand(query, selection);
|
const {command, range} = this.getCurrentCommand(query, selection);
|
||||||
if (command) {
|
if (command) {
|
||||||
completions = this.fuse.search(command[0]).map(result => {
|
completions = this.fuse.search(command[0]).map((result) => {
|
||||||
return {
|
return {
|
||||||
completion: result.command + ' ',
|
completion: result.command + ' ',
|
||||||
component: (<TextualCompletion
|
component: (<TextualCompletion
|
||||||
title={result.command}
|
title={result.command}
|
||||||
subtitle={result.args}
|
subtitle={result.args}
|
||||||
description={result.description}
|
description={ _t(result.description) }
|
||||||
/>),
|
/>),
|
||||||
range,
|
range,
|
||||||
};
|
};
|
||||||
|
@ -78,12 +97,11 @@ export default class CommandProvider extends AutocompleteProvider {
|
||||||
}
|
}
|
||||||
|
|
||||||
getName() {
|
getName() {
|
||||||
return '*️⃣ Commands';
|
return '*️⃣ ' + _t('Commands');
|
||||||
}
|
}
|
||||||
|
|
||||||
static getInstance(): CommandProvider {
|
static getInstance(): CommandProvider {
|
||||||
if (instance == null)
|
if (instance === null) instance = new CommandProvider();
|
||||||
{instance = new CommandProvider();}
|
|
||||||
|
|
||||||
return instance;
|
return instance;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,3 +1,19 @@
|
||||||
|
/*
|
||||||
|
Copyright 2016 Aviral Dasgupta
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import ReactDOM from 'react-dom';
|
import ReactDOM from 'react-dom';
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
|
|
|
@ -1,4 +1,22 @@
|
||||||
|
/*
|
||||||
|
Copyright 2016 Aviral Dasgupta
|
||||||
|
Copyright 2017 Vector Creations 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 React from 'react';
|
||||||
|
import { _t } from '../languageHandler';
|
||||||
import AutocompleteProvider from './AutocompleteProvider';
|
import AutocompleteProvider from './AutocompleteProvider';
|
||||||
import 'whatwg-fetch';
|
import 'whatwg-fetch';
|
||||||
|
|
||||||
|
@ -75,7 +93,7 @@ export default class DuckDuckGoProvider extends AutocompleteProvider {
|
||||||
}
|
}
|
||||||
|
|
||||||
getName() {
|
getName() {
|
||||||
return '🔍 Results from DuckDuckGo';
|
return '🔍 ' + _t('Results from DuckDuckGo');
|
||||||
}
|
}
|
||||||
|
|
||||||
static getInstance(): DuckDuckGoProvider {
|
static getInstance(): DuckDuckGoProvider {
|
||||||
|
|
|
@ -1,4 +1,22 @@
|
||||||
|
/*
|
||||||
|
Copyright 2016 Aviral Dasgupta
|
||||||
|
Copyright 2017 Vector Creations 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 React from 'react';
|
||||||
|
import { _t } from '../languageHandler';
|
||||||
import AutocompleteProvider from './AutocompleteProvider';
|
import AutocompleteProvider from './AutocompleteProvider';
|
||||||
import {emojioneList, shortnameToImage, shortnameToUnicode} from 'emojione';
|
import {emojioneList, shortnameToImage, shortnameToUnicode} from 'emojione';
|
||||||
import Fuse from 'fuse.js';
|
import Fuse from 'fuse.js';
|
||||||
|
@ -14,7 +32,7 @@ let instance = null;
|
||||||
export default class EmojiProvider extends AutocompleteProvider {
|
export default class EmojiProvider extends AutocompleteProvider {
|
||||||
constructor() {
|
constructor() {
|
||||||
super(EMOJI_REGEX);
|
super(EMOJI_REGEX);
|
||||||
this.fuse = new Fuse(EMOJI_SHORTNAMES);
|
this.fuse = new Fuse(EMOJI_SHORTNAMES, {});
|
||||||
}
|
}
|
||||||
|
|
||||||
async getCompletions(query: string, selection: SelectionRange) {
|
async getCompletions(query: string, selection: SelectionRange) {
|
||||||
|
@ -39,7 +57,7 @@ export default class EmojiProvider extends AutocompleteProvider {
|
||||||
}
|
}
|
||||||
|
|
||||||
getName() {
|
getName() {
|
||||||
return '😃 Emoji';
|
return '😃 ' + _t('Emoji');
|
||||||
}
|
}
|
||||||
|
|
||||||
static getInstance() {
|
static getInstance() {
|
||||||
|
|
|
@ -1,4 +1,22 @@
|
||||||
|
/*
|
||||||
|
Copyright 2016 Aviral Dasgupta
|
||||||
|
Copyright 2017 Vector Creations 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 React from 'react';
|
||||||
|
import { _t } from '../languageHandler';
|
||||||
import AutocompleteProvider from './AutocompleteProvider';
|
import AutocompleteProvider from './AutocompleteProvider';
|
||||||
import MatrixClientPeg from '../MatrixClientPeg';
|
import MatrixClientPeg from '../MatrixClientPeg';
|
||||||
import Fuse from 'fuse.js';
|
import Fuse from 'fuse.js';
|
||||||
|
@ -50,7 +68,7 @@ export default class RoomProvider extends AutocompleteProvider {
|
||||||
}
|
}
|
||||||
|
|
||||||
getName() {
|
getName() {
|
||||||
return '💬 Rooms';
|
return '💬 ' + _t('Rooms');
|
||||||
}
|
}
|
||||||
|
|
||||||
static getInstance() {
|
static getInstance() {
|
||||||
|
|
|
@ -1,4 +1,22 @@
|
||||||
|
/*
|
||||||
|
Copyright 2016 Aviral Dasgupta
|
||||||
|
Copyright 2017 Vector Creations 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 React from 'react';
|
||||||
|
import { _t } from '../languageHandler';
|
||||||
import AutocompleteProvider from './AutocompleteProvider';
|
import AutocompleteProvider from './AutocompleteProvider';
|
||||||
import Q from 'q';
|
import Q from 'q';
|
||||||
import Fuse from 'fuse.js';
|
import Fuse from 'fuse.js';
|
||||||
|
@ -51,7 +69,7 @@ export default class UserProvider extends AutocompleteProvider {
|
||||||
}
|
}
|
||||||
|
|
||||||
getName() {
|
getName() {
|
||||||
return '👥 Users';
|
return '👥 ' + _t('Users');
|
||||||
}
|
}
|
||||||
|
|
||||||
setUserList(users) {
|
setUserList(users) {
|
||||||
|
|
|
@ -1,253 +0,0 @@
|
||||||
/*
|
|
||||||
Copyright 2015, 2016 OpenMarket 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.
|
|
||||||
*/
|
|
||||||
|
|
||||||
/*
|
|
||||||
* THIS FILE IS AUTO-GENERATED
|
|
||||||
* You can edit it you like, but your changes will be overwritten,
|
|
||||||
* so you'd just be trying to swim upstream like a salmon.
|
|
||||||
* You are not a salmon.
|
|
||||||
*
|
|
||||||
* To update it, run:
|
|
||||||
* ./reskindex.js -h header
|
|
||||||
*/
|
|
||||||
|
|
||||||
module.exports.components = {};
|
|
||||||
import structures$ContextualMenu from './components/structures/ContextualMenu';
|
|
||||||
structures$ContextualMenu && (module.exports.components['structures.ContextualMenu'] = structures$ContextualMenu);
|
|
||||||
import structures$CreateRoom from './components/structures/CreateRoom';
|
|
||||||
structures$CreateRoom && (module.exports.components['structures.CreateRoom'] = structures$CreateRoom);
|
|
||||||
import structures$FilePanel from './components/structures/FilePanel';
|
|
||||||
structures$FilePanel && (module.exports.components['structures.FilePanel'] = structures$FilePanel);
|
|
||||||
import structures$InteractiveAuth from './components/structures/InteractiveAuth';
|
|
||||||
structures$InteractiveAuth && (module.exports.components['structures.InteractiveAuth'] = structures$InteractiveAuth);
|
|
||||||
import structures$LoggedInView from './components/structures/LoggedInView';
|
|
||||||
structures$LoggedInView && (module.exports.components['structures.LoggedInView'] = structures$LoggedInView);
|
|
||||||
import structures$MatrixChat from './components/structures/MatrixChat';
|
|
||||||
structures$MatrixChat && (module.exports.components['structures.MatrixChat'] = structures$MatrixChat);
|
|
||||||
import structures$MessagePanel from './components/structures/MessagePanel';
|
|
||||||
structures$MessagePanel && (module.exports.components['structures.MessagePanel'] = structures$MessagePanel);
|
|
||||||
import structures$NotificationPanel from './components/structures/NotificationPanel';
|
|
||||||
structures$NotificationPanel && (module.exports.components['structures.NotificationPanel'] = structures$NotificationPanel);
|
|
||||||
import structures$RoomStatusBar from './components/structures/RoomStatusBar';
|
|
||||||
structures$RoomStatusBar && (module.exports.components['structures.RoomStatusBar'] = structures$RoomStatusBar);
|
|
||||||
import structures$RoomView from './components/structures/RoomView';
|
|
||||||
structures$RoomView && (module.exports.components['structures.RoomView'] = structures$RoomView);
|
|
||||||
import structures$ScrollPanel from './components/structures/ScrollPanel';
|
|
||||||
structures$ScrollPanel && (module.exports.components['structures.ScrollPanel'] = structures$ScrollPanel);
|
|
||||||
import structures$TimelinePanel from './components/structures/TimelinePanel';
|
|
||||||
structures$TimelinePanel && (module.exports.components['structures.TimelinePanel'] = structures$TimelinePanel);
|
|
||||||
import structures$UploadBar from './components/structures/UploadBar';
|
|
||||||
structures$UploadBar && (module.exports.components['structures.UploadBar'] = structures$UploadBar);
|
|
||||||
import structures$UserSettings from './components/structures/UserSettings';
|
|
||||||
structures$UserSettings && (module.exports.components['structures.UserSettings'] = structures$UserSettings);
|
|
||||||
import structures$login$ForgotPassword from './components/structures/login/ForgotPassword';
|
|
||||||
structures$login$ForgotPassword && (module.exports.components['structures.login.ForgotPassword'] = structures$login$ForgotPassword);
|
|
||||||
import structures$login$Login from './components/structures/login/Login';
|
|
||||||
structures$login$Login && (module.exports.components['structures.login.Login'] = structures$login$Login);
|
|
||||||
import structures$login$PostRegistration from './components/structures/login/PostRegistration';
|
|
||||||
structures$login$PostRegistration && (module.exports.components['structures.login.PostRegistration'] = structures$login$PostRegistration);
|
|
||||||
import structures$login$Registration from './components/structures/login/Registration';
|
|
||||||
structures$login$Registration && (module.exports.components['structures.login.Registration'] = structures$login$Registration);
|
|
||||||
import views$avatars$BaseAvatar from './components/views/avatars/BaseAvatar';
|
|
||||||
views$avatars$BaseAvatar && (module.exports.components['views.avatars.BaseAvatar'] = views$avatars$BaseAvatar);
|
|
||||||
import views$avatars$MemberAvatar from './components/views/avatars/MemberAvatar';
|
|
||||||
views$avatars$MemberAvatar && (module.exports.components['views.avatars.MemberAvatar'] = views$avatars$MemberAvatar);
|
|
||||||
import views$avatars$RoomAvatar from './components/views/avatars/RoomAvatar';
|
|
||||||
views$avatars$RoomAvatar && (module.exports.components['views.avatars.RoomAvatar'] = views$avatars$RoomAvatar);
|
|
||||||
import views$create_room$CreateRoomButton from './components/views/create_room/CreateRoomButton';
|
|
||||||
views$create_room$CreateRoomButton && (module.exports.components['views.create_room.CreateRoomButton'] = views$create_room$CreateRoomButton);
|
|
||||||
import views$create_room$Presets from './components/views/create_room/Presets';
|
|
||||||
views$create_room$Presets && (module.exports.components['views.create_room.Presets'] = views$create_room$Presets);
|
|
||||||
import views$create_room$RoomAlias from './components/views/create_room/RoomAlias';
|
|
||||||
views$create_room$RoomAlias && (module.exports.components['views.create_room.RoomAlias'] = views$create_room$RoomAlias);
|
|
||||||
import views$dialogs$BaseDialog from './components/views/dialogs/BaseDialog';
|
|
||||||
views$dialogs$BaseDialog && (module.exports.components['views.dialogs.BaseDialog'] = views$dialogs$BaseDialog);
|
|
||||||
import views$dialogs$ChatCreateOrReuseDialog from './components/views/dialogs/ChatCreateOrReuseDialog';
|
|
||||||
views$dialogs$ChatCreateOrReuseDialog && (module.exports.components['views.dialogs.ChatCreateOrReuseDialog'] = views$dialogs$ChatCreateOrReuseDialog);
|
|
||||||
import views$dialogs$ChatInviteDialog from './components/views/dialogs/ChatInviteDialog';
|
|
||||||
views$dialogs$ChatInviteDialog && (module.exports.components['views.dialogs.ChatInviteDialog'] = views$dialogs$ChatInviteDialog);
|
|
||||||
import views$dialogs$ConfirmRedactDialog from './components/views/dialogs/ConfirmRedactDialog';
|
|
||||||
views$dialogs$ConfirmRedactDialog && (module.exports.components['views.dialogs.ConfirmRedactDialog'] = views$dialogs$ConfirmRedactDialog);
|
|
||||||
import views$dialogs$ConfirmUserActionDialog from './components/views/dialogs/ConfirmUserActionDialog';
|
|
||||||
views$dialogs$ConfirmUserActionDialog && (module.exports.components['views.dialogs.ConfirmUserActionDialog'] = views$dialogs$ConfirmUserActionDialog);
|
|
||||||
import views$dialogs$DeactivateAccountDialog from './components/views/dialogs/DeactivateAccountDialog';
|
|
||||||
views$dialogs$DeactivateAccountDialog && (module.exports.components['views.dialogs.DeactivateAccountDialog'] = views$dialogs$DeactivateAccountDialog);
|
|
||||||
import views$dialogs$ErrorDialog from './components/views/dialogs/ErrorDialog';
|
|
||||||
views$dialogs$ErrorDialog && (module.exports.components['views.dialogs.ErrorDialog'] = views$dialogs$ErrorDialog);
|
|
||||||
import views$dialogs$InteractiveAuthDialog from './components/views/dialogs/InteractiveAuthDialog';
|
|
||||||
views$dialogs$InteractiveAuthDialog && (module.exports.components['views.dialogs.InteractiveAuthDialog'] = views$dialogs$InteractiveAuthDialog);
|
|
||||||
import views$dialogs$NeedToRegisterDialog from './components/views/dialogs/NeedToRegisterDialog';
|
|
||||||
views$dialogs$NeedToRegisterDialog && (module.exports.components['views.dialogs.NeedToRegisterDialog'] = views$dialogs$NeedToRegisterDialog);
|
|
||||||
import views$dialogs$QuestionDialog from './components/views/dialogs/QuestionDialog';
|
|
||||||
views$dialogs$QuestionDialog && (module.exports.components['views.dialogs.QuestionDialog'] = views$dialogs$QuestionDialog);
|
|
||||||
import views$dialogs$SessionRestoreErrorDialog from './components/views/dialogs/SessionRestoreErrorDialog';
|
|
||||||
views$dialogs$SessionRestoreErrorDialog && (module.exports.components['views.dialogs.SessionRestoreErrorDialog'] = views$dialogs$SessionRestoreErrorDialog);
|
|
||||||
import views$dialogs$SetDisplayNameDialog from './components/views/dialogs/SetDisplayNameDialog';
|
|
||||||
views$dialogs$SetDisplayNameDialog && (module.exports.components['views.dialogs.SetDisplayNameDialog'] = views$dialogs$SetDisplayNameDialog);
|
|
||||||
import views$dialogs$TextInputDialog from './components/views/dialogs/TextInputDialog';
|
|
||||||
views$dialogs$TextInputDialog && (module.exports.components['views.dialogs.TextInputDialog'] = views$dialogs$TextInputDialog);
|
|
||||||
import views$dialogs$UnknownDeviceDialog from './components/views/dialogs/UnknownDeviceDialog';
|
|
||||||
views$dialogs$UnknownDeviceDialog && (module.exports.components['views.dialogs.UnknownDeviceDialog'] = views$dialogs$UnknownDeviceDialog);
|
|
||||||
import views$elements$AccessibleButton from './components/views/elements/AccessibleButton';
|
|
||||||
views$elements$AccessibleButton && (module.exports.components['views.elements.AccessibleButton'] = views$elements$AccessibleButton);
|
|
||||||
import views$elements$AddressSelector from './components/views/elements/AddressSelector';
|
|
||||||
views$elements$AddressSelector && (module.exports.components['views.elements.AddressSelector'] = views$elements$AddressSelector);
|
|
||||||
import views$elements$AddressTile from './components/views/elements/AddressTile';
|
|
||||||
views$elements$AddressTile && (module.exports.components['views.elements.AddressTile'] = views$elements$AddressTile);
|
|
||||||
import views$elements$DeviceVerifyButtons from './components/views/elements/DeviceVerifyButtons';
|
|
||||||
views$elements$DeviceVerifyButtons && (module.exports.components['views.elements.DeviceVerifyButtons'] = views$elements$DeviceVerifyButtons);
|
|
||||||
import views$elements$DirectorySearchBox from './components/views/elements/DirectorySearchBox';
|
|
||||||
views$elements$DirectorySearchBox && (module.exports.components['views.elements.DirectorySearchBox'] = views$elements$DirectorySearchBox);
|
|
||||||
import views$elements$Dropdown from './components/views/elements/Dropdown';
|
|
||||||
views$elements$Dropdown && (module.exports.components['views.elements.Dropdown'] = views$elements$Dropdown);
|
|
||||||
import views$elements$EditableText from './components/views/elements/EditableText';
|
|
||||||
views$elements$EditableText && (module.exports.components['views.elements.EditableText'] = views$elements$EditableText);
|
|
||||||
import views$elements$EditableTextContainer from './components/views/elements/EditableTextContainer';
|
|
||||||
views$elements$EditableTextContainer && (module.exports.components['views.elements.EditableTextContainer'] = views$elements$EditableTextContainer);
|
|
||||||
import views$elements$EmojiText from './components/views/elements/EmojiText';
|
|
||||||
views$elements$EmojiText && (module.exports.components['views.elements.EmojiText'] = views$elements$EmojiText);
|
|
||||||
import views$elements$MemberEventListSummary from './components/views/elements/MemberEventListSummary';
|
|
||||||
views$elements$MemberEventListSummary && (module.exports.components['views.elements.MemberEventListSummary'] = views$elements$MemberEventListSummary);
|
|
||||||
import views$elements$PowerSelector from './components/views/elements/PowerSelector';
|
|
||||||
views$elements$PowerSelector && (module.exports.components['views.elements.PowerSelector'] = views$elements$PowerSelector);
|
|
||||||
import views$elements$ProgressBar from './components/views/elements/ProgressBar';
|
|
||||||
views$elements$ProgressBar && (module.exports.components['views.elements.ProgressBar'] = views$elements$ProgressBar);
|
|
||||||
import views$elements$TintableSvg from './components/views/elements/TintableSvg';
|
|
||||||
views$elements$TintableSvg && (module.exports.components['views.elements.TintableSvg'] = views$elements$TintableSvg);
|
|
||||||
import views$elements$TruncatedList from './components/views/elements/TruncatedList';
|
|
||||||
views$elements$TruncatedList && (module.exports.components['views.elements.TruncatedList'] = views$elements$TruncatedList);
|
|
||||||
import views$elements$UserSelector from './components/views/elements/UserSelector';
|
|
||||||
views$elements$UserSelector && (module.exports.components['views.elements.UserSelector'] = views$elements$UserSelector);
|
|
||||||
import views$login$CaptchaForm from './components/views/login/CaptchaForm';
|
|
||||||
views$login$CaptchaForm && (module.exports.components['views.login.CaptchaForm'] = views$login$CaptchaForm);
|
|
||||||
import views$login$CasLogin from './components/views/login/CasLogin';
|
|
||||||
views$login$CasLogin && (module.exports.components['views.login.CasLogin'] = views$login$CasLogin);
|
|
||||||
import views$login$CountryDropdown from './components/views/login/CountryDropdown';
|
|
||||||
views$login$CountryDropdown && (module.exports.components['views.login.CountryDropdown'] = views$login$CountryDropdown);
|
|
||||||
import views$login$CustomServerDialog from './components/views/login/CustomServerDialog';
|
|
||||||
views$login$CustomServerDialog && (module.exports.components['views.login.CustomServerDialog'] = views$login$CustomServerDialog);
|
|
||||||
import views$login$InteractiveAuthEntryComponents from './components/views/login/InteractiveAuthEntryComponents';
|
|
||||||
views$login$InteractiveAuthEntryComponents && (module.exports.components['views.login.InteractiveAuthEntryComponents'] = views$login$InteractiveAuthEntryComponents);
|
|
||||||
import views$login$LoginFooter from './components/views/login/LoginFooter';
|
|
||||||
views$login$LoginFooter && (module.exports.components['views.login.LoginFooter'] = views$login$LoginFooter);
|
|
||||||
import views$login$LoginHeader from './components/views/login/LoginHeader';
|
|
||||||
views$login$LoginHeader && (module.exports.components['views.login.LoginHeader'] = views$login$LoginHeader);
|
|
||||||
import views$login$PasswordLogin from './components/views/login/PasswordLogin';
|
|
||||||
views$login$PasswordLogin && (module.exports.components['views.login.PasswordLogin'] = views$login$PasswordLogin);
|
|
||||||
import views$login$RegistrationForm from './components/views/login/RegistrationForm';
|
|
||||||
views$login$RegistrationForm && (module.exports.components['views.login.RegistrationForm'] = views$login$RegistrationForm);
|
|
||||||
import views$login$ServerConfig from './components/views/login/ServerConfig';
|
|
||||||
views$login$ServerConfig && (module.exports.components['views.login.ServerConfig'] = views$login$ServerConfig);
|
|
||||||
import views$messages$MAudioBody from './components/views/messages/MAudioBody';
|
|
||||||
views$messages$MAudioBody && (module.exports.components['views.messages.MAudioBody'] = views$messages$MAudioBody);
|
|
||||||
import views$messages$MFileBody from './components/views/messages/MFileBody';
|
|
||||||
views$messages$MFileBody && (module.exports.components['views.messages.MFileBody'] = views$messages$MFileBody);
|
|
||||||
import views$messages$MImageBody from './components/views/messages/MImageBody';
|
|
||||||
views$messages$MImageBody && (module.exports.components['views.messages.MImageBody'] = views$messages$MImageBody);
|
|
||||||
import views$messages$MVideoBody from './components/views/messages/MVideoBody';
|
|
||||||
views$messages$MVideoBody && (module.exports.components['views.messages.MVideoBody'] = views$messages$MVideoBody);
|
|
||||||
import views$messages$MessageEvent from './components/views/messages/MessageEvent';
|
|
||||||
views$messages$MessageEvent && (module.exports.components['views.messages.MessageEvent'] = views$messages$MessageEvent);
|
|
||||||
import views$messages$SenderProfile from './components/views/messages/SenderProfile';
|
|
||||||
views$messages$SenderProfile && (module.exports.components['views.messages.SenderProfile'] = views$messages$SenderProfile);
|
|
||||||
import views$messages$TextualBody from './components/views/messages/TextualBody';
|
|
||||||
views$messages$TextualBody && (module.exports.components['views.messages.TextualBody'] = views$messages$TextualBody);
|
|
||||||
import views$messages$TextualEvent from './components/views/messages/TextualEvent';
|
|
||||||
views$messages$TextualEvent && (module.exports.components['views.messages.TextualEvent'] = views$messages$TextualEvent);
|
|
||||||
import views$messages$UnknownBody from './components/views/messages/UnknownBody';
|
|
||||||
views$messages$UnknownBody && (module.exports.components['views.messages.UnknownBody'] = views$messages$UnknownBody);
|
|
||||||
import views$room_settings$AliasSettings from './components/views/room_settings/AliasSettings';
|
|
||||||
views$room_settings$AliasSettings && (module.exports.components['views.room_settings.AliasSettings'] = views$room_settings$AliasSettings);
|
|
||||||
import views$room_settings$ColorSettings from './components/views/room_settings/ColorSettings';
|
|
||||||
views$room_settings$ColorSettings && (module.exports.components['views.room_settings.ColorSettings'] = views$room_settings$ColorSettings);
|
|
||||||
import views$room_settings$UrlPreviewSettings from './components/views/room_settings/UrlPreviewSettings';
|
|
||||||
views$room_settings$UrlPreviewSettings && (module.exports.components['views.room_settings.UrlPreviewSettings'] = views$room_settings$UrlPreviewSettings);
|
|
||||||
import views$rooms$Autocomplete from './components/views/rooms/Autocomplete';
|
|
||||||
views$rooms$Autocomplete && (module.exports.components['views.rooms.Autocomplete'] = views$rooms$Autocomplete);
|
|
||||||
import views$rooms$AuxPanel from './components/views/rooms/AuxPanel';
|
|
||||||
views$rooms$AuxPanel && (module.exports.components['views.rooms.AuxPanel'] = views$rooms$AuxPanel);
|
|
||||||
import views$rooms$EntityTile from './components/views/rooms/EntityTile';
|
|
||||||
views$rooms$EntityTile && (module.exports.components['views.rooms.EntityTile'] = views$rooms$EntityTile);
|
|
||||||
import views$rooms$EventTile from './components/views/rooms/EventTile';
|
|
||||||
views$rooms$EventTile && (module.exports.components['views.rooms.EventTile'] = views$rooms$EventTile);
|
|
||||||
import views$rooms$LinkPreviewWidget from './components/views/rooms/LinkPreviewWidget';
|
|
||||||
views$rooms$LinkPreviewWidget && (module.exports.components['views.rooms.LinkPreviewWidget'] = views$rooms$LinkPreviewWidget);
|
|
||||||
import views$rooms$MemberDeviceInfo from './components/views/rooms/MemberDeviceInfo';
|
|
||||||
views$rooms$MemberDeviceInfo && (module.exports.components['views.rooms.MemberDeviceInfo'] = views$rooms$MemberDeviceInfo);
|
|
||||||
import views$rooms$MemberInfo from './components/views/rooms/MemberInfo';
|
|
||||||
views$rooms$MemberInfo && (module.exports.components['views.rooms.MemberInfo'] = views$rooms$MemberInfo);
|
|
||||||
import views$rooms$MemberList from './components/views/rooms/MemberList';
|
|
||||||
views$rooms$MemberList && (module.exports.components['views.rooms.MemberList'] = views$rooms$MemberList);
|
|
||||||
import views$rooms$MemberTile from './components/views/rooms/MemberTile';
|
|
||||||
views$rooms$MemberTile && (module.exports.components['views.rooms.MemberTile'] = views$rooms$MemberTile);
|
|
||||||
import views$rooms$MessageComposer from './components/views/rooms/MessageComposer';
|
|
||||||
views$rooms$MessageComposer && (module.exports.components['views.rooms.MessageComposer'] = views$rooms$MessageComposer);
|
|
||||||
import views$rooms$MessageComposerInput from './components/views/rooms/MessageComposerInput';
|
|
||||||
views$rooms$MessageComposerInput && (module.exports.components['views.rooms.MessageComposerInput'] = views$rooms$MessageComposerInput);
|
|
||||||
import views$rooms$MessageComposerInputOld from './components/views/rooms/MessageComposerInputOld';
|
|
||||||
views$rooms$MessageComposerInputOld && (module.exports.components['views.rooms.MessageComposerInputOld'] = views$rooms$MessageComposerInputOld);
|
|
||||||
import views$rooms$PresenceLabel from './components/views/rooms/PresenceLabel';
|
|
||||||
views$rooms$PresenceLabel && (module.exports.components['views.rooms.PresenceLabel'] = views$rooms$PresenceLabel);
|
|
||||||
import views$rooms$ReadReceiptMarker from './components/views/rooms/ReadReceiptMarker';
|
|
||||||
views$rooms$ReadReceiptMarker && (module.exports.components['views.rooms.ReadReceiptMarker'] = views$rooms$ReadReceiptMarker);
|
|
||||||
import views$rooms$RoomHeader from './components/views/rooms/RoomHeader';
|
|
||||||
views$rooms$RoomHeader && (module.exports.components['views.rooms.RoomHeader'] = views$rooms$RoomHeader);
|
|
||||||
import views$rooms$RoomList from './components/views/rooms/RoomList';
|
|
||||||
views$rooms$RoomList && (module.exports.components['views.rooms.RoomList'] = views$rooms$RoomList);
|
|
||||||
import views$rooms$RoomNameEditor from './components/views/rooms/RoomNameEditor';
|
|
||||||
views$rooms$RoomNameEditor && (module.exports.components['views.rooms.RoomNameEditor'] = views$rooms$RoomNameEditor);
|
|
||||||
import views$rooms$RoomPreviewBar from './components/views/rooms/RoomPreviewBar';
|
|
||||||
views$rooms$RoomPreviewBar && (module.exports.components['views.rooms.RoomPreviewBar'] = views$rooms$RoomPreviewBar);
|
|
||||||
import views$rooms$RoomSettings from './components/views/rooms/RoomSettings';
|
|
||||||
views$rooms$RoomSettings && (module.exports.components['views.rooms.RoomSettings'] = views$rooms$RoomSettings);
|
|
||||||
import views$rooms$RoomTile from './components/views/rooms/RoomTile';
|
|
||||||
views$rooms$RoomTile && (module.exports.components['views.rooms.RoomTile'] = views$rooms$RoomTile);
|
|
||||||
import views$rooms$RoomTopicEditor from './components/views/rooms/RoomTopicEditor';
|
|
||||||
views$rooms$RoomTopicEditor && (module.exports.components['views.rooms.RoomTopicEditor'] = views$rooms$RoomTopicEditor);
|
|
||||||
import views$rooms$SearchResultTile from './components/views/rooms/SearchResultTile';
|
|
||||||
views$rooms$SearchResultTile && (module.exports.components['views.rooms.SearchResultTile'] = views$rooms$SearchResultTile);
|
|
||||||
import views$rooms$SearchableEntityList from './components/views/rooms/SearchableEntityList';
|
|
||||||
views$rooms$SearchableEntityList && (module.exports.components['views.rooms.SearchableEntityList'] = views$rooms$SearchableEntityList);
|
|
||||||
import views$rooms$SimpleRoomHeader from './components/views/rooms/SimpleRoomHeader';
|
|
||||||
views$rooms$SimpleRoomHeader && (module.exports.components['views.rooms.SimpleRoomHeader'] = views$rooms$SimpleRoomHeader);
|
|
||||||
import views$rooms$TabCompleteBar from './components/views/rooms/TabCompleteBar';
|
|
||||||
views$rooms$TabCompleteBar && (module.exports.components['views.rooms.TabCompleteBar'] = views$rooms$TabCompleteBar);
|
|
||||||
import views$rooms$TopUnreadMessagesBar from './components/views/rooms/TopUnreadMessagesBar';
|
|
||||||
views$rooms$TopUnreadMessagesBar && (module.exports.components['views.rooms.TopUnreadMessagesBar'] = views$rooms$TopUnreadMessagesBar);
|
|
||||||
import views$rooms$UserTile from './components/views/rooms/UserTile';
|
|
||||||
views$rooms$UserTile && (module.exports.components['views.rooms.UserTile'] = views$rooms$UserTile);
|
|
||||||
import views$settings$AddPhoneNumber from './components/views/settings/AddPhoneNumber';
|
|
||||||
views$settings$AddPhoneNumber && (module.exports.components['views.settings.AddPhoneNumber'] = views$settings$AddPhoneNumber);
|
|
||||||
import views$settings$ChangeAvatar from './components/views/settings/ChangeAvatar';
|
|
||||||
views$settings$ChangeAvatar && (module.exports.components['views.settings.ChangeAvatar'] = views$settings$ChangeAvatar);
|
|
||||||
import views$settings$ChangeDisplayName from './components/views/settings/ChangeDisplayName';
|
|
||||||
views$settings$ChangeDisplayName && (module.exports.components['views.settings.ChangeDisplayName'] = views$settings$ChangeDisplayName);
|
|
||||||
import views$settings$ChangePassword from './components/views/settings/ChangePassword';
|
|
||||||
views$settings$ChangePassword && (module.exports.components['views.settings.ChangePassword'] = views$settings$ChangePassword);
|
|
||||||
import views$settings$DevicesPanel from './components/views/settings/DevicesPanel';
|
|
||||||
views$settings$DevicesPanel && (module.exports.components['views.settings.DevicesPanel'] = views$settings$DevicesPanel);
|
|
||||||
import views$settings$DevicesPanelEntry from './components/views/settings/DevicesPanelEntry';
|
|
||||||
views$settings$DevicesPanelEntry && (module.exports.components['views.settings.DevicesPanelEntry'] = views$settings$DevicesPanelEntry);
|
|
||||||
import views$settings$EnableNotificationsButton from './components/views/settings/EnableNotificationsButton';
|
|
||||||
views$settings$EnableNotificationsButton && (module.exports.components['views.settings.EnableNotificationsButton'] = views$settings$EnableNotificationsButton);
|
|
||||||
import views$voip$CallView from './components/views/voip/CallView';
|
|
||||||
views$voip$CallView && (module.exports.components['views.voip.CallView'] = views$voip$CallView);
|
|
||||||
import views$voip$IncomingCallBox from './components/views/voip/IncomingCallBox';
|
|
||||||
views$voip$IncomingCallBox && (module.exports.components['views.voip.IncomingCallBox'] = views$voip$IncomingCallBox);
|
|
||||||
import views$voip$VideoFeed from './components/views/voip/VideoFeed';
|
|
||||||
views$voip$VideoFeed && (module.exports.components['views.voip.VideoFeed'] = views$voip$VideoFeed);
|
|
||||||
import views$voip$VideoView from './components/views/voip/VideoView';
|
|
||||||
views$voip$VideoView && (module.exports.components['views.voip.VideoView'] = views$voip$VideoView);
|
|
|
@ -16,15 +16,16 @@ limitations under the License.
|
||||||
|
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
var React = require("react");
|
import React from 'react';
|
||||||
var MatrixClientPeg = require("../../MatrixClientPeg");
|
import q from 'q';
|
||||||
var PresetValues = {
|
import { _t } from '../../languageHandler';
|
||||||
|
import sdk from '../../index';
|
||||||
|
import MatrixClientPeg from '../../MatrixClientPeg';
|
||||||
|
const PresetValues = {
|
||||||
PrivateChat: "private_chat",
|
PrivateChat: "private_chat",
|
||||||
PublicChat: "public_chat",
|
PublicChat: "public_chat",
|
||||||
Custom: "custom",
|
Custom: "custom",
|
||||||
};
|
};
|
||||||
var q = require('q');
|
|
||||||
var sdk = require('../../index');
|
|
||||||
|
|
||||||
module.exports = React.createClass({
|
module.exports = React.createClass({
|
||||||
displayName: 'CreateRoom',
|
displayName: 'CreateRoom',
|
||||||
|
@ -231,7 +232,7 @@ module.exports = React.createClass({
|
||||||
if (curr_phase == this.phases.ERROR) {
|
if (curr_phase == this.phases.ERROR) {
|
||||||
error_box = (
|
error_box = (
|
||||||
<div className="mx_Error">
|
<div className="mx_Error">
|
||||||
An error occured: {this.state.error_string}
|
{_t('An error occured: %(error_string)s', {error_string: this.state.error_string})}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -248,27 +249,27 @@ module.exports = React.createClass({
|
||||||
<div className="mx_CreateRoom">
|
<div className="mx_CreateRoom">
|
||||||
<SimpleRoomHeader title="CreateRoom" collapsedRhs={ this.props.collapsedRhs }/>
|
<SimpleRoomHeader title="CreateRoom" collapsedRhs={ this.props.collapsedRhs }/>
|
||||||
<div className="mx_CreateRoom_body">
|
<div className="mx_CreateRoom_body">
|
||||||
<input type="text" ref="room_name" value={this.state.room_name} onChange={this.onNameChange} placeholder="Name"/> <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="Topic"/> <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 />
|
<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 />
|
<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 />
|
<Presets ref="presets" onChange={this.onPresetChanged} preset={this.state.preset}/> <br />
|
||||||
<div>
|
<div>
|
||||||
<label>
|
<label>
|
||||||
<input type="checkbox" ref="is_private" checked={this.state.is_private} onChange={this.onPrivateChanged}/>
|
<input type="checkbox" ref="is_private" checked={this.state.is_private} onChange={this.onPrivateChanged}/>
|
||||||
Make this room private
|
{_t('Make this room private')}
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<label>
|
<label>
|
||||||
<input type="checkbox" ref="share_history" checked={this.state.share_history} onChange={this.onShareHistoryChanged}/>
|
<input type="checkbox" ref="share_history" checked={this.state.share_history} onChange={this.onShareHistoryChanged}/>
|
||||||
Share message history with new users
|
{_t('Share message history with new users')}
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
<div className="mx_CreateRoom_encrypt">
|
<div className="mx_CreateRoom_encrypt">
|
||||||
<label>
|
<label>
|
||||||
<input type="checkbox" ref="encrypt" checked={this.state.encrypt} onChange={this.onEncryptChanged}/>
|
<input type="checkbox" ref="encrypt" checked={this.state.encrypt} onChange={this.onEncryptChanged}/>
|
||||||
Encrypt room
|
{_t('Encrypt room')}
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
|
|
|
@ -14,13 +14,12 @@ See the License for the specific language governing permissions and
|
||||||
limitations under the License.
|
limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
var React = require('react');
|
import React from 'react';
|
||||||
var ReactDOM = require("react-dom");
|
|
||||||
|
|
||||||
var Matrix = require("matrix-js-sdk");
|
import Matrix from 'matrix-js-sdk';
|
||||||
var sdk = require('../../index');
|
import sdk from '../../index';
|
||||||
var MatrixClientPeg = require("../../MatrixClientPeg");
|
import MatrixClientPeg from '../../MatrixClientPeg';
|
||||||
var dis = require("../../dispatcher");
|
import { _t } from '../../languageHandler';
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Component which shows the filtered file using a TimelinePanel
|
* Component which shows the filtered file using a TimelinePanel
|
||||||
|
@ -59,6 +58,8 @@ var FilePanel = React.createClass({
|
||||||
var client = MatrixClientPeg.get();
|
var client = MatrixClientPeg.get();
|
||||||
var room = client.getRoom(roomId);
|
var room = client.getRoom(roomId);
|
||||||
|
|
||||||
|
this.noRoom = !room;
|
||||||
|
|
||||||
if (room) {
|
if (room) {
|
||||||
var filter = new Matrix.Filter(client.credentials.userId);
|
var filter = new Matrix.Filter(client.credentials.userId);
|
||||||
filter.setDefinition(
|
filter.setDefinition(
|
||||||
|
@ -82,13 +83,22 @@ var FilePanel = React.createClass({
|
||||||
console.error("Failed to get or create file panel filter", error);
|
console.error("Failed to get or create file panel filter", error);
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
}
|
} else {
|
||||||
else {
|
|
||||||
console.error("Failed to add filtered timelineSet for FilePanel as no room!");
|
console.error("Failed to add filtered timelineSet for FilePanel as no room!");
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
render: function() {
|
render: function() {
|
||||||
|
if (MatrixClientPeg.get().isGuest()) {
|
||||||
|
return <div className="mx_FilePanel mx_RoomView_messageListWrapper">
|
||||||
|
<div className="mx_RoomView_empty">You must <a href="#/register">register</a> to use this functionality</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>;
|
||||||
|
}
|
||||||
|
|
||||||
// wrap a TimelinePanel with the jump-to-event bits turned off.
|
// wrap a TimelinePanel with the jump-to-event bits turned off.
|
||||||
var TimelinePanel = sdk.getComponent("structures.TimelinePanel");
|
var TimelinePanel = sdk.getComponent("structures.TimelinePanel");
|
||||||
var Loader = sdk.getComponent("elements.Spinner");
|
var Loader = sdk.getComponent("elements.Spinner");
|
||||||
|
@ -105,7 +115,7 @@ var FilePanel = React.createClass({
|
||||||
showUrlPreview = { false }
|
showUrlPreview = { false }
|
||||||
tileShape="file_grid"
|
tileShape="file_grid"
|
||||||
opacity={ this.props.opacity }
|
opacity={ this.props.opacity }
|
||||||
empty="There are no visible files in this room"
|
empty={_t('There are no visible files in this room')}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
/*
|
/*
|
||||||
Copyright 2015, 2016 OpenMarket Ltd
|
Copyright 2015, 2016 OpenMarket Ltd
|
||||||
|
Copyright 2017 Vector Creations Ltd
|
||||||
|
|
||||||
Licensed under the Apache License, Version 2.0 (the "License");
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
you may not use this file except in compliance with the License.
|
you may not use this file except in compliance with the License.
|
||||||
|
@ -17,9 +18,11 @@ limitations under the License.
|
||||||
import * as Matrix from 'matrix-js-sdk';
|
import * as Matrix from 'matrix-js-sdk';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
|
||||||
|
import UserSettingsStore from '../../UserSettingsStore';
|
||||||
import KeyCode from '../../KeyCode';
|
import KeyCode from '../../KeyCode';
|
||||||
import Notifier from '../../Notifier';
|
import Notifier from '../../Notifier';
|
||||||
import PageTypes from '../../PageTypes';
|
import PageTypes from '../../PageTypes';
|
||||||
|
import CallMediaHandler from '../../CallMediaHandler';
|
||||||
import sdk from '../../index';
|
import sdk from '../../index';
|
||||||
import dis from '../../dispatcher';
|
import dis from '../../dispatcher';
|
||||||
|
|
||||||
|
@ -62,6 +65,13 @@ export default React.createClass({
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|
||||||
|
getInitialState: function() {
|
||||||
|
return {
|
||||||
|
// use compact timeline view
|
||||||
|
useCompactLayout: UserSettingsStore.getSyncedSetting('useCompactLayout'),
|
||||||
|
};
|
||||||
|
},
|
||||||
|
|
||||||
componentWillMount: function() {
|
componentWillMount: function() {
|
||||||
// stash the MatrixClient in case we log out before we are unmounted
|
// stash the MatrixClient in case we log out before we are unmounted
|
||||||
this._matrixClient = this.props.matrixClient;
|
this._matrixClient = this.props.matrixClient;
|
||||||
|
@ -70,11 +80,15 @@ export default React.createClass({
|
||||||
// RoomView.getScrollState()
|
// RoomView.getScrollState()
|
||||||
this._scrollStateMap = {};
|
this._scrollStateMap = {};
|
||||||
|
|
||||||
|
CallMediaHandler.loadDevices();
|
||||||
|
|
||||||
document.addEventListener('keydown', this._onKeyDown);
|
document.addEventListener('keydown', this._onKeyDown);
|
||||||
|
this._matrixClient.on("accountData", this.onAccountData);
|
||||||
},
|
},
|
||||||
|
|
||||||
componentWillUnmount: function() {
|
componentWillUnmount: function() {
|
||||||
document.removeEventListener('keydown', this._onKeyDown);
|
document.removeEventListener('keydown', this._onKeyDown);
|
||||||
|
this._matrixClient.removeListener("accountData", this.onAccountData);
|
||||||
},
|
},
|
||||||
|
|
||||||
getScrollStateForRoom: function(roomId) {
|
getScrollStateForRoom: function(roomId) {
|
||||||
|
@ -88,6 +102,14 @@ export default React.createClass({
|
||||||
return this.refs.roomView.canResetTimeline();
|
return this.refs.roomView.canResetTimeline();
|
||||||
},
|
},
|
||||||
|
|
||||||
|
onAccountData: function(event) {
|
||||||
|
if (event.getType() === "im.vector.web.settings") {
|
||||||
|
this.setState({
|
||||||
|
useCompactLayout: event.getContent().useCompactLayout
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
_onKeyDown: function(ev) {
|
_onKeyDown: function(ev) {
|
||||||
/*
|
/*
|
||||||
// Remove this for now as ctrl+alt = alt-gr so this breaks keyboards which rely on alt-gr for numbers
|
// Remove this for now as ctrl+alt = alt-gr so this breaks keyboards which rely on alt-gr for numbers
|
||||||
|
@ -108,7 +130,7 @@ export default React.createClass({
|
||||||
switch (ev.keyCode) {
|
switch (ev.keyCode) {
|
||||||
case KeyCode.UP:
|
case KeyCode.UP:
|
||||||
case KeyCode.DOWN:
|
case KeyCode.DOWN:
|
||||||
if (ev.altKey) {
|
if (ev.altKey && !ev.shiftKey && !ev.ctrlKey && !ev.metaKey) {
|
||||||
var action = ev.keyCode == KeyCode.UP ?
|
var action = ev.keyCode == KeyCode.UP ?
|
||||||
'view_prev_room' : 'view_next_room';
|
'view_prev_room' : 'view_next_room';
|
||||||
dis.dispatch({action: action});
|
dis.dispatch({action: action});
|
||||||
|
@ -118,13 +140,15 @@ export default React.createClass({
|
||||||
|
|
||||||
case KeyCode.PAGE_UP:
|
case KeyCode.PAGE_UP:
|
||||||
case KeyCode.PAGE_DOWN:
|
case KeyCode.PAGE_DOWN:
|
||||||
|
if (!ev.ctrlKey && !ev.shiftKey && !ev.altKey && !ev.metaKey) {
|
||||||
this._onScrollKeyPressed(ev);
|
this._onScrollKeyPressed(ev);
|
||||||
handled = true;
|
handled = true;
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case KeyCode.HOME:
|
case KeyCode.HOME:
|
||||||
case KeyCode.END:
|
case KeyCode.END:
|
||||||
if (ev.ctrlKey) {
|
if (ev.ctrlKey && !ev.shiftKey && !ev.altKey && !ev.metaKey) {
|
||||||
this._onScrollKeyPressed(ev);
|
this._onScrollKeyPressed(ev);
|
||||||
handled = true;
|
handled = true;
|
||||||
}
|
}
|
||||||
|
@ -142,22 +166,25 @@ export default React.createClass({
|
||||||
if (this.refs.roomView) {
|
if (this.refs.roomView) {
|
||||||
this.refs.roomView.handleScrollKey(ev);
|
this.refs.roomView.handleScrollKey(ev);
|
||||||
}
|
}
|
||||||
|
else if (this.refs.roomDirectory) {
|
||||||
|
this.refs.roomDirectory.handleScrollKey(ev);
|
||||||
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
render: function() {
|
render: function() {
|
||||||
var LeftPanel = sdk.getComponent('structures.LeftPanel');
|
const LeftPanel = sdk.getComponent('structures.LeftPanel');
|
||||||
var RightPanel = sdk.getComponent('structures.RightPanel');
|
const RightPanel = sdk.getComponent('structures.RightPanel');
|
||||||
var RoomView = sdk.getComponent('structures.RoomView');
|
const RoomView = sdk.getComponent('structures.RoomView');
|
||||||
var UserSettings = sdk.getComponent('structures.UserSettings');
|
const UserSettings = sdk.getComponent('structures.UserSettings');
|
||||||
var CreateRoom = sdk.getComponent('structures.CreateRoom');
|
const CreateRoom = sdk.getComponent('structures.CreateRoom');
|
||||||
var RoomDirectory = sdk.getComponent('structures.RoomDirectory');
|
const RoomDirectory = sdk.getComponent('structures.RoomDirectory');
|
||||||
var HomePage = sdk.getComponent('structures.HomePage');
|
const HomePage = sdk.getComponent('structures.HomePage');
|
||||||
var MatrixToolbar = sdk.getComponent('globals.MatrixToolbar');
|
const MatrixToolbar = sdk.getComponent('globals.MatrixToolbar');
|
||||||
var GuestWarningBar = sdk.getComponent('globals.GuestWarningBar');
|
const GuestWarningBar = sdk.getComponent('globals.GuestWarningBar');
|
||||||
var NewVersionBar = sdk.getComponent('globals.NewVersionBar');
|
const NewVersionBar = sdk.getComponent('globals.NewVersionBar');
|
||||||
|
|
||||||
var page_element;
|
let page_element;
|
||||||
var right_panel = '';
|
let right_panel = '';
|
||||||
|
|
||||||
switch (this.props.page_type) {
|
switch (this.props.page_type) {
|
||||||
case PageTypes.RoomView:
|
case PageTypes.RoomView:
|
||||||
|
@ -177,7 +204,7 @@ export default React.createClass({
|
||||||
ConferenceHandler={this.props.ConferenceHandler}
|
ConferenceHandler={this.props.ConferenceHandler}
|
||||||
scrollStateMap={this._scrollStateMap}
|
scrollStateMap={this._scrollStateMap}
|
||||||
/>;
|
/>;
|
||||||
if (!this.props.collapse_rhs) right_panel = <RightPanel roomId={this.props.currentRoomId} opacity={this.props.sideOpacity} />;
|
if (!this.props.collapse_rhs) right_panel = <RightPanel roomId={this.props.currentRoomId} opacity={this.props.rightOpacity} />;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case PageTypes.UserSettings:
|
case PageTypes.UserSettings:
|
||||||
|
@ -189,7 +216,7 @@ export default React.createClass({
|
||||||
referralBaseUrl={this.props.config.referralBaseUrl}
|
referralBaseUrl={this.props.config.referralBaseUrl}
|
||||||
teamToken={this.props.teamToken}
|
teamToken={this.props.teamToken}
|
||||||
/>;
|
/>;
|
||||||
if (!this.props.collapse_rhs) right_panel = <RightPanel opacity={this.props.sideOpacity}/>;
|
if (!this.props.collapse_rhs) right_panel = <RightPanel opacity={this.props.rightOpacity}/>;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case PageTypes.CreateRoom:
|
case PageTypes.CreateRoom:
|
||||||
|
@ -197,15 +224,14 @@ export default React.createClass({
|
||||||
onRoomCreated={this.props.onRoomCreated}
|
onRoomCreated={this.props.onRoomCreated}
|
||||||
collapsedRhs={this.props.collapse_rhs}
|
collapsedRhs={this.props.collapse_rhs}
|
||||||
/>;
|
/>;
|
||||||
if (!this.props.collapse_rhs) right_panel = <RightPanel opacity={this.props.sideOpacity}/>;
|
if (!this.props.collapse_rhs) right_panel = <RightPanel opacity={this.props.rightOpacity}/>;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case PageTypes.RoomDirectory:
|
case PageTypes.RoomDirectory:
|
||||||
page_element = <RoomDirectory
|
page_element = <RoomDirectory
|
||||||
collapsedRhs={this.props.collapse_rhs}
|
ref="roomDirectory"
|
||||||
config={this.props.config.roomDirectory}
|
config={this.props.config.roomDirectory}
|
||||||
/>;
|
/>;
|
||||||
if (!this.props.collapse_rhs) right_panel = <RightPanel opacity={this.props.sideOpacity}/>;
|
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case PageTypes.HomePage:
|
case PageTypes.HomePage:
|
||||||
|
@ -214,12 +240,12 @@ export default React.createClass({
|
||||||
teamServerUrl={this.props.config.teamServerConfig.teamServerURL}
|
teamServerUrl={this.props.config.teamServerConfig.teamServerURL}
|
||||||
teamToken={this.props.teamToken}
|
teamToken={this.props.teamToken}
|
||||||
/>
|
/>
|
||||||
if (!this.props.collapse_rhs) right_panel = <RightPanel opacity={this.props.sideOpacity}/>
|
if (!this.props.collapse_rhs) right_panel = <RightPanel opacity={this.props.rightOpacity}/>
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case PageTypes.UserView:
|
case PageTypes.UserView:
|
||||||
page_element = null; // deliberately null for now
|
page_element = null; // deliberately null for now
|
||||||
right_panel = <RightPanel userId={this.props.viewUserId} opacity={this.props.sideOpacity} />;
|
right_panel = <RightPanel userId={this.props.viewUserId} opacity={this.props.rightOpacity} />;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -240,6 +266,9 @@ export default React.createClass({
|
||||||
if (topBar) {
|
if (topBar) {
|
||||||
bodyClasses += ' mx_MatrixChat_toolbarShowing';
|
bodyClasses += ' mx_MatrixChat_toolbarShowing';
|
||||||
}
|
}
|
||||||
|
if (this.state.useCompactLayout) {
|
||||||
|
bodyClasses += ' mx_MatrixChat_useCompactLayout';
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className='mx_MatrixChat_wrapper'>
|
<div className='mx_MatrixChat_wrapper'>
|
||||||
|
@ -248,7 +277,7 @@ export default React.createClass({
|
||||||
<LeftPanel
|
<LeftPanel
|
||||||
selectedRoom={this.props.currentRoomId}
|
selectedRoom={this.props.currentRoomId}
|
||||||
collapsed={this.props.collapse_lhs || false}
|
collapsed={this.props.collapse_lhs || false}
|
||||||
opacity={this.props.sideOpacity}
|
opacity={this.props.leftOpacity}
|
||||||
teamToken={this.props.teamToken}
|
teamToken={this.props.teamToken}
|
||||||
/>
|
/>
|
||||||
<main className='mx_MatrixChat_middlePanel'>
|
<main className='mx_MatrixChat_middlePanel'>
|
||||||
|
|
|
@ -17,28 +17,28 @@ limitations under the License.
|
||||||
|
|
||||||
import q from 'q';
|
import q from 'q';
|
||||||
|
|
||||||
var React = require('react');
|
import React from 'react';
|
||||||
var Matrix = require("matrix-js-sdk");
|
import Matrix from "matrix-js-sdk";
|
||||||
|
|
||||||
var MatrixClientPeg = require("../../MatrixClientPeg");
|
import Analytics from "../../Analytics";
|
||||||
var PlatformPeg = require("../../PlatformPeg");
|
import UserSettingsStore from '../../UserSettingsStore';
|
||||||
var SdkConfig = require("../../SdkConfig");
|
import MatrixClientPeg from "../../MatrixClientPeg";
|
||||||
var ContextualMenu = require("./ContextualMenu");
|
import PlatformPeg from "../../PlatformPeg";
|
||||||
var RoomListSorter = require("../../RoomListSorter");
|
import SdkConfig from "../../SdkConfig";
|
||||||
var UserActivity = require("../../UserActivity");
|
import * as RoomListSorter from "../../RoomListSorter";
|
||||||
var Presence = require("../../Presence");
|
import dis from "../../dispatcher";
|
||||||
var dis = require("../../dispatcher");
|
|
||||||
|
|
||||||
var Modal = require("../../Modal");
|
import Modal from "../../Modal";
|
||||||
var Tinter = require("../../Tinter");
|
import Tinter from "../../Tinter";
|
||||||
var sdk = require('../../index');
|
import sdk from '../../index';
|
||||||
var Rooms = require('../../Rooms');
|
import * as Rooms from '../../Rooms';
|
||||||
var linkifyMatrix = require("../../linkify-matrix");
|
import linkifyMatrix from "../../linkify-matrix";
|
||||||
var Lifecycle = require('../../Lifecycle');
|
import * as Lifecycle from '../../Lifecycle';
|
||||||
var PageTypes = require('../../PageTypes');
|
import PageTypes from '../../PageTypes';
|
||||||
|
|
||||||
var createRoom = require("../../createRoom");
|
import createRoom from "../../createRoom";
|
||||||
import * as UDEHandler from '../../UnknownDeviceErrorHandler';
|
import * as UDEHandler from '../../UnknownDeviceErrorHandler';
|
||||||
|
import { _t, getCurrentLanguage } from '../../languageHandler';
|
||||||
|
|
||||||
module.exports = React.createClass({
|
module.exports = React.createClass({
|
||||||
displayName: 'MatrixChat',
|
displayName: 'MatrixChat',
|
||||||
|
@ -89,7 +89,7 @@ module.exports = React.createClass({
|
||||||
},
|
},
|
||||||
|
|
||||||
getInitialState: function() {
|
getInitialState: function() {
|
||||||
var s = {
|
const s = {
|
||||||
loading: true,
|
loading: true,
|
||||||
screen: undefined,
|
screen: undefined,
|
||||||
screenAfterLogin: this.props.initialScreenAfterLogin,
|
screenAfterLogin: this.props.initialScreenAfterLogin,
|
||||||
|
@ -119,8 +119,9 @@ module.exports = React.createClass({
|
||||||
collapse_rhs: false,
|
collapse_rhs: false,
|
||||||
ready: false,
|
ready: false,
|
||||||
width: 10000,
|
width: 10000,
|
||||||
sideOpacity: 1.0,
|
leftOpacity: 1.0,
|
||||||
middleOpacity: 1.0,
|
middleOpacity: 1.0,
|
||||||
|
rightOpacity: 1.0,
|
||||||
|
|
||||||
version: null,
|
version: null,
|
||||||
newVersion: null,
|
newVersion: null,
|
||||||
|
@ -156,11 +157,9 @@ module.exports = React.createClass({
|
||||||
return this.state.register_hs_url;
|
return this.state.register_hs_url;
|
||||||
} else if (MatrixClientPeg.get()) {
|
} else if (MatrixClientPeg.get()) {
|
||||||
return MatrixClientPeg.get().getHomeserverUrl();
|
return MatrixClientPeg.get().getHomeserverUrl();
|
||||||
}
|
} else if (window.localStorage && window.localStorage.getItem("mx_hs_url")) {
|
||||||
else if (window.localStorage && window.localStorage.getItem("mx_hs_url")) {
|
|
||||||
return window.localStorage.getItem("mx_hs_url");
|
return window.localStorage.getItem("mx_hs_url");
|
||||||
}
|
} else {
|
||||||
else {
|
|
||||||
return this.getDefaultHsUrl();
|
return this.getDefaultHsUrl();
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -178,11 +177,9 @@ module.exports = React.createClass({
|
||||||
return this.state.register_is_url;
|
return this.state.register_is_url;
|
||||||
} else if (MatrixClientPeg.get()) {
|
} else if (MatrixClientPeg.get()) {
|
||||||
return MatrixClientPeg.get().getIdentityServerUrl();
|
return MatrixClientPeg.get().getIdentityServerUrl();
|
||||||
}
|
} else if (window.localStorage && window.localStorage.getItem("mx_is_url")) {
|
||||||
else if (window.localStorage && window.localStorage.getItem("mx_is_url")) {
|
|
||||||
return window.localStorage.getItem("mx_is_url");
|
return window.localStorage.getItem("mx_is_url");
|
||||||
}
|
} else {
|
||||||
else {
|
|
||||||
return this.getDefaultIsUrl();
|
return this.getDefaultIsUrl();
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -194,6 +191,8 @@ module.exports = React.createClass({
|
||||||
componentWillMount: function() {
|
componentWillMount: function() {
|
||||||
SdkConfig.put(this.props.config);
|
SdkConfig.put(this.props.config);
|
||||||
|
|
||||||
|
if (!UserSettingsStore.getLocalSetting('analyticsOptOut', false)) Analytics.enable();
|
||||||
|
|
||||||
// Used by _viewRoom before getting state from sync
|
// Used by _viewRoom before getting state from sync
|
||||||
this.firstSyncComplete = false;
|
this.firstSyncComplete = false;
|
||||||
this.firstSyncPromise = q.defer();
|
this.firstSyncPromise = q.defer();
|
||||||
|
@ -253,7 +252,6 @@ module.exports = React.createClass({
|
||||||
UDEHandler.startListening();
|
UDEHandler.startListening();
|
||||||
|
|
||||||
this.focusComposer = false;
|
this.focusComposer = false;
|
||||||
window.addEventListener("focus", this.onFocus);
|
|
||||||
|
|
||||||
// this can technically be done anywhere but doing this here keeps all
|
// this can technically be done anywhere but doing this here keeps all
|
||||||
// the routing url path logic together.
|
// the routing url path logic together.
|
||||||
|
@ -324,28 +322,14 @@ module.exports = React.createClass({
|
||||||
onAction: function(payload) {
|
onAction: function(payload) {
|
||||||
const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
|
const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
|
||||||
const QuestionDialog = sdk.getComponent("dialogs.QuestionDialog");
|
const QuestionDialog = sdk.getComponent("dialogs.QuestionDialog");
|
||||||
var roomIndexDelta = 1;
|
const TextInputDialog = sdk.getComponent("dialogs.TextInputDialog");
|
||||||
|
|
||||||
var self = this;
|
|
||||||
switch (payload.action) {
|
switch (payload.action) {
|
||||||
case 'logout':
|
case 'logout':
|
||||||
Lifecycle.logout();
|
Lifecycle.logout();
|
||||||
break;
|
break;
|
||||||
case 'start_registration':
|
case 'start_registration':
|
||||||
const params = payload.params || {};
|
this._startRegistration(payload.params || {});
|
||||||
this.setStateForNewScreen({
|
|
||||||
screen: 'register',
|
|
||||||
// these params may be undefined, but if they are,
|
|
||||||
// unset them from our state: we don't want to
|
|
||||||
// resume a previous registration session if the
|
|
||||||
// user just clicked 'register'
|
|
||||||
register_client_secret: params.client_secret,
|
|
||||||
register_session_id: params.session_id,
|
|
||||||
register_hs_url: params.hs_url,
|
|
||||||
register_is_url: params.is_url,
|
|
||||||
register_id_sid: params.sid,
|
|
||||||
});
|
|
||||||
this.notifyNewScreen('register');
|
|
||||||
break;
|
break;
|
||||||
case 'start_login':
|
case 'start_login':
|
||||||
if (MatrixClientPeg.get() &&
|
if (MatrixClientPeg.get() &&
|
||||||
|
@ -362,7 +346,7 @@ module.exports = React.createClass({
|
||||||
break;
|
break;
|
||||||
case 'start_post_registration':
|
case 'start_post_registration':
|
||||||
this.setState({ // don't clobber loggedIn status
|
this.setState({ // don't clobber loggedIn status
|
||||||
screen: 'post_registration'
|
screen: 'post_registration',
|
||||||
});
|
});
|
||||||
break;
|
break;
|
||||||
case 'start_upgrade_registration':
|
case 'start_upgrade_registration':
|
||||||
|
@ -385,45 +369,18 @@ module.exports = React.createClass({
|
||||||
this.notifyNewScreen('register');
|
this.notifyNewScreen('register');
|
||||||
break;
|
break;
|
||||||
case 'start_password_recovery':
|
case 'start_password_recovery':
|
||||||
if (this.state.loggedIn) return;
|
|
||||||
this.setStateForNewScreen({
|
this.setStateForNewScreen({
|
||||||
screen: 'forgot_password',
|
screen: 'forgot_password',
|
||||||
});
|
});
|
||||||
this.notifyNewScreen('forgot_password');
|
this.notifyNewScreen('forgot_password');
|
||||||
break;
|
break;
|
||||||
case 'leave_room':
|
case 'leave_room':
|
||||||
Modal.createDialog(QuestionDialog, {
|
this._leaveRoom(payload.room_id);
|
||||||
title: "Leave room",
|
|
||||||
description: "Are you sure you want to leave the room?",
|
|
||||||
onFinished: (should_leave) => {
|
|
||||||
if (should_leave) {
|
|
||||||
const d = MatrixClientPeg.get().leave(payload.room_id);
|
|
||||||
|
|
||||||
// FIXME: controller shouldn't be loading a view :(
|
|
||||||
const Loader = sdk.getComponent("elements.Spinner");
|
|
||||||
const modal = Modal.createDialog(Loader, null, 'mx_Dialog_spinner');
|
|
||||||
|
|
||||||
d.then(() => {
|
|
||||||
modal.close();
|
|
||||||
if (this.currentRoomId === payload.room_id) {
|
|
||||||
dis.dispatch({action: 'view_next_room'});
|
|
||||||
}
|
|
||||||
}, (err) => {
|
|
||||||
modal.close();
|
|
||||||
console.error("Failed to leave room " + payload.room_id + " " + err);
|
|
||||||
Modal.createDialog(ErrorDialog, {
|
|
||||||
title: "Failed to leave room",
|
|
||||||
description: "Server may be unavailable, overloaded, or you hit a bug."
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
break;
|
break;
|
||||||
case 'reject_invite':
|
case 'reject_invite':
|
||||||
Modal.createDialog(QuestionDialog, {
|
Modal.createDialog(QuestionDialog, {
|
||||||
title: "Reject invitation",
|
title: _t('Reject invitation'),
|
||||||
description: "Are you sure you want to reject the invitation?",
|
description: _t('Are you sure you want to reject the invitation?'),
|
||||||
onFinished: (confirm) => {
|
onFinished: (confirm) => {
|
||||||
if (confirm) {
|
if (confirm) {
|
||||||
// FIXME: controller shouldn't be loading a view :(
|
// FIXME: controller shouldn't be loading a view :(
|
||||||
|
@ -438,12 +395,12 @@ module.exports = React.createClass({
|
||||||
}, (err) => {
|
}, (err) => {
|
||||||
modal.close();
|
modal.close();
|
||||||
Modal.createDialog(ErrorDialog, {
|
Modal.createDialog(ErrorDialog, {
|
||||||
title: "Failed to reject invitation",
|
title: _t('Failed to reject invitation'),
|
||||||
description: err.toString()
|
description: err.toString(),
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
});
|
});
|
||||||
break;
|
break;
|
||||||
case 'view_user':
|
case 'view_user':
|
||||||
|
@ -468,30 +425,13 @@ module.exports = React.createClass({
|
||||||
this._viewRoom(payload);
|
this._viewRoom(payload);
|
||||||
break;
|
break;
|
||||||
case 'view_prev_room':
|
case 'view_prev_room':
|
||||||
roomIndexDelta = -1;
|
this._viewNextRoom(-1);
|
||||||
case 'view_next_room':
|
|
||||||
var allRooms = RoomListSorter.mostRecentActivityFirst(
|
|
||||||
MatrixClientPeg.get().getRooms()
|
|
||||||
);
|
|
||||||
var roomIndex = -1;
|
|
||||||
for (var i = 0; i < allRooms.length; ++i) {
|
|
||||||
if (allRooms[i].roomId == this.state.currentRoomId) {
|
|
||||||
roomIndex = i;
|
|
||||||
break;
|
break;
|
||||||
}
|
case 'view_next_room':
|
||||||
}
|
this._viewNextRoom(1);
|
||||||
roomIndex = (roomIndex + roomIndexDelta) % allRooms.length;
|
|
||||||
if (roomIndex < 0) roomIndex = allRooms.length - 1;
|
|
||||||
this._viewRoom({ room_id: allRooms[roomIndex].roomId });
|
|
||||||
break;
|
break;
|
||||||
case 'view_indexed_room':
|
case 'view_indexed_room':
|
||||||
var allRooms = RoomListSorter.mostRecentActivityFirst(
|
this._viewIndexedRoom(payload.roomIndex);
|
||||||
MatrixClientPeg.get().getRooms()
|
|
||||||
);
|
|
||||||
var roomIndex = payload.roomIndex;
|
|
||||||
if (allRooms[roomIndex]) {
|
|
||||||
this._viewRoom({ room_id: allRooms[roomIndex].roomId });
|
|
||||||
}
|
|
||||||
break;
|
break;
|
||||||
case 'view_user_settings':
|
case 'view_user_settings':
|
||||||
this._setPage(PageTypes.UserSettings);
|
this._setPage(PageTypes.UserSettings);
|
||||||
|
@ -500,19 +440,17 @@ module.exports = React.createClass({
|
||||||
case 'view_create_room':
|
case 'view_create_room':
|
||||||
//this._setPage(PageTypes.CreateRoom);
|
//this._setPage(PageTypes.CreateRoom);
|
||||||
//this.notifyNewScreen('new');
|
//this.notifyNewScreen('new');
|
||||||
|
|
||||||
var TextInputDialog = sdk.getComponent("dialogs.TextInputDialog");
|
|
||||||
Modal.createDialog(TextInputDialog, {
|
Modal.createDialog(TextInputDialog, {
|
||||||
title: "Create Room",
|
title: _t('Create Room'),
|
||||||
description: "Room name (optional)",
|
description: _t('Room name (optional)'),
|
||||||
button: "Create Room",
|
button: _t('Create Room'),
|
||||||
onFinished: (should_create, name) => {
|
onFinished: (shouldCreate, name) => {
|
||||||
if (should_create) {
|
if (shouldCreate) {
|
||||||
const createOpts = {};
|
const createOpts = {};
|
||||||
if (name) createOpts.name = name;
|
if (name) createOpts.name = name;
|
||||||
createRoom({createOpts}).done();
|
createRoom({createOpts}).done();
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
});
|
});
|
||||||
break;
|
break;
|
||||||
case 'view_room_directory':
|
case 'view_room_directory':
|
||||||
|
@ -556,12 +494,14 @@ module.exports = React.createClass({
|
||||||
collapse_rhs: false,
|
collapse_rhs: false,
|
||||||
});
|
});
|
||||||
break;
|
break;
|
||||||
case 'ui_opacity':
|
case 'ui_opacity': {
|
||||||
|
const sideDefault = payload.sideOpacity >= 0.0 ? payload.sideOpacity : 1.0;
|
||||||
this.setState({
|
this.setState({
|
||||||
sideOpacity: payload.sideOpacity,
|
leftOpacity: payload.leftOpacity >= 0.0 ? payload.leftOpacity : sideDefault,
|
||||||
middleOpacity: payload.middleOpacity,
|
middleOpacity: payload.middleOpacity || 1.0,
|
||||||
|
rightOpacity: payload.rightOpacity >= 0.0 ? payload.rightOpacity : sideDefault,
|
||||||
});
|
});
|
||||||
break;
|
break; }
|
||||||
case 'set_theme':
|
case 'set_theme':
|
||||||
this._onSetTheme(payload.value);
|
this._onSetTheme(payload.value);
|
||||||
break;
|
break;
|
||||||
|
@ -583,7 +523,7 @@ module.exports = React.createClass({
|
||||||
case 'new_version':
|
case 'new_version':
|
||||||
this.onVersion(
|
this.onVersion(
|
||||||
payload.currentVersion, payload.newVersion,
|
payload.currentVersion, payload.newVersion,
|
||||||
payload.releaseNotes
|
payload.releaseNotes,
|
||||||
);
|
);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
@ -595,46 +535,87 @@ module.exports = React.createClass({
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
|
_startRegistration: function(params) {
|
||||||
|
this.setStateForNewScreen({
|
||||||
|
screen: 'register',
|
||||||
|
// these params may be undefined, but if they are,
|
||||||
|
// unset them from our state: we don't want to
|
||||||
|
// resume a previous registration session if the
|
||||||
|
// user just clicked 'register'
|
||||||
|
register_client_secret: params.client_secret,
|
||||||
|
register_session_id: params.session_id,
|
||||||
|
register_hs_url: params.hs_url,
|
||||||
|
register_is_url: params.is_url,
|
||||||
|
register_id_sid: params.sid,
|
||||||
|
});
|
||||||
|
this.notifyNewScreen('register');
|
||||||
|
},
|
||||||
|
|
||||||
|
_viewNextRoom: function(roomIndexDelta) {
|
||||||
|
const allRooms = RoomListSorter.mostRecentActivityFirst(
|
||||||
|
MatrixClientPeg.get().getRooms(),
|
||||||
|
);
|
||||||
|
let roomIndex = -1;
|
||||||
|
for (let i = 0; i < allRooms.length; ++i) {
|
||||||
|
if (allRooms[i].roomId == this.state.currentRoomId) {
|
||||||
|
roomIndex = i;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
roomIndex = (roomIndex + roomIndexDelta) % allRooms.length;
|
||||||
|
if (roomIndex < 0) roomIndex = allRooms.length - 1;
|
||||||
|
this._viewRoom({ room_id: allRooms[roomIndex].roomId });
|
||||||
|
},
|
||||||
|
|
||||||
|
_viewIndexedRoom: function(roomIndex) {
|
||||||
|
const allRooms = RoomListSorter.mostRecentActivityFirst(
|
||||||
|
MatrixClientPeg.get().getRooms(),
|
||||||
|
);
|
||||||
|
if (allRooms[roomIndex]) {
|
||||||
|
this._viewRoom({ room_id: allRooms[roomIndex].roomId });
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
// switch view to the given room
|
// switch view to the given room
|
||||||
//
|
//
|
||||||
// @param {Object} room_info Object containing data about the room to be joined
|
// @param {Object} roomInfo Object containing data about the room to be joined
|
||||||
// @param {string=} room_info.room_id ID of the room to join. One of room_id or room_alias must be given.
|
// @param {string=} roomInfo.room_id ID of the room to join. One of room_id or room_alias must be given.
|
||||||
// @param {string=} room_info.room_alias Alias of the room to join. One of room_id or room_alias must be given.
|
// @param {string=} roomInfo.room_alias Alias of the room to join. One of room_id or room_alias must be given.
|
||||||
// @param {boolean=} room_info.auto_join If true, automatically attempt to join the room if not already a member.
|
// @param {boolean=} roomInfo.auto_join If true, automatically attempt to join the room if not already a member.
|
||||||
// @param {boolean=} room_info.show_settings Makes RoomView show the room settings dialog.
|
// @param {boolean=} roomInfo.show_settings Makes RoomView show the room settings dialog.
|
||||||
// @param {string=} room_info.event_id ID of the event in this room to show: this will cause a switch to the
|
// @param {string=} roomInfo.event_id ID of the event in this room to show: this will cause a switch to the
|
||||||
// context of that particular event.
|
// context of that particular event.
|
||||||
// @param {Object=} room_info.third_party_invite Object containing data about the third party
|
// @param {Object=} roomInfo.third_party_invite Object containing data about the third party
|
||||||
// we received to join the room, if any.
|
// we received to join the room, if any.
|
||||||
// @param {string=} room_info.third_party_invite.inviteSignUrl 3pid invite sign URL
|
// @param {string=} roomInfo.third_party_invite.inviteSignUrl 3pid invite sign URL
|
||||||
// @param {string=} room_info.third_party_invite.invitedEmail The email address the invite was sent to
|
// @param {string=} roomInfo.third_party_invite.invitedEmail The email address the invite was sent to
|
||||||
// @param {Object=} room_info.oob_data Object of additional data about the room
|
// @param {Object=} roomInfo.oob_data Object of additional data about the room
|
||||||
// that has been passed out-of-band (eg.
|
// that has been passed out-of-band (eg.
|
||||||
// room name and avatar from an invite email)
|
// room name and avatar from an invite email)
|
||||||
_viewRoom: function(room_info) {
|
_viewRoom: function(roomInfo) {
|
||||||
this.focusComposer = true;
|
this.focusComposer = true;
|
||||||
|
|
||||||
var newState = {
|
const newState = {
|
||||||
initialEventId: room_info.event_id,
|
initialEventId: roomInfo.event_id,
|
||||||
highlightedEventId: room_info.event_id,
|
highlightedEventId: roomInfo.event_id,
|
||||||
initialEventPixelOffset: undefined,
|
initialEventPixelOffset: undefined,
|
||||||
page_type: PageTypes.RoomView,
|
page_type: PageTypes.RoomView,
|
||||||
thirdPartyInvite: room_info.third_party_invite,
|
thirdPartyInvite: roomInfo.third_party_invite,
|
||||||
roomOobData: room_info.oob_data,
|
roomOobData: roomInfo.oob_data,
|
||||||
currentRoomAlias: room_info.room_alias,
|
currentRoomAlias: roomInfo.room_alias,
|
||||||
autoJoin: room_info.auto_join,
|
autoJoin: roomInfo.auto_join,
|
||||||
};
|
};
|
||||||
|
|
||||||
if (!room_info.room_alias) {
|
if (!roomInfo.room_alias) {
|
||||||
newState.currentRoomId = room_info.room_id;
|
newState.currentRoomId = roomInfo.room_id;
|
||||||
}
|
}
|
||||||
|
|
||||||
// if we aren't given an explicit event id, look for one in the
|
// if we aren't given an explicit event id, look for one in the
|
||||||
// scrollStateMap.
|
// scrollStateMap.
|
||||||
//
|
//
|
||||||
// TODO: do this in RoomView rather than here
|
// TODO: do this in RoomView rather than here
|
||||||
if (!room_info.event_id && this.refs.loggedInView) {
|
if (!roomInfo.event_id && this.refs.loggedInView) {
|
||||||
var scrollState = this.refs.loggedInView.getScrollStateForRoom(room_info.room_id);
|
const scrollState = this.refs.loggedInView.getScrollStateForRoom(roomInfo.room_id);
|
||||||
if (scrollState) {
|
if (scrollState) {
|
||||||
newState.initialEventId = scrollState.focussedEvent;
|
newState.initialEventId = scrollState.focussedEvent;
|
||||||
newState.initialEventPixelOffset = scrollState.pixelOffset;
|
newState.initialEventPixelOffset = scrollState.pixelOffset;
|
||||||
|
@ -646,15 +627,15 @@ module.exports = React.createClass({
|
||||||
let waitFor = q(null);
|
let waitFor = q(null);
|
||||||
if (!this.firstSyncComplete) {
|
if (!this.firstSyncComplete) {
|
||||||
if (!this.firstSyncPromise) {
|
if (!this.firstSyncPromise) {
|
||||||
console.warn('Cannot view a room before first sync. room_id:', room_info.room_id);
|
console.warn('Cannot view a room before first sync. room_id:', roomInfo.room_id);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
waitFor = this.firstSyncPromise.promise;
|
waitFor = this.firstSyncPromise.promise;
|
||||||
}
|
}
|
||||||
|
|
||||||
waitFor.done(() => {
|
waitFor.done(() => {
|
||||||
let presentedId = room_info.room_alias || room_info.room_id;
|
let presentedId = roomInfo.room_alias || roomInfo.room_id;
|
||||||
const room = MatrixClientPeg.get().getRoom(room_info.room_id);
|
const room = MatrixClientPeg.get().getRoom(roomInfo.room_id);
|
||||||
if (room) {
|
if (room) {
|
||||||
const theAlias = Rooms.getDisplayAliasForRoom(room);
|
const theAlias = Rooms.getDisplayAliasForRoom(room);
|
||||||
if (theAlias) presentedId = theAlias;
|
if (theAlias) presentedId = theAlias;
|
||||||
|
@ -666,8 +647,8 @@ module.exports = React.createClass({
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (room_info.event_id) {
|
if (roomInfo.event_id) {
|
||||||
presentedId += "/" + room_info.event_id;
|
presentedId += "/" + roomInfo.event_id;
|
||||||
}
|
}
|
||||||
this.notifyNewScreen('room/' + presentedId);
|
this.notifyNewScreen('room/' + presentedId);
|
||||||
newState.ready = true;
|
newState.ready = true;
|
||||||
|
@ -676,22 +657,65 @@ module.exports = React.createClass({
|
||||||
},
|
},
|
||||||
|
|
||||||
_createChat: function() {
|
_createChat: function() {
|
||||||
var ChatInviteDialog = sdk.getComponent("dialogs.ChatInviteDialog");
|
const ChatInviteDialog = sdk.getComponent("dialogs.ChatInviteDialog");
|
||||||
Modal.createDialog(ChatInviteDialog, {
|
Modal.createDialog(ChatInviteDialog, {
|
||||||
title: "Start a new chat",
|
title: _t('Start a chat'),
|
||||||
|
description: _t("Who would you like to communicate with?"),
|
||||||
|
placeholder: _t("Email, name or matrix ID"),
|
||||||
|
button: _t("Start Chat"),
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
_invite: function(roomId) {
|
_invite: function(roomId) {
|
||||||
var ChatInviteDialog = sdk.getComponent("dialogs.ChatInviteDialog");
|
const ChatInviteDialog = sdk.getComponent("dialogs.ChatInviteDialog");
|
||||||
Modal.createDialog(ChatInviteDialog, {
|
Modal.createDialog(ChatInviteDialog, {
|
||||||
title: "Invite new room members",
|
title: _t('Invite new room members'),
|
||||||
button: "Send Invites",
|
description: _t('Who would you like to add to this room?'),
|
||||||
description: "Who would you like to add to this room?",
|
button: _t('Send Invites'),
|
||||||
|
placeholder: _t("Email, name or matrix ID"),
|
||||||
roomId: roomId,
|
roomId: roomId,
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
|
_leaveRoom: function(roomId) {
|
||||||
|
const QuestionDialog = sdk.getComponent("dialogs.QuestionDialog");
|
||||||
|
const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
|
||||||
|
|
||||||
|
const roomToLeave = MatrixClientPeg.get().getRoom(roomId);
|
||||||
|
Modal.createDialog(QuestionDialog, {
|
||||||
|
title: _t("Leave room"),
|
||||||
|
description: (
|
||||||
|
<span>
|
||||||
|
{_t("Are you sure you want to leave the room '%(roomName)s'?", {roomName: roomToLeave.name})}
|
||||||
|
</span>
|
||||||
|
),
|
||||||
|
onFinished: (shouldLeave) => {
|
||||||
|
if (shouldLeave) {
|
||||||
|
const d = MatrixClientPeg.get().leave(roomId);
|
||||||
|
|
||||||
|
// FIXME: controller shouldn't be loading a view :(
|
||||||
|
const Loader = sdk.getComponent("elements.Spinner");
|
||||||
|
const modal = Modal.createDialog(Loader, null, 'mx_Dialog_spinner');
|
||||||
|
|
||||||
|
d.then(() => {
|
||||||
|
modal.close();
|
||||||
|
if (this.currentRoomId === roomId) {
|
||||||
|
dis.dispatch({action: 'view_next_room'});
|
||||||
|
}
|
||||||
|
}, (err) => {
|
||||||
|
modal.close();
|
||||||
|
console.error("Failed to leave room " + roomId + " " + err);
|
||||||
|
Modal.createDialog(ErrorDialog, {
|
||||||
|
title: _t("Failed to leave room"),
|
||||||
|
description: (err && err.message ? err.message :
|
||||||
|
_t("Server may be unavailable, overloaded, or you hit a bug.")),
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Called when the sessionloader has finished
|
* Called when the sessionloader has finished
|
||||||
*/
|
*/
|
||||||
|
@ -710,6 +734,8 @@ module.exports = React.createClass({
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Called whenever someone changes the theme
|
* Called whenever someone changes the theme
|
||||||
|
*
|
||||||
|
* @param {string} theme new theme
|
||||||
*/
|
*/
|
||||||
_onSetTheme: function(theme) {
|
_onSetTheme: function(theme) {
|
||||||
if (!theme) {
|
if (!theme) {
|
||||||
|
@ -718,12 +744,12 @@ module.exports = React.createClass({
|
||||||
|
|
||||||
// look for the stylesheet elements.
|
// look for the stylesheet elements.
|
||||||
// styleElements is a map from style name to HTMLLinkElement.
|
// styleElements is a map from style name to HTMLLinkElement.
|
||||||
var styleElements = Object.create(null);
|
const styleElements = Object.create(null);
|
||||||
var i, a;
|
let a;
|
||||||
for (i = 0; (a = document.getElementsByTagName("link")[i]); i++) {
|
for (let i = 0; (a = document.getElementsByTagName("link")[i]); i++) {
|
||||||
var href = a.getAttribute("href");
|
const href = a.getAttribute("href");
|
||||||
// shouldn't we be using the 'title' tag rather than the href?
|
// shouldn't we be using the 'title' tag rather than the href?
|
||||||
var match = href.match(/^bundles\/.*\/theme-(.*)\.css$/);
|
const match = href.match(/^bundles\/.*\/theme-(.*)\.css$/);
|
||||||
if (match) {
|
if (match) {
|
||||||
styleElements[match[1]] = a;
|
styleElements[match[1]] = a;
|
||||||
}
|
}
|
||||||
|
@ -746,14 +772,15 @@ module.exports = React.createClass({
|
||||||
// abuse the tinter to change all the SVG's #fff to #2d2d2d
|
// abuse the tinter to change all the SVG's #fff to #2d2d2d
|
||||||
// XXX: obviously this shouldn't be hardcoded here.
|
// XXX: obviously this shouldn't be hardcoded here.
|
||||||
Tinter.tintSvgWhite('#2d2d2d');
|
Tinter.tintSvgWhite('#2d2d2d');
|
||||||
}
|
} else {
|
||||||
else {
|
|
||||||
Tinter.tintSvgWhite('#ffffff');
|
Tinter.tintSvgWhite('#ffffff');
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Called when a new logged in session has started
|
* Called when a new logged in session has started
|
||||||
|
*
|
||||||
|
* @param {string} teamToken
|
||||||
*/
|
*/
|
||||||
_onLoggedIn: function(teamToken) {
|
_onLoggedIn: function(teamToken) {
|
||||||
this.setState({
|
this.setState({
|
||||||
|
@ -767,8 +794,12 @@ module.exports = React.createClass({
|
||||||
this._teamToken = teamToken;
|
this._teamToken = teamToken;
|
||||||
dis.dispatch({action: 'view_home_page'});
|
dis.dispatch({action: 'view_home_page'});
|
||||||
} else if (this._is_registered) {
|
} else if (this._is_registered) {
|
||||||
|
if (this.props.config.welcomeUserId && getCurrentLanguage().startsWith("en")) {
|
||||||
|
createRoom({dmUserId: this.props.config.welcomeUserId});
|
||||||
|
return;
|
||||||
|
}
|
||||||
// The user has just logged in after registering
|
// The user has just logged in after registering
|
||||||
dis.dispatch({action: 'view_user_settings'});
|
dis.dispatch({action: 'view_room_directory'});
|
||||||
} else {
|
} else {
|
||||||
this._showScreenAfterLogin();
|
this._showScreenAfterLogin();
|
||||||
}
|
}
|
||||||
|
@ -780,7 +811,7 @@ module.exports = React.createClass({
|
||||||
if (this.state.screenAfterLogin && this.state.screenAfterLogin.screen) {
|
if (this.state.screenAfterLogin && this.state.screenAfterLogin.screen) {
|
||||||
this.showScreen(
|
this.showScreen(
|
||||||
this.state.screenAfterLogin.screen,
|
this.state.screenAfterLogin.screen,
|
||||||
this.state.screenAfterLogin.params
|
this.state.screenAfterLogin.params,
|
||||||
);
|
);
|
||||||
this.notifyNewScreen(this.state.screenAfterLogin.screen);
|
this.notifyNewScreen(this.state.screenAfterLogin.screen);
|
||||||
this.setState({screenAfterLogin: null});
|
this.setState({screenAfterLogin: null});
|
||||||
|
@ -821,8 +852,8 @@ module.exports = React.createClass({
|
||||||
* (useful for setting listeners)
|
* (useful for setting listeners)
|
||||||
*/
|
*/
|
||||||
_onWillStartClient() {
|
_onWillStartClient() {
|
||||||
var self = this;
|
const self = this;
|
||||||
var cli = MatrixClientPeg.get();
|
const cli = MatrixClientPeg.get();
|
||||||
|
|
||||||
// Allow the JS SDK to reap timeline events. This reduces the amount of
|
// Allow the JS SDK to reap timeline events. This reduces the amount of
|
||||||
// memory consumed as the JS SDK stores multiple distinct copies of room
|
// memory consumed as the JS SDK stores multiple distinct copies of room
|
||||||
|
@ -863,17 +894,17 @@ module.exports = React.createClass({
|
||||||
cli.on('Call.incoming', function(call) {
|
cli.on('Call.incoming', function(call) {
|
||||||
dis.dispatch({
|
dis.dispatch({
|
||||||
action: 'incoming_call',
|
action: 'incoming_call',
|
||||||
call: call
|
call: call,
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
cli.on('Session.logged_out', function(call) {
|
cli.on('Session.logged_out', function(call) {
|
||||||
var ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
|
const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
|
||||||
Modal.createDialog(ErrorDialog, {
|
Modal.createDialog(ErrorDialog, {
|
||||||
title: "Signed Out",
|
title: _t('Signed Out'),
|
||||||
description: "For security, this session has been signed out. Please sign in again."
|
description: _t('For security, this session has been signed out. Please sign in again.'),
|
||||||
});
|
});
|
||||||
dis.dispatch({
|
dis.dispatch({
|
||||||
action: 'logout'
|
action: 'logout',
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
cli.on("accountData", function(ev) {
|
cli.on("accountData", function(ev) {
|
||||||
|
@ -888,25 +919,21 @@ module.exports = React.createClass({
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
onFocus: function(ev) {
|
|
||||||
dis.dispatch({action: 'focus_composer'});
|
|
||||||
},
|
|
||||||
|
|
||||||
showScreen: function(screen, params) {
|
showScreen: function(screen, params) {
|
||||||
if (screen == 'register') {
|
if (screen == 'register') {
|
||||||
dis.dispatch({
|
dis.dispatch({
|
||||||
action: 'start_registration',
|
action: 'start_registration',
|
||||||
params: params
|
params: params,
|
||||||
});
|
});
|
||||||
} else if (screen == 'login') {
|
} else if (screen == 'login') {
|
||||||
dis.dispatch({
|
dis.dispatch({
|
||||||
action: 'start_login',
|
action: 'start_login',
|
||||||
params: params
|
params: params,
|
||||||
});
|
});
|
||||||
} else if (screen == 'forgot_password') {
|
} else if (screen == 'forgot_password') {
|
||||||
dis.dispatch({
|
dis.dispatch({
|
||||||
action: 'start_password_recovery',
|
action: 'start_password_recovery',
|
||||||
params: params
|
params: params,
|
||||||
});
|
});
|
||||||
} else if (screen == 'new') {
|
} else if (screen == 'new') {
|
||||||
dis.dispatch({
|
dis.dispatch({
|
||||||
|
@ -929,26 +956,26 @@ module.exports = React.createClass({
|
||||||
action: 'start_post_registration',
|
action: 'start_post_registration',
|
||||||
});
|
});
|
||||||
} else if (screen.indexOf('room/') == 0) {
|
} else if (screen.indexOf('room/') == 0) {
|
||||||
var segments = screen.substring(5).split('/');
|
const segments = screen.substring(5).split('/');
|
||||||
var roomString = segments[0];
|
const roomString = segments[0];
|
||||||
var eventId = segments[1]; // undefined if no event id given
|
const eventId = segments[1]; // undefined if no event id given
|
||||||
|
|
||||||
// FIXME: sort_out caseConsistency
|
// FIXME: sort_out caseConsistency
|
||||||
var third_party_invite = {
|
const thirdPartyInvite = {
|
||||||
inviteSignUrl: params.signurl,
|
inviteSignUrl: params.signurl,
|
||||||
invitedEmail: params.email,
|
invitedEmail: params.email,
|
||||||
};
|
};
|
||||||
var oob_data = {
|
const oobData = {
|
||||||
name: params.room_name,
|
name: params.room_name,
|
||||||
avatarUrl: params.room_avatar_url,
|
avatarUrl: params.room_avatar_url,
|
||||||
inviterName: params.inviter_name,
|
inviterName: params.inviter_name,
|
||||||
};
|
};
|
||||||
|
|
||||||
var payload = {
|
const payload = {
|
||||||
action: 'view_room',
|
action: 'view_room',
|
||||||
event_id: eventId,
|
event_id: eventId,
|
||||||
third_party_invite: third_party_invite,
|
third_party_invite: thirdPartyInvite,
|
||||||
oob_data: oob_data,
|
oob_data: oobData,
|
||||||
};
|
};
|
||||||
if (roomString[0] == '#') {
|
if (roomString[0] == '#') {
|
||||||
payload.room_alias = roomString;
|
payload.room_alias = roomString;
|
||||||
|
@ -962,19 +989,18 @@ module.exports = React.createClass({
|
||||||
dis.dispatch(payload);
|
dis.dispatch(payload);
|
||||||
}
|
}
|
||||||
} else if (screen.indexOf('user/') == 0) {
|
} else if (screen.indexOf('user/') == 0) {
|
||||||
var userId = screen.substring(5);
|
const userId = screen.substring(5);
|
||||||
this.setState({ viewUserId: userId });
|
this.setState({ viewUserId: userId });
|
||||||
this._setPage(PageTypes.UserView);
|
this._setPage(PageTypes.UserView);
|
||||||
this.notifyNewScreen('user/' + userId);
|
this.notifyNewScreen('user/' + userId);
|
||||||
var member = new Matrix.RoomMember(null, userId);
|
const member = new Matrix.RoomMember(null, userId);
|
||||||
if (member) {
|
if (member) {
|
||||||
dis.dispatch({
|
dis.dispatch({
|
||||||
action: 'view_user',
|
action: 'view_user',
|
||||||
member: member,
|
member: member,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
} else {
|
||||||
else {
|
|
||||||
console.info("Ignoring showScreen for '%s'", screen);
|
console.info("Ignoring showScreen for '%s'", screen);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -983,6 +1009,7 @@ module.exports = React.createClass({
|
||||||
if (this.props.onNewScreen) {
|
if (this.props.onNewScreen) {
|
||||||
this.props.onNewScreen(screen);
|
this.props.onNewScreen(screen);
|
||||||
}
|
}
|
||||||
|
Analytics.trackPageChange();
|
||||||
},
|
},
|
||||||
|
|
||||||
onAliasClick: function(event, alias) {
|
onAliasClick: function(event, alias) {
|
||||||
|
@ -993,7 +1020,7 @@ module.exports = React.createClass({
|
||||||
onUserClick: function(event, userId) {
|
onUserClick: function(event, userId) {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
|
|
||||||
var member = new Matrix.RoomMember(null, userId);
|
const member = new Matrix.RoomMember(null, userId);
|
||||||
if (!member) { return; }
|
if (!member) { return; }
|
||||||
dis.dispatch({
|
dis.dispatch({
|
||||||
action: 'view_user',
|
action: 'view_user',
|
||||||
|
@ -1003,17 +1030,17 @@ module.exports = React.createClass({
|
||||||
|
|
||||||
onLogoutClick: function(event) {
|
onLogoutClick: function(event) {
|
||||||
dis.dispatch({
|
dis.dispatch({
|
||||||
action: 'logout'
|
action: 'logout',
|
||||||
});
|
});
|
||||||
event.stopPropagation();
|
event.stopPropagation();
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
},
|
},
|
||||||
|
|
||||||
handleResize: function(e) {
|
handleResize: function(e) {
|
||||||
var hideLhsThreshold = 1000;
|
const hideLhsThreshold = 1000;
|
||||||
var showLhsThreshold = 1000;
|
const showLhsThreshold = 1000;
|
||||||
var hideRhsThreshold = 820;
|
const hideRhsThreshold = 820;
|
||||||
var showRhsThreshold = 820;
|
const showRhsThreshold = 820;
|
||||||
|
|
||||||
if (this.state.width > hideLhsThreshold && window.innerWidth <= hideLhsThreshold) {
|
if (this.state.width > hideLhsThreshold && window.innerWidth <= hideLhsThreshold) {
|
||||||
dis.dispatch({ action: 'hide_left_panel' });
|
dis.dispatch({ action: 'hide_left_panel' });
|
||||||
|
@ -1031,10 +1058,10 @@ module.exports = React.createClass({
|
||||||
this.setState({width: window.innerWidth});
|
this.setState({width: window.innerWidth});
|
||||||
},
|
},
|
||||||
|
|
||||||
onRoomCreated: function(room_id) {
|
onRoomCreated: function(roomId) {
|
||||||
dis.dispatch({
|
dis.dispatch({
|
||||||
action: "view_room",
|
action: "view_room",
|
||||||
room_id: room_id,
|
room_id: roomId,
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -1068,7 +1095,7 @@ module.exports = React.createClass({
|
||||||
onFinishPostRegistration: function() {
|
onFinishPostRegistration: function() {
|
||||||
// Don't confuse this with "PageType" which is the middle window to show
|
// Don't confuse this with "PageType" which is the middle window to show
|
||||||
this.setState({
|
this.setState({
|
||||||
screen: undefined
|
screen: undefined,
|
||||||
});
|
});
|
||||||
this.showScreen("settings");
|
this.showScreen("settings");
|
||||||
},
|
},
|
||||||
|
@ -1083,10 +1110,10 @@ module.exports = React.createClass({
|
||||||
},
|
},
|
||||||
|
|
||||||
updateStatusIndicator: function(state, prevState) {
|
updateStatusIndicator: function(state, prevState) {
|
||||||
var notifCount = 0;
|
let notifCount = 0;
|
||||||
|
|
||||||
var rooms = MatrixClientPeg.get().getRooms();
|
const rooms = MatrixClientPeg.get().getRooms();
|
||||||
for (var i = 0; i < rooms.length; ++i) {
|
for (let i = 0; i < rooms.length; ++i) {
|
||||||
if (rooms[i].hasMembershipState(MatrixClientPeg.get().credentials.userId, 'invite')) {
|
if (rooms[i].hasMembershipState(MatrixClientPeg.get().credentials.userId, 'invite')) {
|
||||||
notifCount++;
|
notifCount++;
|
||||||
} else if (rooms[i].getUnreadNotificationCount()) {
|
} else if (rooms[i].getUnreadNotificationCount()) {
|
||||||
|
@ -1113,19 +1140,18 @@ module.exports = React.createClass({
|
||||||
action: 'view_room',
|
action: 'view_room',
|
||||||
room_id: this.state.currentRoomId,
|
room_id: this.state.currentRoomId,
|
||||||
});
|
});
|
||||||
}
|
} else {
|
||||||
else {
|
|
||||||
dis.dispatch({
|
dis.dispatch({
|
||||||
action: 'view_room_directory',
|
action: 'view_room_directory',
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
onRoomIdResolved: function(room_id) {
|
onRoomIdResolved: function(roomId) {
|
||||||
// It's the RoomView's resposibility to look up room aliases, but we need the
|
// It's the RoomView's resposibility to look up room aliases, but we need the
|
||||||
// ID to pass into things like the Member List, so the Room View tells us when
|
// ID to pass into things like the Member List, so the Room View tells us when
|
||||||
// its done that resolution so we can display things that take a room ID.
|
// its done that resolution so we can display things that take a room ID.
|
||||||
this.setState({currentRoomId: room_id});
|
this.setState({currentRoomId: roomId});
|
||||||
},
|
},
|
||||||
|
|
||||||
_makeRegistrationUrl: function(params) {
|
_makeRegistrationUrl: function(params) {
|
||||||
|
@ -1148,14 +1174,20 @@ module.exports = React.createClass({
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// needs to be before normal PageTypes as you are logged in technically
|
// needs to be before normal PageTypes as you are logged in technically
|
||||||
else if (this.state.screen == 'post_registration') {
|
if (this.state.screen == 'post_registration') {
|
||||||
const PostRegistration = sdk.getComponent('structures.login.PostRegistration');
|
const PostRegistration = sdk.getComponent('structures.login.PostRegistration');
|
||||||
return (
|
return (
|
||||||
<PostRegistration
|
<PostRegistration
|
||||||
onComplete={this.onFinishPostRegistration} />
|
onComplete={this.onFinishPostRegistration} />
|
||||||
);
|
);
|
||||||
} else if (this.state.loggedIn && this.state.ready) {
|
}
|
||||||
|
|
||||||
|
// `ready` and `loggedIn` may be set before `page_type` (because the
|
||||||
|
// latter is set via the dispatcher). If we don't yet have a `page_type`,
|
||||||
|
// keep showing the spinner for now.
|
||||||
|
if (this.state.loggedIn && this.state.ready && this.state.page_type) {
|
||||||
/* for now, we stuff the entirety of our props and state into the LoggedInView.
|
/* for now, we stuff the entirety of our props and state into the LoggedInView.
|
||||||
* we should go through and figure out what we actually need to pass down, as well
|
* we should go through and figure out what we actually need to pass down, as well
|
||||||
* as using something like redux to avoid having a billion bits of state kicking around.
|
* as using something like redux to avoid having a billion bits of state kicking around.
|
||||||
|
@ -1178,7 +1210,7 @@ module.exports = React.createClass({
|
||||||
<div className="mx_MatrixChat_splash">
|
<div className="mx_MatrixChat_splash">
|
||||||
<Spinner />
|
<Spinner />
|
||||||
<a href="#" className="mx_MatrixChat_splashButtons" onClick={ this.onLogoutClick }>
|
<a href="#" className="mx_MatrixChat_splashButtons" onClick={ this.onLogoutClick }>
|
||||||
Logout
|
{ _t('Logout') }
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
@ -1237,5 +1269,5 @@ module.exports = React.createClass({
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
});
|
});
|
||||||
|
|
|
@ -84,6 +84,15 @@ module.exports = React.createClass({
|
||||||
|
|
||||||
// shape parameter to be passed to EventTiles
|
// shape parameter to be passed to EventTiles
|
||||||
tileShape: React.PropTypes.string,
|
tileShape: React.PropTypes.string,
|
||||||
|
|
||||||
|
// show twelve hour timestamps
|
||||||
|
isTwelveHour: React.PropTypes.bool,
|
||||||
|
|
||||||
|
// show timestamps always
|
||||||
|
alwaysShowTimestamps: React.PropTypes.bool,
|
||||||
|
|
||||||
|
// hide redacted events as per old behaviour
|
||||||
|
hideRedactions: React.PropTypes.bool,
|
||||||
},
|
},
|
||||||
|
|
||||||
componentWillMount: function() {
|
componentWillMount: function() {
|
||||||
|
@ -230,8 +239,8 @@ module.exports = React.createClass({
|
||||||
},
|
},
|
||||||
|
|
||||||
_getEventTiles: function() {
|
_getEventTiles: function() {
|
||||||
var EventTile = sdk.getComponent('rooms.EventTile');
|
const EventTile = sdk.getComponent('rooms.EventTile');
|
||||||
var DateSeparator = sdk.getComponent('messages.DateSeparator');
|
const DateSeparator = sdk.getComponent('messages.DateSeparator');
|
||||||
const MemberEventListSummary = sdk.getComponent('views.elements.MemberEventListSummary');
|
const MemberEventListSummary = sdk.getComponent('views.elements.MemberEventListSummary');
|
||||||
|
|
||||||
this.eventNodes = {};
|
this.eventNodes = {};
|
||||||
|
@ -279,20 +288,19 @@ module.exports = React.createClass({
|
||||||
this.currentGhostEventId = null;
|
this.currentGhostEventId = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
var isMembershipChange = (e) =>
|
var isMembershipChange = (e) => e.getType() === 'm.room.member';
|
||||||
e.getType() === 'm.room.member'
|
|
||||||
&& (!e.getPrevContent() || e.getContent().membership !== e.getPrevContent().membership);
|
|
||||||
|
|
||||||
for (i = 0; i < this.props.events.length; i++) {
|
for (i = 0; i < this.props.events.length; i++) {
|
||||||
var mxEv = this.props.events[i];
|
let mxEv = this.props.events[i];
|
||||||
var wantTile = true;
|
let wantTile = true;
|
||||||
var eventId = mxEv.getId();
|
let eventId = mxEv.getId();
|
||||||
|
let readMarkerInMels = false;
|
||||||
|
|
||||||
if (!EventTile.haveTileForEvent(mxEv)) {
|
if (!EventTile.haveTileForEvent(mxEv)) {
|
||||||
wantTile = false;
|
wantTile = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
var last = (i == lastShownEventIndex);
|
let last = (i == lastShownEventIndex);
|
||||||
|
|
||||||
// Wrap consecutive member events in a ListSummary, ignore if redacted
|
// Wrap consecutive member events in a ListSummary, ignore if redacted
|
||||||
if (isMembershipChange(mxEv) &&
|
if (isMembershipChange(mxEv) &&
|
||||||
|
@ -311,7 +319,7 @@ module.exports = React.createClass({
|
||||||
const key = "membereventlistsummary-" + (prevEvent ? mxEv.getId() : "initial");
|
const key = "membereventlistsummary-" + (prevEvent ? mxEv.getId() : "initial");
|
||||||
|
|
||||||
if (this._wantsDateSeparator(prevEvent, mxEv.getDate())) {
|
if (this._wantsDateSeparator(prevEvent, mxEv.getDate())) {
|
||||||
let dateSeparator = <li key={ts1+'~'}><DateSeparator key={ts1+'~'} ts={ts1}/></li>;
|
let dateSeparator = <li key={ts1+'~'}><DateSeparator key={ts1+'~'} ts={ts1} showTwelveHour={this.props.isTwelveHour}/></li>;
|
||||||
ret.push(dateSeparator);
|
ret.push(dateSeparator);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -334,6 +342,9 @@ module.exports = React.createClass({
|
||||||
|
|
||||||
let eventTiles = summarisedEvents.map(
|
let eventTiles = summarisedEvents.map(
|
||||||
(e) => {
|
(e) => {
|
||||||
|
if (e.getId() === this.props.readMarkerEventId) {
|
||||||
|
readMarkerInMels = true;
|
||||||
|
}
|
||||||
// In order to prevent DateSeparators from appearing in the expanded form
|
// In order to prevent DateSeparators from appearing in the expanded form
|
||||||
// of MemberEventListSummary, render each member event as if the previous
|
// of MemberEventListSummary, render each member event as if the previous
|
||||||
// one was itself. This way, the timestamp of the previous event === the
|
// one was itself. This way, the timestamp of the previous event === the
|
||||||
|
@ -352,12 +363,16 @@ module.exports = React.createClass({
|
||||||
<MemberEventListSummary
|
<MemberEventListSummary
|
||||||
key={key}
|
key={key}
|
||||||
events={summarisedEvents}
|
events={summarisedEvents}
|
||||||
data-scroll-token={eventId}
|
|
||||||
onToggle={this._onWidgetLoad} // Update scroll state
|
onToggle={this._onWidgetLoad} // Update scroll state
|
||||||
>
|
>
|
||||||
{eventTiles}
|
{eventTiles}
|
||||||
</MemberEventListSummary>
|
</MemberEventListSummary>
|
||||||
);
|
);
|
||||||
|
|
||||||
|
if (readMarkerInMels) {
|
||||||
|
ret.push(this._getReadMarkerTile(visible));
|
||||||
|
}
|
||||||
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -388,6 +403,8 @@ module.exports = React.createClass({
|
||||||
isVisibleReadMarker = visible;
|
isVisibleReadMarker = visible;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// XXX: there should be no need for a ghost tile - we should just use a
|
||||||
|
// a dispatch (user_activity_end) to start the RM animation.
|
||||||
if (eventId == this.currentGhostEventId) {
|
if (eventId == this.currentGhostEventId) {
|
||||||
// if we're showing an animation, continue to show it.
|
// if we're showing an animation, continue to show it.
|
||||||
ret.push(this._getReadMarkerGhostTile());
|
ret.push(this._getReadMarkerGhostTile());
|
||||||
|
@ -405,8 +422,8 @@ module.exports = React.createClass({
|
||||||
},
|
},
|
||||||
|
|
||||||
_getTilesForEvent: function(prevEvent, mxEv, last) {
|
_getTilesForEvent: function(prevEvent, mxEv, last) {
|
||||||
var EventTile = sdk.getComponent('rooms.EventTile');
|
const EventTile = sdk.getComponent('rooms.EventTile');
|
||||||
var DateSeparator = sdk.getComponent('messages.DateSeparator');
|
const DateSeparator = sdk.getComponent('messages.DateSeparator');
|
||||||
var ret = [];
|
var ret = [];
|
||||||
|
|
||||||
// is this a continuation of the previous message?
|
// is this a continuation of the previous message?
|
||||||
|
@ -444,11 +461,13 @@ module.exports = React.createClass({
|
||||||
|
|
||||||
// do we need a date separator since the last event?
|
// do we need a date separator since the last event?
|
||||||
if (this._wantsDateSeparator(prevEvent, eventDate)) {
|
if (this._wantsDateSeparator(prevEvent, eventDate)) {
|
||||||
var dateSeparator = <li key={ts1}><DateSeparator key={ts1} ts={ts1}/></li>;
|
var dateSeparator = <li key={ts1}><DateSeparator key={ts1} ts={ts1} showTwelveHour={this.props.isTwelveHour}/></li>;
|
||||||
ret.push(dateSeparator);
|
ret.push(dateSeparator);
|
||||||
continuation = false;
|
continuation = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (mxEv.isRedacted() && this.props.hideRedactions) return ret;
|
||||||
|
|
||||||
var eventId = mxEv.getId();
|
var eventId = mxEv.getId();
|
||||||
var highlight = (eventId == this.props.highlightedEventId);
|
var highlight = (eventId == this.props.highlightedEventId);
|
||||||
|
|
||||||
|
@ -460,11 +479,10 @@ module.exports = React.createClass({
|
||||||
if (this.props.manageReadReceipts) {
|
if (this.props.manageReadReceipts) {
|
||||||
readReceipts = this._getReadReceiptsForEvent(mxEv);
|
readReceipts = this._getReadReceiptsForEvent(mxEv);
|
||||||
}
|
}
|
||||||
|
|
||||||
ret.push(
|
ret.push(
|
||||||
<li key={eventId}
|
<li key={eventId}
|
||||||
ref={this._collectEventNode.bind(this, eventId)}
|
ref={this._collectEventNode.bind(this, eventId)}
|
||||||
data-scroll-token={scrollToken}>
|
data-scroll-tokens={scrollToken}>
|
||||||
<EventTile mxEvent={mxEv} continuation={continuation}
|
<EventTile mxEvent={mxEv} continuation={continuation}
|
||||||
isRedacted={mxEv.isRedacted()}
|
isRedacted={mxEv.isRedacted()}
|
||||||
onWidgetLoad={this._onWidgetLoad}
|
onWidgetLoad={this._onWidgetLoad}
|
||||||
|
@ -474,6 +492,7 @@ module.exports = React.createClass({
|
||||||
checkUnmounting={this._isUnmounting}
|
checkUnmounting={this._isUnmounting}
|
||||||
eventSendStatus={mxEv.status}
|
eventSendStatus={mxEv.status}
|
||||||
tileShape={this.props.tileShape}
|
tileShape={this.props.tileShape}
|
||||||
|
isTwelveHour={this.props.isTwelveHour}
|
||||||
last={last} isSelectedEvent={highlight}/>
|
last={last} isSelectedEvent={highlight}/>
|
||||||
</li>
|
</li>
|
||||||
);
|
);
|
||||||
|
@ -607,8 +626,13 @@ module.exports = React.createClass({
|
||||||
var style = this.props.hidden ? { display: 'none' } : {};
|
var style = this.props.hidden ? { display: 'none' } : {};
|
||||||
style.opacity = this.props.opacity;
|
style.opacity = this.props.opacity;
|
||||||
|
|
||||||
|
var className = this.props.className + " mx_fadable";
|
||||||
|
if (this.props.alwaysShowTimestamps) {
|
||||||
|
className += " mx_MessagePanel_alwaysShowTimestamps";
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ScrollPanel ref="scrollPanel" className={ this.props.className + " mx_fadable" }
|
<ScrollPanel ref="scrollPanel" className={ className }
|
||||||
onScroll={ this.props.onScroll }
|
onScroll={ this.props.onScroll }
|
||||||
onResize={ this.onResize }
|
onResize={ this.onResize }
|
||||||
onFillRequest={ this.props.onFillRequest }
|
onFillRequest={ this.props.onFillRequest }
|
||||||
|
|
|
@ -16,7 +16,7 @@ limitations under the License.
|
||||||
|
|
||||||
var React = require('react');
|
var React = require('react');
|
||||||
var ReactDOM = require("react-dom");
|
var ReactDOM = require("react-dom");
|
||||||
|
import { _t } from '../../languageHandler';
|
||||||
var Matrix = require("matrix-js-sdk");
|
var Matrix = require("matrix-js-sdk");
|
||||||
var sdk = require('../../index');
|
var sdk = require('../../index');
|
||||||
var MatrixClientPeg = require("../../MatrixClientPeg");
|
var MatrixClientPeg = require("../../MatrixClientPeg");
|
||||||
|
@ -37,7 +37,6 @@ var NotificationPanel = React.createClass({
|
||||||
var Loader = sdk.getComponent("elements.Spinner");
|
var Loader = sdk.getComponent("elements.Spinner");
|
||||||
|
|
||||||
var timelineSet = MatrixClientPeg.get().getNotifTimelineSet();
|
var timelineSet = MatrixClientPeg.get().getNotifTimelineSet();
|
||||||
|
|
||||||
if (timelineSet) {
|
if (timelineSet) {
|
||||||
return (
|
return (
|
||||||
<TimelinePanel key={"NotificationPanel_" + this.props.roomId}
|
<TimelinePanel key={"NotificationPanel_" + this.props.roomId}
|
||||||
|
@ -48,7 +47,7 @@ var NotificationPanel = React.createClass({
|
||||||
showUrlPreview = { false }
|
showUrlPreview = { false }
|
||||||
opacity={ this.props.opacity }
|
opacity={ this.props.opacity }
|
||||||
tileShape="notif"
|
tileShape="notif"
|
||||||
empty="You have no visible notifications"
|
empty={ _t('You have no visible notifications') }
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -14,12 +14,13 @@ See the License for the specific language governing permissions and
|
||||||
limitations under the License.
|
limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
var React = require('react');
|
import React from 'react';
|
||||||
var sdk = require('../../index');
|
import { _t } from '../../languageHandler';
|
||||||
var dis = require("../../dispatcher");
|
import sdk from '../../index';
|
||||||
var WhoIsTyping = require("../../WhoIsTyping");
|
import dis from '../../dispatcher';
|
||||||
var MatrixClientPeg = require("../../MatrixClientPeg");
|
import WhoIsTyping from '../../WhoIsTyping';
|
||||||
const MemberAvatar = require("../views/avatars/MemberAvatar");
|
import MatrixClientPeg from '../../MatrixClientPeg';
|
||||||
|
import MemberAvatar from '../views/avatars/MemberAvatar';
|
||||||
|
|
||||||
const HIDE_DEBOUNCE_MS = 10000;
|
const HIDE_DEBOUNCE_MS = 10000;
|
||||||
const STATUS_BAR_HIDDEN = 0;
|
const STATUS_BAR_HIDDEN = 0;
|
||||||
|
@ -175,8 +176,8 @@ module.exports = React.createClass({
|
||||||
<div className="mx_RoomStatusBar_scrollDownIndicator"
|
<div className="mx_RoomStatusBar_scrollDownIndicator"
|
||||||
onClick={ this.props.onScrollToBottomClick }>
|
onClick={ this.props.onScrollToBottomClick }>
|
||||||
<img src="img/scrolldown.svg" width="24" height="24"
|
<img src="img/scrolldown.svg" width="24" height="24"
|
||||||
alt="Scroll to bottom of page"
|
alt={ _t("Scroll to bottom of page") }
|
||||||
title="Scroll to bottom of page"/>
|
title={ _t("Scroll to bottom of page") }/>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -250,10 +251,10 @@ module.exports = React.createClass({
|
||||||
<div className="mx_RoomStatusBar_connectionLostBar">
|
<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">
|
<div className="mx_RoomStatusBar_connectionLostBar_title">
|
||||||
Connectivity to the server has been lost.
|
{_t('Connectivity to the server has been lost.')}
|
||||||
</div>
|
</div>
|
||||||
<div className="mx_RoomStatusBar_connectionLostBar_desc">
|
<div className="mx_RoomStatusBar_connectionLostBar_desc">
|
||||||
Sent messages will be stored until your connection has returned.
|
{_t('Sent messages will be stored until your connection has returned.')}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
@ -266,7 +267,7 @@ module.exports = React.createClass({
|
||||||
<TabCompleteBar tabComplete={this.props.tabComplete} />
|
<TabCompleteBar tabComplete={this.props.tabComplete} />
|
||||||
<div className="mx_RoomStatusBar_tabCompleteEol" title="->|">
|
<div className="mx_RoomStatusBar_tabCompleteEol" title="->|">
|
||||||
<TintableSvg src="img/eol.svg" width="22" height="16"/>
|
<TintableSvg src="img/eol.svg" width="22" height="16"/>
|
||||||
Auto-complete
|
{_t('Auto-complete')}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -283,13 +284,12 @@ module.exports = React.createClass({
|
||||||
<div className="mx_RoomStatusBar_connectionLostBar_desc">
|
<div className="mx_RoomStatusBar_connectionLostBar_desc">
|
||||||
<a className="mx_RoomStatusBar_resend_link"
|
<a className="mx_RoomStatusBar_resend_link"
|
||||||
onClick={ this.props.onResendAllClick }>
|
onClick={ this.props.onResendAllClick }>
|
||||||
Resend all
|
{_t('Resend all')}
|
||||||
</a> or <a
|
</a> {_t('or')} <a
|
||||||
className="mx_RoomStatusBar_resend_link"
|
className="mx_RoomStatusBar_resend_link"
|
||||||
onClick={ this.props.onCancelAllClick }>
|
onClick={ this.props.onCancelAllClick }>
|
||||||
cancel all
|
{_t('cancel all')}
|
||||||
</a> now. You can also select individual messages to
|
</a> {_t('now. You can also select individual messages to resend or cancel.')}
|
||||||
resend or cancel.
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
@ -324,7 +324,7 @@ module.exports = React.createClass({
|
||||||
if (this.props.hasActiveCall) {
|
if (this.props.hasActiveCall) {
|
||||||
return (
|
return (
|
||||||
<div className="mx_RoomStatusBar_callBar">
|
<div className="mx_RoomStatusBar_callBar">
|
||||||
<b>Active call</b>
|
<b>{_t('Active call')}</b>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -25,7 +25,9 @@ var ReactDOM = require("react-dom");
|
||||||
var q = require("q");
|
var q = require("q");
|
||||||
var classNames = require("classnames");
|
var classNames = require("classnames");
|
||||||
var Matrix = require("matrix-js-sdk");
|
var Matrix = require("matrix-js-sdk");
|
||||||
|
import { _t } from '../../languageHandler';
|
||||||
|
|
||||||
|
var UserSettingsStore = require('../../UserSettingsStore');
|
||||||
var MatrixClientPeg = require("../../MatrixClientPeg");
|
var MatrixClientPeg = require("../../MatrixClientPeg");
|
||||||
var ContentMessages = require("../../ContentMessages");
|
var ContentMessages = require("../../ContentMessages");
|
||||||
var Modal = require("../../Modal");
|
var Modal = require("../../Modal");
|
||||||
|
@ -124,6 +126,8 @@ module.exports = React.createClass({
|
||||||
roomId: null,
|
roomId: null,
|
||||||
userId: null,
|
userId: null,
|
||||||
roomLoading: true,
|
roomLoading: true,
|
||||||
|
|
||||||
|
forwardingEvent: null,
|
||||||
editingRoomSettings: false,
|
editingRoomSettings: false,
|
||||||
uploadingRoomSettings: false,
|
uploadingRoomSettings: false,
|
||||||
numUnreadMessages: 0,
|
numUnreadMessages: 0,
|
||||||
|
@ -274,6 +278,7 @@ module.exports = React.createClass({
|
||||||
|
|
||||||
this._updateConfCallNotification();
|
this._updateConfCallNotification();
|
||||||
|
|
||||||
|
window.addEventListener('beforeunload', this.onPageUnload);
|
||||||
window.addEventListener('resize', this.onResize);
|
window.addEventListener('resize', this.onResize);
|
||||||
this.onResize();
|
this.onResize();
|
||||||
|
|
||||||
|
@ -298,7 +303,7 @@ module.exports = React.createClass({
|
||||||
|
|
||||||
componentWillReceiveProps: function(newProps) {
|
componentWillReceiveProps: function(newProps) {
|
||||||
if (newProps.roomAddress != this.props.roomAddress) {
|
if (newProps.roomAddress != this.props.roomAddress) {
|
||||||
throw new Error("changing room on a RoomView is not supported");
|
throw new Error(_t("changing room on a RoomView is not supported"));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (newProps.eventId != this.props.eventId) {
|
if (newProps.eventId != this.props.eventId) {
|
||||||
|
@ -356,6 +361,7 @@ module.exports = React.createClass({
|
||||||
MatrixClientPeg.get().removeListener("accountData", this.onAccountData);
|
MatrixClientPeg.get().removeListener("accountData", this.onAccountData);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
window.removeEventListener('beforeunload', this.onPageUnload);
|
||||||
window.removeEventListener('resize', this.onResize);
|
window.removeEventListener('resize', this.onResize);
|
||||||
|
|
||||||
document.removeEventListener("keydown", this.onKeyDown);
|
document.removeEventListener("keydown", this.onKeyDown);
|
||||||
|
@ -368,6 +374,17 @@ module.exports = React.createClass({
|
||||||
// Tinter.tint(); // reset colourscheme
|
// Tinter.tint(); // reset colourscheme
|
||||||
},
|
},
|
||||||
|
|
||||||
|
onPageUnload(event) {
|
||||||
|
if (ContentMessages.getCurrentUploads().length > 0) {
|
||||||
|
return event.returnValue =
|
||||||
|
_t("You seem to be uploading files, are you sure you want to quit?");
|
||||||
|
} else if (this._getCallForRoom() && this.state.callState !== 'ended') {
|
||||||
|
return event.returnValue =
|
||||||
|
_t("You seem to be in a call, are you sure you want to quit?");
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
|
||||||
onKeyDown: function(ev) {
|
onKeyDown: function(ev) {
|
||||||
let handled = false;
|
let handled = false;
|
||||||
const isMac = navigator.platform.toUpperCase().indexOf('MAC') >= 0;
|
const isMac = navigator.platform.toUpperCase().indexOf('MAC') >= 0;
|
||||||
|
@ -447,6 +464,11 @@ module.exports = React.createClass({
|
||||||
showApps: payload.show ? true : false,
|
showApps: payload.show ? true : false,
|
||||||
});
|
});
|
||||||
break;
|
break;
|
||||||
|
case 'forward_event':
|
||||||
|
this.setState({
|
||||||
|
forwardingEvent: payload.content,
|
||||||
|
});
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -525,14 +547,14 @@ module.exports = React.createClass({
|
||||||
if (!userHasUsedEncryption) {
|
if (!userHasUsedEncryption) {
|
||||||
const QuestionDialog = sdk.getComponent("dialogs.QuestionDialog");
|
const QuestionDialog = sdk.getComponent("dialogs.QuestionDialog");
|
||||||
Modal.createDialog(QuestionDialog, {
|
Modal.createDialog(QuestionDialog, {
|
||||||
title: "Warning!",
|
title: _t("Warning!"),
|
||||||
hasCancelButton: false,
|
hasCancelButton: false,
|
||||||
description: (
|
description: (
|
||||||
<div>
|
<div>
|
||||||
<p>End-to-end encryption is in beta and may not be reliable.</p>
|
<p>{ _t("End-to-end encryption is in beta and may not be reliable") }.</p>
|
||||||
<p>You should <b>not</b> yet trust it to secure data.</p>
|
<p>{ _t("You should not yet trust it to secure data") }.</p>
|
||||||
<p>Devices will <b>not</b> yet be able to decrypt history from before they joined the room.</p>
|
<p>{ _t("Devices will not yet be able to decrypt history from before they joined the room") }.</p>
|
||||||
<p>Encrypted messages will not be visible on clients that do not yet implement encryption.</p>
|
<p>{ _t("Encrypted messages will not be visible on clients that do not yet implement encryption") }.</p>
|
||||||
</div>
|
</div>
|
||||||
),
|
),
|
||||||
});
|
});
|
||||||
|
@ -703,10 +725,10 @@ module.exports = React.createClass({
|
||||||
if (!unsentMessages.length) return "";
|
if (!unsentMessages.length) return "";
|
||||||
for (const event of unsentMessages) {
|
for (const event of unsentMessages) {
|
||||||
if (!event.error || event.error.name !== "UnknownDeviceError") {
|
if (!event.error || event.error.name !== "UnknownDeviceError") {
|
||||||
return "Some of your messages have not been sent.";
|
return _t("Some of your messages have not been sent") + ".";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return "Message not sent due to unknown devices being present";
|
return _t("Message not sent due to unknown devices being present");
|
||||||
},
|
},
|
||||||
|
|
||||||
_getUnsentMessages: function(room) {
|
_getUnsentMessages: function(room) {
|
||||||
|
@ -866,15 +888,15 @@ module.exports = React.createClass({
|
||||||
) {
|
) {
|
||||||
var NeedToRegisterDialog = sdk.getComponent("dialogs.NeedToRegisterDialog");
|
var NeedToRegisterDialog = sdk.getComponent("dialogs.NeedToRegisterDialog");
|
||||||
Modal.createDialog(NeedToRegisterDialog, {
|
Modal.createDialog(NeedToRegisterDialog, {
|
||||||
title: "Failed to join the room",
|
title: _t("Failed to join the room"),
|
||||||
description: "This room is private or inaccessible to guests. You may be able to join if you register."
|
description: _t("This room is private or inaccessible to guests. You may be able to join if you register") + "."
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
var msg = error.message ? error.message : JSON.stringify(error);
|
var msg = error.message ? error.message : JSON.stringify(error);
|
||||||
var ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
|
var ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
|
||||||
Modal.createDialog(ErrorDialog, {
|
Modal.createDialog(ErrorDialog, {
|
||||||
title: "Failed to join room",
|
title: _t("Failed to join room"),
|
||||||
description: msg
|
description: msg,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}).done();
|
}).done();
|
||||||
|
@ -934,8 +956,8 @@ module.exports = React.createClass({
|
||||||
if (MatrixClientPeg.get().isGuest()) {
|
if (MatrixClientPeg.get().isGuest()) {
|
||||||
var NeedToRegisterDialog = sdk.getComponent("dialogs.NeedToRegisterDialog");
|
var NeedToRegisterDialog = sdk.getComponent("dialogs.NeedToRegisterDialog");
|
||||||
Modal.createDialog(NeedToRegisterDialog, {
|
Modal.createDialog(NeedToRegisterDialog, {
|
||||||
title: "Please Register",
|
title: _t("Please Register"),
|
||||||
description: "Guest users can't upload files. Please register to upload."
|
description: _t("Guest users can't upload files. Please register to upload") + "."
|
||||||
});
|
});
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -954,8 +976,8 @@ module.exports = React.createClass({
|
||||||
const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
|
const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
|
||||||
console.error("Failed to upload file " + file + " " + error);
|
console.error("Failed to upload file " + file + " " + error);
|
||||||
Modal.createDialog(ErrorDialog, {
|
Modal.createDialog(ErrorDialog, {
|
||||||
title: "Failed to upload file",
|
title: _t('Failed to upload file'),
|
||||||
description: "Server may be unavailable, overloaded, or the file too big",
|
description: ((error && error.message) ? error.message : _t("Server may be unavailable, overloaded, or the file too big")),
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
@ -1041,8 +1063,8 @@ module.exports = React.createClass({
|
||||||
var ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
|
var ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
|
||||||
console.error("Search failed: " + error);
|
console.error("Search failed: " + error);
|
||||||
Modal.createDialog(ErrorDialog, {
|
Modal.createDialog(ErrorDialog, {
|
||||||
title: "Search failed",
|
title: _t("Search failed"),
|
||||||
description: "Server may be unavailable, overloaded, or search timed out :("
|
description: ((error && error.message) ? error.message : _t("Server may be unavailable, overloaded, or search timed out :(")),
|
||||||
});
|
});
|
||||||
}).finally(function() {
|
}).finally(function() {
|
||||||
self.setState({
|
self.setState({
|
||||||
|
@ -1077,12 +1099,12 @@ module.exports = React.createClass({
|
||||||
if (!this.state.searchResults.next_batch) {
|
if (!this.state.searchResults.next_batch) {
|
||||||
if (this.state.searchResults.results.length == 0) {
|
if (this.state.searchResults.results.length == 0) {
|
||||||
ret.push(<li key="search-top-marker">
|
ret.push(<li key="search-top-marker">
|
||||||
<h2 className="mx_RoomView_topMarker">No results</h2>
|
<h2 className="mx_RoomView_topMarker">{ _t("No results") }</h2>
|
||||||
</li>
|
</li>
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
ret.push(<li key="search-top-marker">
|
ret.push(<li key="search-top-marker">
|
||||||
<h2 className="mx_RoomView_topMarker">No more results</h2>
|
<h2 className="mx_RoomView_topMarker">{ _t("No more results") }</h2>
|
||||||
</li>
|
</li>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -1119,10 +1141,10 @@ module.exports = React.createClass({
|
||||||
// it. We should tell the js sdk to go and find out 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
|
// it. But that's not an issue currently, as synapse only
|
||||||
// returns results for rooms we're joined to.
|
// returns results for rooms we're joined to.
|
||||||
var roomName = room ? room.name : "Unknown room "+roomId;
|
var roomName = room ? room.name : _t("Unknown room %(roomId)s", { roomId: roomId });
|
||||||
|
|
||||||
ret.push(<li key={mxEv.getId() + "-room"}>
|
ret.push(<li key={mxEv.getId() + "-room"}>
|
||||||
<h1>Room: { roomName }</h1>
|
<h1>{ _t("Room") }: { roomName }</h1>
|
||||||
</li>);
|
</li>);
|
||||||
lastRoomId = roomId;
|
lastRoomId = roomId;
|
||||||
}
|
}
|
||||||
|
@ -1168,7 +1190,7 @@ module.exports = React.createClass({
|
||||||
});
|
});
|
||||||
var ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
|
var ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
|
||||||
Modal.createDialog(ErrorDialog, {
|
Modal.createDialog(ErrorDialog, {
|
||||||
title: "Failed to save settings",
|
title: _t("Failed to save settings"),
|
||||||
description: fails.map(function(result) { return result.reason; }).join("\n"),
|
description: fails.map(function(result) { return result.reason; }).join("\n"),
|
||||||
});
|
});
|
||||||
// still editing room settings
|
// still editing room settings
|
||||||
|
@ -1189,7 +1211,11 @@ module.exports = React.createClass({
|
||||||
onCancelClick: function() {
|
onCancelClick: function() {
|
||||||
console.log("updateTint from onCancelClick");
|
console.log("updateTint from onCancelClick");
|
||||||
this.updateTint();
|
this.updateTint();
|
||||||
this.setState({editingRoomSettings: false});
|
this.setState({
|
||||||
|
editingRoomSettings: false,
|
||||||
|
forwardingEvent: null,
|
||||||
|
});
|
||||||
|
dis.dispatch({action: 'focus_composer'});
|
||||||
},
|
},
|
||||||
|
|
||||||
onLeaveClick: function() {
|
onLeaveClick: function() {
|
||||||
|
@ -1203,11 +1229,11 @@ module.exports = React.createClass({
|
||||||
MatrixClientPeg.get().forget(this.state.room.roomId).done(function() {
|
MatrixClientPeg.get().forget(this.state.room.roomId).done(function() {
|
||||||
dis.dispatch({ action: 'view_next_room' });
|
dis.dispatch({ action: 'view_next_room' });
|
||||||
}, function(err) {
|
}, function(err) {
|
||||||
var errCode = err.errcode || "unknown error code";
|
var errCode = err.errcode || _t("unknown error code");
|
||||||
var ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
|
var ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
|
||||||
Modal.createDialog(ErrorDialog, {
|
Modal.createDialog(ErrorDialog, {
|
||||||
title: "Error",
|
title: _t("Error"),
|
||||||
description: `Failed to forget room (${errCode})`
|
description: _t("Failed to forget room %(errCode)s", { errCode: errCode }),
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
@ -1228,8 +1254,8 @@ module.exports = React.createClass({
|
||||||
var msg = error.message ? error.message : JSON.stringify(error);
|
var msg = error.message ? error.message : JSON.stringify(error);
|
||||||
var ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
|
var ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
|
||||||
Modal.createDialog(ErrorDialog, {
|
Modal.createDialog(ErrorDialog, {
|
||||||
title: "Failed to reject invite",
|
title: _t("Failed to reject invite"),
|
||||||
description: msg
|
description: msg,
|
||||||
});
|
});
|
||||||
|
|
||||||
self.setState({
|
self.setState({
|
||||||
|
@ -1263,6 +1289,7 @@ module.exports = React.createClass({
|
||||||
// jump down to the bottom of this room, where new events are arriving
|
// jump down to the bottom of this room, where new events are arriving
|
||||||
jumpToLiveTimeline: function() {
|
jumpToLiveTimeline: function() {
|
||||||
this.refs.messagePanel.jumpToLiveTimeline();
|
this.refs.messagePanel.jumpToLiveTimeline();
|
||||||
|
dis.dispatch({action: 'focus_composer'});
|
||||||
},
|
},
|
||||||
|
|
||||||
// jump up to wherever our read marker is
|
// jump up to wherever our read marker is
|
||||||
|
@ -1282,12 +1309,7 @@ module.exports = React.createClass({
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
var pos = this.refs.messagePanel.getReadMarkerPosition();
|
const showBar = this.refs.messagePanel.canJumpToReadMarker();
|
||||||
|
|
||||||
// we want to show the bar if the read-marker is off the top of the
|
|
||||||
// screen.
|
|
||||||
var showBar = (pos < 0);
|
|
||||||
|
|
||||||
if (this.state.showTopUnreadMessagesBar != showBar) {
|
if (this.state.showTopUnreadMessagesBar != showBar) {
|
||||||
this.setState({showTopUnreadMessagesBar: showBar},
|
this.setState({showTopUnreadMessagesBar: showBar},
|
||||||
this.onChildResize);
|
this.onChildResize);
|
||||||
|
@ -1470,16 +1492,17 @@ module.exports = React.createClass({
|
||||||
},
|
},
|
||||||
|
|
||||||
render: function() {
|
render: function() {
|
||||||
var RoomHeader = sdk.getComponent('rooms.RoomHeader');
|
const RoomHeader = sdk.getComponent('rooms.RoomHeader');
|
||||||
var MessageComposer = sdk.getComponent('rooms.MessageComposer');
|
const MessageComposer = sdk.getComponent('rooms.MessageComposer');
|
||||||
var RoomSettings = sdk.getComponent("rooms.RoomSettings");
|
const ForwardMessage = sdk.getComponent("rooms.ForwardMessage");
|
||||||
var AuxPanel = sdk.getComponent("rooms.AuxPanel");
|
const RoomSettings = sdk.getComponent("rooms.RoomSettings");
|
||||||
var SearchBar = sdk.getComponent("rooms.SearchBar");
|
const AuxPanel = sdk.getComponent("rooms.AuxPanel");
|
||||||
var ScrollPanel = sdk.getComponent("structures.ScrollPanel");
|
const SearchBar = sdk.getComponent("rooms.SearchBar");
|
||||||
var TintableSvg = sdk.getComponent("elements.TintableSvg");
|
const ScrollPanel = sdk.getComponent("structures.ScrollPanel");
|
||||||
var RoomPreviewBar = sdk.getComponent("rooms.RoomPreviewBar");
|
const TintableSvg = sdk.getComponent("elements.TintableSvg");
|
||||||
var Loader = sdk.getComponent("elements.Spinner");
|
const RoomPreviewBar = sdk.getComponent("rooms.RoomPreviewBar");
|
||||||
var TimelinePanel = sdk.getComponent("structures.TimelinePanel");
|
const Loader = sdk.getComponent("elements.Spinner");
|
||||||
|
const TimelinePanel = sdk.getComponent("structures.TimelinePanel");
|
||||||
|
|
||||||
if (!this.state.room) {
|
if (!this.state.room) {
|
||||||
if (this.state.roomLoading) {
|
if (this.state.roomLoading) {
|
||||||
|
@ -1488,8 +1511,7 @@ module.exports = React.createClass({
|
||||||
<Loader />
|
<Loader />
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
} else {
|
||||||
else {
|
|
||||||
var inviterName = undefined;
|
var inviterName = undefined;
|
||||||
if (this.props.oobData) {
|
if (this.props.oobData) {
|
||||||
inviterName = this.props.oobData.inviterName;
|
inviterName = this.props.oobData.inviterName;
|
||||||
|
@ -1607,17 +1629,18 @@ module.exports = React.createClass({
|
||||||
/>;
|
/>;
|
||||||
}
|
}
|
||||||
|
|
||||||
var aux = null;
|
let aux = null;
|
||||||
|
let hideCancel = false;
|
||||||
if (this.state.editingRoomSettings) {
|
if (this.state.editingRoomSettings) {
|
||||||
aux = <RoomSettings ref="room_settings" onSaveClick={this.onSettingsSaveClick} onCancelClick={this.onCancelClick} room={this.state.room} />;
|
aux = <RoomSettings ref="room_settings" onSaveClick={this.onSettingsSaveClick} onCancelClick={this.onCancelClick} room={this.state.room} />;
|
||||||
}
|
} else if (this.state.uploadingRoomSettings) {
|
||||||
else if (this.state.uploadingRoomSettings) {
|
|
||||||
aux = <Loader/>;
|
aux = <Loader/>;
|
||||||
}
|
} else if (this.state.forwardingEvent !== null) {
|
||||||
else if (this.state.searching) {
|
aux = <ForwardMessage onCancelClick={this.onCancelClick} currentRoomId={this.state.room.roomId} mxEvent={this.state.forwardingEvent} />;
|
||||||
|
} 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") {
|
||||||
else if (!myMember || myMember.membership !== "join") {
|
|
||||||
// We do have a room object for this room, but we're not currently in it.
|
// 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.
|
// We may have a 3rd party invite to it.
|
||||||
var inviterName = undefined;
|
var inviterName = undefined;
|
||||||
|
@ -1628,6 +1651,7 @@ module.exports = React.createClass({
|
||||||
if (this.props.thirdPartyInvite) {
|
if (this.props.thirdPartyInvite) {
|
||||||
invitedEmail = this.props.thirdPartyInvite.invitedEmail;
|
invitedEmail = this.props.thirdPartyInvite.invitedEmail;
|
||||||
}
|
}
|
||||||
|
hideCancel = true;
|
||||||
aux = (
|
aux = (
|
||||||
<RoomPreviewBar onJoinClick={this.onJoinButtonClicked}
|
<RoomPreviewBar onJoinClick={this.onJoinButtonClicked}
|
||||||
onForgetClick={ this.onForgetClick }
|
onForgetClick={ this.onForgetClick }
|
||||||
|
@ -1687,7 +1711,7 @@ module.exports = React.createClass({
|
||||||
|
|
||||||
if (call.type === "video") {
|
if (call.type === "video") {
|
||||||
zoomButton = (
|
zoomButton = (
|
||||||
<div className="mx_RoomView_voipButton" onClick={this.onFullscreenClick} title="Fill screen">
|
<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 }}/>
|
<TintableSvg src="img/fullscreen.svg" width="29" height="22" style={{ marginTop: 1, marginRight: 4 }}/>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
@ -1695,14 +1719,14 @@ module.exports = React.createClass({
|
||||||
videoMuteButton =
|
videoMuteButton =
|
||||||
<div className="mx_RoomView_voipButton" onClick={this.onMuteVideoClick}>
|
<div className="mx_RoomView_voipButton" onClick={this.onMuteVideoClick}>
|
||||||
<TintableSvg src={call.isLocalVideoMuted() ? "img/video-unmute.svg" : "img/video-mute.svg"}
|
<TintableSvg src={call.isLocalVideoMuted() ? "img/video-unmute.svg" : "img/video-mute.svg"}
|
||||||
alt={call.isLocalVideoMuted() ? "Click to unmute video" : "Click to mute video"}
|
alt={call.isLocalVideoMuted() ? _t("Click to unmute video") : _t("Click to mute video")}
|
||||||
width="31" height="27"/>
|
width="31" height="27"/>
|
||||||
</div>;
|
</div>;
|
||||||
}
|
}
|
||||||
voiceMuteButton =
|
voiceMuteButton =
|
||||||
<div className="mx_RoomView_voipButton" onClick={this.onMuteAudioClick}>
|
<div className="mx_RoomView_voipButton" onClick={this.onMuteAudioClick}>
|
||||||
<TintableSvg src={call.isMicrophoneMuted() ? "img/voice-unmute.svg" : "img/voice-mute.svg"}
|
<TintableSvg src={call.isMicrophoneMuted() ? "img/voice-unmute.svg" : "img/voice-mute.svg"}
|
||||||
alt={call.isMicrophoneMuted() ? "Click to unmute audio" : "Click to mute audio"}
|
alt={call.isMicrophoneMuted() ? _t("Click to unmute audio") : _t("Click to mute audio")}
|
||||||
width="21" height="26"/>
|
width="21" height="26"/>
|
||||||
</div>;
|
</div>;
|
||||||
|
|
||||||
|
@ -1738,14 +1762,13 @@ module.exports = React.createClass({
|
||||||
}
|
}
|
||||||
|
|
||||||
// console.log("ShowUrlPreview for %s is %s", this.state.room.roomId, this.state.showUrlPreview);
|
// console.log("ShowUrlPreview for %s is %s", this.state.room.roomId, this.state.showUrlPreview);
|
||||||
|
|
||||||
var messagePanel = (
|
var messagePanel = (
|
||||||
<TimelinePanel ref={this._gatherTimelinePanelRef}
|
<TimelinePanel ref={this._gatherTimelinePanelRef}
|
||||||
timelineSet={this.state.room.getUnfilteredTimelineSet()}
|
timelineSet={this.state.room.getUnfilteredTimelineSet()}
|
||||||
manageReadReceipts={true}
|
manageReadReceipts={!UserSettingsStore.getSyncedSetting('hideReadReceipts', false)}
|
||||||
manageReadMarkers={true}
|
manageReadMarkers={true}
|
||||||
hidden={hideMessagePanel}
|
hidden={hideMessagePanel}
|
||||||
highlightedEventId={this.props.highlightedEventId}
|
highlightedEventId={this.state.forwardingEvent ? this.state.forwardingEvent.getId() : this.props.highlightedEventId}
|
||||||
eventId={this.props.eventId}
|
eventId={this.props.eventId}
|
||||||
eventPixelOffset={this.props.eventPixelOffset}
|
eventPixelOffset={this.props.eventPixelOffset}
|
||||||
onScroll={ this.onMessageListScroll }
|
onScroll={ this.onMessageListScroll }
|
||||||
|
@ -1778,17 +1801,15 @@ module.exports = React.createClass({
|
||||||
oobData={this.props.oobData}
|
oobData={this.props.oobData}
|
||||||
editing={this.state.editingRoomSettings}
|
editing={this.state.editingRoomSettings}
|
||||||
saving={this.state.uploadingRoomSettings}
|
saving={this.state.uploadingRoomSettings}
|
||||||
|
inRoom={myMember && myMember.membership === 'join'}
|
||||||
collapsedRhs={ this.props.collapsedRhs }
|
collapsedRhs={ this.props.collapsedRhs }
|
||||||
onSearchClick={this.onSearchClick}
|
onSearchClick={this.onSearchClick}
|
||||||
onSettingsClick={this.onSettingsClick}
|
onSettingsClick={this.onSettingsClick}
|
||||||
onSaveClick={this.onSettingsSaveClick}
|
onSaveClick={this.onSettingsSaveClick}
|
||||||
onCancelClick={this.onCancelClick}
|
onCancelClick={(aux && !hideCancel) ? this.onCancelClick : null}
|
||||||
onForgetClick={
|
onForgetClick={(myMember && myMember.membership === "leave") ? this.onForgetClick : null}
|
||||||
(myMember && myMember.membership === "leave") ? this.onForgetClick : null
|
onLeaveClick={(myMember && myMember.membership === "join") ? this.onLeaveClick : null}
|
||||||
}
|
/>
|
||||||
onLeaveClick={
|
|
||||||
(myMember && myMember.membership === "join") ? this.onLeaveClick : null
|
|
||||||
} />
|
|
||||||
{ auxPanel }
|
{ auxPanel }
|
||||||
{ topUnreadMessagesBar }
|
{ topUnreadMessagesBar }
|
||||||
{ messagePanel }
|
{ messagePanel }
|
||||||
|
|
|
@ -46,9 +46,13 @@ if (DEBUG_SCROLL) {
|
||||||
* It also provides a hook which allows parents to provide more list elements
|
* It also provides a hook which allows parents to provide more list elements
|
||||||
* when we get close to the start or end of the list.
|
* when we get close to the start or end of the list.
|
||||||
*
|
*
|
||||||
* Each child element should have a 'data-scroll-token'. This token is used to
|
* Each child element should have a 'data-scroll-tokens'. This string of
|
||||||
* serialise the scroll state, and returned as the 'trackedScrollToken'
|
* comma-separated tokens may contain a single token or many, where many indicates
|
||||||
* attribute by getScrollState().
|
* that the element contains elements that have scroll tokens themselves. The first
|
||||||
|
* token in 'data-scroll-tokens' is used to serialise the scroll state, and returned
|
||||||
|
* as the 'trackedScrollToken' attribute by getScrollState().
|
||||||
|
*
|
||||||
|
* IMPORTANT: INDIVIDUAL TOKENS WITHIN 'data-scroll-tokens' MUST NOT CONTAIN COMMAS.
|
||||||
*
|
*
|
||||||
* Some notes about the implementation:
|
* Some notes about the implementation:
|
||||||
*
|
*
|
||||||
|
@ -349,8 +353,8 @@ module.exports = React.createClass({
|
||||||
// Subtract height of tile as if it were unpaginated
|
// Subtract height of tile as if it were unpaginated
|
||||||
excessHeight -= tile.clientHeight;
|
excessHeight -= tile.clientHeight;
|
||||||
// The tile may not have a scroll token, so guard it
|
// The tile may not have a scroll token, so guard it
|
||||||
if (tile.dataset.scrollToken) {
|
if (tile.dataset.scrollTokens) {
|
||||||
markerScrollToken = tile.dataset.scrollToken;
|
markerScrollToken = tile.dataset.scrollTokens.split(',')[0];
|
||||||
}
|
}
|
||||||
if (tile.clientHeight > excessHeight) {
|
if (tile.clientHeight > excessHeight) {
|
||||||
break;
|
break;
|
||||||
|
@ -419,7 +423,8 @@ module.exports = React.createClass({
|
||||||
* scroll. false if we are tracking a particular child.
|
* scroll. false if we are tracking a particular child.
|
||||||
*
|
*
|
||||||
* string trackedScrollToken: undefined if stuckAtBottom is true; if it is
|
* string trackedScrollToken: undefined if stuckAtBottom is true; if it is
|
||||||
* false, the data-scroll-token of the child which we are tracking.
|
* false, the first token in data-scroll-tokens of the child which we are
|
||||||
|
* tracking.
|
||||||
*
|
*
|
||||||
* number pixelOffset: undefined if stuckAtBottom is true; if it is false,
|
* number pixelOffset: undefined if stuckAtBottom is true; if it is false,
|
||||||
* the number of pixels the bottom of the tracked child is above the
|
* the number of pixels the bottom of the tracked child is above the
|
||||||
|
@ -483,21 +488,25 @@ module.exports = React.createClass({
|
||||||
handleScrollKey: function(ev) {
|
handleScrollKey: function(ev) {
|
||||||
switch (ev.keyCode) {
|
switch (ev.keyCode) {
|
||||||
case KeyCode.PAGE_UP:
|
case KeyCode.PAGE_UP:
|
||||||
|
if (!ev.ctrlKey && !ev.shiftKey && !ev.altKey && !ev.metaKey) {
|
||||||
this.scrollRelative(-1);
|
this.scrollRelative(-1);
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case KeyCode.PAGE_DOWN:
|
case KeyCode.PAGE_DOWN:
|
||||||
|
if (!ev.ctrlKey && !ev.shiftKey && !ev.altKey && !ev.metaKey) {
|
||||||
this.scrollRelative(1);
|
this.scrollRelative(1);
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case KeyCode.HOME:
|
case KeyCode.HOME:
|
||||||
if (ev.ctrlKey) {
|
if (ev.ctrlKey && !ev.shiftKey && !ev.altKey && !ev.metaKey) {
|
||||||
this.scrollToTop();
|
this.scrollToTop();
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case KeyCode.END:
|
case KeyCode.END:
|
||||||
if (ev.ctrlKey) {
|
if (ev.ctrlKey && !ev.shiftKey && !ev.altKey && !ev.metaKey) {
|
||||||
this.scrollToBottom();
|
this.scrollToBottom();
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
@ -547,8 +556,10 @@ module.exports = React.createClass({
|
||||||
var messages = this.refs.itemlist.children;
|
var messages = this.refs.itemlist.children;
|
||||||
for (var i = messages.length-1; i >= 0; --i) {
|
for (var i = messages.length-1; i >= 0; --i) {
|
||||||
var m = messages[i];
|
var m = messages[i];
|
||||||
if (!m.dataset.scrollToken) continue;
|
// 'data-scroll-tokens' is a DOMString of comma-separated scroll tokens
|
||||||
if (m.dataset.scrollToken == scrollToken) {
|
// There might only be one scroll token
|
||||||
|
if (m.dataset.scrollTokens &&
|
||||||
|
m.dataset.scrollTokens.split(',').indexOf(scrollToken) !== -1) {
|
||||||
node = m;
|
node = m;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
@ -564,7 +575,7 @@ module.exports = React.createClass({
|
||||||
var boundingRect = node.getBoundingClientRect();
|
var boundingRect = node.getBoundingClientRect();
|
||||||
var scrollDelta = boundingRect.bottom + pixelOffset - wrapperRect.bottom;
|
var scrollDelta = boundingRect.bottom + pixelOffset - wrapperRect.bottom;
|
||||||
|
|
||||||
debuglog("ScrollPanel: scrolling to token '" + node.dataset.scrollToken + "'+" +
|
debuglog("ScrollPanel: scrolling to token '" + scrollToken + "'+" +
|
||||||
pixelOffset + " (delta: "+scrollDelta+")");
|
pixelOffset + " (delta: "+scrollDelta+")");
|
||||||
|
|
||||||
if(scrollDelta != 0) {
|
if(scrollDelta != 0) {
|
||||||
|
@ -587,12 +598,12 @@ module.exports = React.createClass({
|
||||||
|
|
||||||
for (var i = messages.length-1; i >= 0; --i) {
|
for (var i = messages.length-1; i >= 0; --i) {
|
||||||
var node = messages[i];
|
var node = messages[i];
|
||||||
if (!node.dataset.scrollToken) continue;
|
if (!node.dataset.scrollTokens) continue;
|
||||||
|
|
||||||
var boundingRect = node.getBoundingClientRect();
|
var boundingRect = node.getBoundingClientRect();
|
||||||
newScrollState = {
|
newScrollState = {
|
||||||
stuckAtBottom: false,
|
stuckAtBottom: false,
|
||||||
trackedScrollToken: node.dataset.scrollToken,
|
trackedScrollToken: node.dataset.scrollTokens.split(',')[0],
|
||||||
pixelOffset: wrapperRect.bottom - boundingRect.bottom,
|
pixelOffset: wrapperRect.bottom - boundingRect.bottom,
|
||||||
};
|
};
|
||||||
// If the bottom of the panel intersects the ClientRect of node, use this node
|
// If the bottom of the panel intersects the ClientRect of node, use this node
|
||||||
|
@ -604,7 +615,7 @@ module.exports = React.createClass({
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// This is only false if there were no nodes with `node.dataset.scrollToken` set.
|
// This is only false if there were no nodes with `node.dataset.scrollTokens` set.
|
||||||
if (newScrollState) {
|
if (newScrollState) {
|
||||||
this.scrollState = newScrollState;
|
this.scrollState = newScrollState;
|
||||||
debuglog("ScrollPanel: saved scroll state", this.scrollState);
|
debuglog("ScrollPanel: saved scroll state", this.scrollState);
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
/*
|
/*
|
||||||
Copyright 2016 OpenMarket Ltd
|
Copyright 2016 OpenMarket Ltd
|
||||||
|
Copyright 2017 Vector Creations Ltd
|
||||||
|
|
||||||
Licensed under the Apache License, Version 2.0 (the "License");
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
you may not use this file except in compliance with the License.
|
you may not use this file except in compliance with the License.
|
||||||
|
@ -22,12 +23,14 @@ var Matrix = require("matrix-js-sdk");
|
||||||
var EventTimeline = Matrix.EventTimeline;
|
var EventTimeline = Matrix.EventTimeline;
|
||||||
|
|
||||||
var sdk = require('../../index');
|
var sdk = require('../../index');
|
||||||
|
import { _t } from '../../languageHandler';
|
||||||
var MatrixClientPeg = require("../../MatrixClientPeg");
|
var MatrixClientPeg = require("../../MatrixClientPeg");
|
||||||
var dis = require("../../dispatcher");
|
var dis = require("../../dispatcher");
|
||||||
var ObjectUtils = require('../../ObjectUtils');
|
var ObjectUtils = require('../../ObjectUtils');
|
||||||
var Modal = require("../../Modal");
|
var Modal = require("../../Modal");
|
||||||
var UserActivity = require("../../UserActivity");
|
var UserActivity = require("../../UserActivity");
|
||||||
var KeyCode = require('../../KeyCode');
|
var KeyCode = require('../../KeyCode');
|
||||||
|
import UserSettingsStore from '../../UserSettingsStore';
|
||||||
|
|
||||||
var PAGINATE_SIZE = 20;
|
var PAGINATE_SIZE = 20;
|
||||||
var INITIAL_SIZE = 20;
|
var INITIAL_SIZE = 20;
|
||||||
|
@ -102,9 +105,6 @@ var TimelinePanel = React.createClass({
|
||||||
},
|
},
|
||||||
|
|
||||||
statics: {
|
statics: {
|
||||||
// a map from room id to read marker event ID
|
|
||||||
roomReadMarkerMap: {},
|
|
||||||
|
|
||||||
// a map from room id to read marker event timestamp
|
// a map from room id to read marker event timestamp
|
||||||
roomReadMarkerTsMap: {},
|
roomReadMarkerTsMap: {},
|
||||||
},
|
},
|
||||||
|
@ -121,11 +121,17 @@ var TimelinePanel = React.createClass({
|
||||||
getInitialState: function() {
|
getInitialState: function() {
|
||||||
// XXX: we could track RM per TimelineSet rather than per Room.
|
// XXX: we could track RM per TimelineSet rather than per Room.
|
||||||
// but for now we just do it per room for simplicity.
|
// but for now we just do it per room for simplicity.
|
||||||
|
let initialReadMarker = null;
|
||||||
if (this.props.manageReadMarkers) {
|
if (this.props.manageReadMarkers) {
|
||||||
var initialReadMarker =
|
const readmarker = this.props.timelineSet.room.getAccountData('m.fully_read');
|
||||||
TimelinePanel.roomReadMarkerMap[this.props.timelineSet.room.roomId]
|
if (readmarker) {
|
||||||
|| this._getCurrentReadReceipt();
|
initialReadMarker = readmarker.getContent().event_id;
|
||||||
|
} else {
|
||||||
|
initialReadMarker = this._getCurrentReadReceipt();
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const syncedSettings = UserSettingsStore.getSyncedSettings();
|
||||||
|
|
||||||
return {
|
return {
|
||||||
events: [],
|
events: [],
|
||||||
|
@ -166,13 +172,26 @@ var TimelinePanel = React.createClass({
|
||||||
|
|
||||||
backPaginating: false,
|
backPaginating: false,
|
||||||
forwardPaginating: false,
|
forwardPaginating: false,
|
||||||
|
|
||||||
|
// cache of matrixClient.getSyncState() (but from the 'sync' event)
|
||||||
|
clientSyncState: MatrixClientPeg.get().getSyncState(),
|
||||||
|
|
||||||
|
// should the event tiles have twelve hour times
|
||||||
|
isTwelveHour: syncedSettings.showTwelveHourTimestamps,
|
||||||
|
|
||||||
|
// always show timestamps on event tiles?
|
||||||
|
alwaysShowTimestamps: syncedSettings.alwaysShowTimestamps,
|
||||||
|
|
||||||
|
// hide redacted events as per old behaviour
|
||||||
|
hideRedactions: syncedSettings.hideRedactions,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|
||||||
componentWillMount: function() {
|
componentWillMount: function() {
|
||||||
debuglog("TimelinePanel: mounting");
|
debuglog("TimelinePanel: mounting");
|
||||||
|
|
||||||
this.last_rr_sent_event_id = undefined;
|
this.lastRRSentEventId = undefined;
|
||||||
|
this.lastRMSentEventId = undefined;
|
||||||
|
|
||||||
this.dispatcherRef = dis.register(this.onAction);
|
this.dispatcherRef = dis.register(this.onAction);
|
||||||
MatrixClientPeg.get().on("Room.timeline", this.onRoomTimeline);
|
MatrixClientPeg.get().on("Room.timeline", this.onRoomTimeline);
|
||||||
|
@ -180,6 +199,8 @@ var TimelinePanel = React.createClass({
|
||||||
MatrixClientPeg.get().on("Room.redaction", this.onRoomRedaction);
|
MatrixClientPeg.get().on("Room.redaction", this.onRoomRedaction);
|
||||||
MatrixClientPeg.get().on("Room.receipt", this.onRoomReceipt);
|
MatrixClientPeg.get().on("Room.receipt", this.onRoomReceipt);
|
||||||
MatrixClientPeg.get().on("Room.localEchoUpdated", this.onLocalEchoUpdated);
|
MatrixClientPeg.get().on("Room.localEchoUpdated", this.onLocalEchoUpdated);
|
||||||
|
MatrixClientPeg.get().on("Room.accountData", this.onAccountData);
|
||||||
|
MatrixClientPeg.get().on("sync", this.onSync);
|
||||||
|
|
||||||
this._initTimeline(this.props);
|
this._initTimeline(this.props);
|
||||||
},
|
},
|
||||||
|
@ -247,6 +268,8 @@ var TimelinePanel = React.createClass({
|
||||||
client.removeListener("Room.redaction", this.onRoomRedaction);
|
client.removeListener("Room.redaction", this.onRoomRedaction);
|
||||||
client.removeListener("Room.receipt", this.onRoomReceipt);
|
client.removeListener("Room.receipt", this.onRoomReceipt);
|
||||||
client.removeListener("Room.localEchoUpdated", this.onLocalEchoUpdated);
|
client.removeListener("Room.localEchoUpdated", this.onLocalEchoUpdated);
|
||||||
|
client.removeListener("Room.accountData", this.onAccountData);
|
||||||
|
client.removeListener("sync", this.onSync);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -414,6 +437,7 @@ var TimelinePanel = React.createClass({
|
||||||
} else if(lastEv && this.getReadMarkerPosition() === 0) {
|
} else if(lastEv && this.getReadMarkerPosition() === 0) {
|
||||||
// we know we're stuckAtBottom, so we can advance the RM
|
// we know we're stuckAtBottom, so we can advance the RM
|
||||||
// immediately, to save a later render cycle
|
// immediately, to save a later render cycle
|
||||||
|
|
||||||
this._setReadMarker(lastEv.getId(), lastEv.getTs(), true);
|
this._setReadMarker(lastEv.getId(), lastEv.getTs(), true);
|
||||||
updatedState.readMarkerVisible = false;
|
updatedState.readMarkerVisible = false;
|
||||||
updatedState.readMarkerEventId = lastEv.getId();
|
updatedState.readMarkerEventId = lastEv.getId();
|
||||||
|
@ -466,6 +490,25 @@ var TimelinePanel = React.createClass({
|
||||||
this._reloadEvents();
|
this._reloadEvents();
|
||||||
},
|
},
|
||||||
|
|
||||||
|
onAccountData: function(ev, room) {
|
||||||
|
if (this.unmounted) return;
|
||||||
|
|
||||||
|
// ignore events for other rooms
|
||||||
|
if (room !== this.props.timelineSet.room) return;
|
||||||
|
|
||||||
|
if (ev.getType() !== "m.fully_read") return;
|
||||||
|
|
||||||
|
// XXX: roomReadMarkerTsMap not updated here so it is now inconsistent. Replace
|
||||||
|
// this mechanism of determining where the RM is relative to the view-port with
|
||||||
|
// one supported by the server (the client needs more than an event ID).
|
||||||
|
this.setState({
|
||||||
|
readMarkerEventId: ev.getContent().event_id,
|
||||||
|
}, this.props.onReadMarkerUpdated);
|
||||||
|
},
|
||||||
|
|
||||||
|
onSync: function(state, prevState, data) {
|
||||||
|
this.setState({clientSyncState: state});
|
||||||
|
},
|
||||||
|
|
||||||
sendReadReceipt: function() {
|
sendReadReceipt: function() {
|
||||||
if (!this.refs.messagePanel) return;
|
if (!this.refs.messagePanel) return;
|
||||||
|
@ -473,11 +516,14 @@ var TimelinePanel = React.createClass({
|
||||||
// This happens on user_activity_end which is delayed, and it's
|
// This happens on user_activity_end which is delayed, and it's
|
||||||
// very possible have logged out within that timeframe, so check
|
// very possible have logged out within that timeframe, so check
|
||||||
// we still have a client.
|
// we still have a client.
|
||||||
if (!MatrixClientPeg.get()) return;
|
const cli = MatrixClientPeg.get();
|
||||||
|
// if no client or client is guest don't send RR or RM
|
||||||
|
if (!cli || cli.isGuest()) return;
|
||||||
|
|
||||||
var currentReadUpToEventId = this._getCurrentReadReceipt(true);
|
let shouldSendRR = true;
|
||||||
var currentReadUpToEventIndex = this._indexForEventId(currentReadUpToEventId);
|
|
||||||
|
|
||||||
|
const currentRREventId = this._getCurrentReadReceipt(true);
|
||||||
|
const currentRREventIndex = this._indexForEventId(currentRREventId);
|
||||||
// We want to avoid sending out read receipts when we are looking at
|
// We want to avoid sending out read receipts when we are looking at
|
||||||
// events in the past which are before the latest RR.
|
// events in the past which are before the latest RR.
|
||||||
//
|
//
|
||||||
|
@ -491,26 +537,60 @@ var TimelinePanel = React.createClass({
|
||||||
// RRs) - but that is a bit of a niche case. It will sort itself out when
|
// RRs) - but that is a bit of a niche case. It will sort itself out when
|
||||||
// the user eventually hits the live timeline.
|
// the user eventually hits the live timeline.
|
||||||
//
|
//
|
||||||
if (currentReadUpToEventId && currentReadUpToEventIndex === null &&
|
if (currentRREventId && currentRREventIndex === null &&
|
||||||
this._timelineWindow.canPaginate(EventTimeline.FORWARDS)) {
|
this._timelineWindow.canPaginate(EventTimeline.FORWARDS)) {
|
||||||
return;
|
shouldSendRR = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
var lastReadEventIndex = this._getLastDisplayedEventIndex({
|
const lastReadEventIndex = this._getLastDisplayedEventIndex({
|
||||||
ignoreOwn: true
|
ignoreOwn: true,
|
||||||
});
|
});
|
||||||
if (lastReadEventIndex === null) return;
|
if (lastReadEventIndex === null) {
|
||||||
|
shouldSendRR = false;
|
||||||
|
}
|
||||||
|
let lastReadEvent = this.state.events[lastReadEventIndex];
|
||||||
|
shouldSendRR = shouldSendRR &&
|
||||||
|
// Only send a RR if the last read event is ahead in the timeline relative to
|
||||||
|
// the current RR event.
|
||||||
|
lastReadEventIndex > currentRREventIndex &&
|
||||||
|
// Only send a RR if the last RR set != the one we would send
|
||||||
|
this.lastRRSentEventId != lastReadEvent.getId();
|
||||||
|
|
||||||
var lastReadEvent = this.state.events[lastReadEventIndex];
|
// Only send a RM if the last RM sent != the one we would send
|
||||||
|
const shouldSendRM =
|
||||||
|
this.lastRMSentEventId != this.state.readMarkerEventId;
|
||||||
|
|
||||||
// we also remember the last read receipt we sent to avoid spamming the
|
// we also remember the last read receipt we sent to avoid spamming the
|
||||||
// same one at the server repeatedly
|
// same one at the server repeatedly
|
||||||
if (lastReadEventIndex > currentReadUpToEventIndex
|
if (shouldSendRR || shouldSendRM) {
|
||||||
&& this.last_rr_sent_event_id != lastReadEvent.getId()) {
|
if (shouldSendRR) {
|
||||||
this.last_rr_sent_event_id = lastReadEvent.getId();
|
this.lastRRSentEventId = lastReadEvent.getId();
|
||||||
MatrixClientPeg.get().sendReadReceipt(lastReadEvent).catch(() => {
|
} else {
|
||||||
|
lastReadEvent = null;
|
||||||
|
}
|
||||||
|
this.lastRMSentEventId = this.state.readMarkerEventId;
|
||||||
|
|
||||||
|
debuglog('TimelinePanel: Sending Read Markers for ',
|
||||||
|
this.props.timelineSet.room.roomId,
|
||||||
|
'rm', this.state.readMarkerEventId,
|
||||||
|
lastReadEvent ? 'rr ' + lastReadEvent.getId() : '',
|
||||||
|
);
|
||||||
|
MatrixClientPeg.get().setRoomReadMarkers(
|
||||||
|
this.props.timelineSet.room.roomId,
|
||||||
|
this.state.readMarkerEventId,
|
||||||
|
lastReadEvent, // Could be null, in which case no RR is sent
|
||||||
|
).catch((e) => {
|
||||||
|
// /read_markers API is not implemented on this HS, fallback to just RR
|
||||||
|
if (e.errcode === 'M_UNRECOGNIZED' && lastReadEvent) {
|
||||||
|
return MatrixClientPeg.get().sendReadReceipt(
|
||||||
|
lastReadEvent,
|
||||||
|
).catch(() => {
|
||||||
|
this.lastRRSentEventId = undefined;
|
||||||
|
});
|
||||||
|
}
|
||||||
// it failed, so allow retries next time the user is active
|
// it failed, so allow retries next time the user is active
|
||||||
this.last_rr_sent_event_id = undefined;
|
this.lastRRSentEventId = undefined;
|
||||||
|
this.lastRMSentEventId = undefined;
|
||||||
});
|
});
|
||||||
|
|
||||||
// do a quick-reset of our unreadNotificationCount to avoid having
|
// do a quick-reset of our unreadNotificationCount to avoid having
|
||||||
|
@ -706,7 +786,7 @@ var TimelinePanel = React.createClass({
|
||||||
|
|
||||||
// the messagePanel doesn't know where the read marker is.
|
// the messagePanel doesn't know where the read marker is.
|
||||||
// if we know the timestamp of the read marker, make a guess based on that.
|
// if we know the timestamp of the read marker, make a guess based on that.
|
||||||
var rmTs = TimelinePanel.roomReadMarkerTsMap[this.props.timelineSet.roomId];
|
const rmTs = TimelinePanel.roomReadMarkerTsMap[this.props.timelineSet.room.roomId];
|
||||||
if (rmTs && this.state.events.length > 0) {
|
if (rmTs && this.state.events.length > 0) {
|
||||||
if (rmTs < this.state.events[0].getTs()) {
|
if (rmTs < this.state.events[0].getTs()) {
|
||||||
return -1;
|
return -1;
|
||||||
|
@ -718,6 +798,19 @@ var TimelinePanel = React.createClass({
|
||||||
return null;
|
return null;
|
||||||
},
|
},
|
||||||
|
|
||||||
|
canJumpToReadMarker: function() {
|
||||||
|
// 1. Do not show jump bar if neither the RM nor the RR are set.
|
||||||
|
// 2. Only show jump bar if RR !== RM. If they are the same, there are only fully
|
||||||
|
// read messages and unread messages. We already have a badge count and the bottom
|
||||||
|
// bar to jump to "live" when we have unread messages.
|
||||||
|
// 3. We want to show the bar if the read-marker is off the top of the screen.
|
||||||
|
// 4. Also, if pos === null, the event might not be paginated - show the unread bar
|
||||||
|
const pos = this.getReadMarkerPosition();
|
||||||
|
return this.state.readMarkerEventId !== null && // 1.
|
||||||
|
this.state.readMarkerEventId !== this._getCurrentReadReceipt() && // 2.
|
||||||
|
(pos < 0 || pos === null); // 3., 4.
|
||||||
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* called by the parent component when PageUp/Down/etc is pressed.
|
* called by the parent component when PageUp/Down/etc is pressed.
|
||||||
*
|
*
|
||||||
|
@ -728,7 +821,9 @@ var TimelinePanel = React.createClass({
|
||||||
|
|
||||||
// jump to the live timeline on ctrl-end, rather than the end of the
|
// jump to the live timeline on ctrl-end, rather than the end of the
|
||||||
// timeline window.
|
// timeline window.
|
||||||
if (ev.ctrlKey && ev.keyCode == KeyCode.END) {
|
if (ev.ctrlKey && !ev.shiftKey && !ev.altKey && !ev.metaKey &&
|
||||||
|
ev.keyCode == KeyCode.END)
|
||||||
|
{
|
||||||
this.jumpToLiveTimeline();
|
this.jumpToLiveTimeline();
|
||||||
} else {
|
} else {
|
||||||
this.refs.messagePanel.handleScrollKey(ev);
|
this.refs.messagePanel.handleScrollKey(ev);
|
||||||
|
@ -825,14 +920,11 @@ var TimelinePanel = React.createClass({
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
var message = "Tried to load a specific point in this room's timeline, but ";
|
var message = (error.errcode == 'M_FORBIDDEN')
|
||||||
if (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") + "."
|
||||||
message += "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") + ".";
|
||||||
} else {
|
|
||||||
message += "was unable to find it.";
|
|
||||||
}
|
|
||||||
Modal.createDialog(ErrorDialog, {
|
Modal.createDialog(ErrorDialog, {
|
||||||
title: "Failed to load timeline position",
|
title: _t("Failed to load timeline position"),
|
||||||
description: message,
|
description: message,
|
||||||
onFinished: onFinished,
|
onFinished: onFinished,
|
||||||
});
|
});
|
||||||
|
@ -956,16 +1048,12 @@ var TimelinePanel = React.createClass({
|
||||||
_setReadMarker: function(eventId, eventTs, inhibitSetState) {
|
_setReadMarker: function(eventId, eventTs, inhibitSetState) {
|
||||||
var roomId = this.props.timelineSet.room.roomId;
|
var roomId = this.props.timelineSet.room.roomId;
|
||||||
|
|
||||||
if (TimelinePanel.roomReadMarkerMap[roomId] == eventId) {
|
|
||||||
// don't update the state (and cause a re-render) if there is
|
// don't update the state (and cause a re-render) if there is
|
||||||
// no change to the RM.
|
// no change to the RM.
|
||||||
|
if (eventId === this.state.readMarkerEventId) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// ideally we'd sync these via the server, but for now just stash them
|
|
||||||
// in a map.
|
|
||||||
TimelinePanel.roomReadMarkerMap[roomId] = eventId;
|
|
||||||
|
|
||||||
// in order to later figure out if the read marker is
|
// in order to later figure out if the read marker is
|
||||||
// above or below the visible timeline, we stash the timestamp.
|
// above or below the visible timeline, we stash the timestamp.
|
||||||
TimelinePanel.roomReadMarkerTsMap[roomId] = eventTs;
|
TimelinePanel.roomReadMarkerTsMap[roomId] = eventTs;
|
||||||
|
@ -974,6 +1062,7 @@ var TimelinePanel = React.createClass({
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Do the local echo of the RM
|
||||||
// run the render cycle before calling the callback, so that
|
// run the render cycle before calling the callback, so that
|
||||||
// getReadMarkerPosition() returns the right thing.
|
// getReadMarkerPosition() returns the right thing.
|
||||||
this.setState({
|
this.setState({
|
||||||
|
@ -1022,11 +1111,17 @@ var TimelinePanel = React.createClass({
|
||||||
// of paginating our way through the entire history of the room.
|
// of paginating our way through the entire history of the room.
|
||||||
var stickyBottom = !this._timelineWindow.canPaginate(EventTimeline.FORWARDS);
|
var 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.
|
||||||
|
const forwardPaginating = (
|
||||||
|
this.state.forwardPaginating || this.state.clientSyncState == 'PREPARED'
|
||||||
|
);
|
||||||
return (
|
return (
|
||||||
<MessagePanel ref="messagePanel"
|
<MessagePanel ref="messagePanel"
|
||||||
hidden={ this.props.hidden }
|
hidden={ this.props.hidden }
|
||||||
|
hideRedactions={ this.state.hideRedactions }
|
||||||
backPaginating={ this.state.backPaginating }
|
backPaginating={ this.state.backPaginating }
|
||||||
forwardPaginating={ this.state.forwardPaginating }
|
forwardPaginating={ forwardPaginating }
|
||||||
events={ this.state.events }
|
events={ this.state.events }
|
||||||
highlightedEventId={ this.props.highlightedEventId }
|
highlightedEventId={ this.props.highlightedEventId }
|
||||||
readMarkerEventId={ this.state.readMarkerEventId }
|
readMarkerEventId={ this.state.readMarkerEventId }
|
||||||
|
@ -1040,6 +1135,8 @@ var TimelinePanel = React.createClass({
|
||||||
onFillRequest={ this.onMessageListFillRequest }
|
onFillRequest={ this.onMessageListFillRequest }
|
||||||
onUnfillRequest={ this.onMessageListUnfillRequest }
|
onUnfillRequest={ this.onMessageListUnfillRequest }
|
||||||
opacity={ this.props.opacity }
|
opacity={ this.props.opacity }
|
||||||
|
isTwelveHour={ this.state.isTwelveHour }
|
||||||
|
alwaysShowTimestamps={ this.state.alwaysShowTimestamps }
|
||||||
className={ this.props.className }
|
className={ this.props.className }
|
||||||
tileShape={ this.props.tileShape }
|
tileShape={ this.props.tileShape }
|
||||||
/>
|
/>
|
||||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -17,6 +17,7 @@ limitations under the License.
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
var React = require('react');
|
var React = require('react');
|
||||||
|
import { _t } from '../../../languageHandler';
|
||||||
var sdk = require('../../../index');
|
var sdk = require('../../../index');
|
||||||
var Modal = require("../../../Modal");
|
var Modal = require("../../../Modal");
|
||||||
var MatrixClientPeg = require('../../../MatrixClientPeg');
|
var MatrixClientPeg = require('../../../MatrixClientPeg');
|
||||||
|
@ -54,7 +55,7 @@ module.exports = React.createClass({
|
||||||
progress: "sent_email"
|
progress: "sent_email"
|
||||||
});
|
});
|
||||||
}, (err) => {
|
}, (err) => {
|
||||||
this.showErrorDialog("Failed to send email: " + err.message);
|
this.showErrorDialog(_t('Failed to send email') + ": " + err.message);
|
||||||
this.setState({
|
this.setState({
|
||||||
progress: null
|
progress: null
|
||||||
});
|
});
|
||||||
|
@ -78,30 +79,33 @@ module.exports = React.createClass({
|
||||||
ev.preventDefault();
|
ev.preventDefault();
|
||||||
|
|
||||||
if (!this.state.email) {
|
if (!this.state.email) {
|
||||||
this.showErrorDialog("The email address linked to your account must be entered.");
|
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("A new password must be entered.");
|
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("New passwords must match each other.");
|
this.showErrorDialog(_t('New passwords must match each other.'));
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
var QuestionDialog = sdk.getComponent("dialogs.QuestionDialog");
|
var QuestionDialog = sdk.getComponent("dialogs.QuestionDialog");
|
||||||
Modal.createDialog(QuestionDialog, {
|
Modal.createDialog(QuestionDialog, {
|
||||||
title: "Warning",
|
title: _t('Warning!'),
|
||||||
description:
|
description:
|
||||||
<div>
|
<div>
|
||||||
Resetting password will currently reset any end-to-end encryption keys on all devices,
|
{ _t(
|
||||||
making encrypted chat history unreadable, unless you first export your room keys
|
'Resetting password will currently reset any ' +
|
||||||
and re-import them afterwards.
|
'end-to-end encryption keys on all devices, ' +
|
||||||
In future this <a href="https://github.com/vector-im/riot-web/issues/2671">will be improved</a>.
|
'making encrypted chat history unreadable, ' +
|
||||||
|
'unless you first export your room keys and re-import ' +
|
||||||
|
'them afterwards. In future this will be improved.'
|
||||||
|
) }
|
||||||
</div>,
|
</div>,
|
||||||
button: "Continue",
|
button: _t('Continue'),
|
||||||
extraButtons: [
|
extraButtons: [
|
||||||
<button className="mx_Dialog_primary"
|
<button className="mx_Dialog_primary"
|
||||||
onClick={this._onExportE2eKeysClicked}>
|
onClick={this._onExportE2eKeysClicked}>
|
||||||
Export E2E room keys
|
{ _t('Export E2E room keys') }
|
||||||
</button>
|
</button>
|
||||||
],
|
],
|
||||||
onFinished: (confirmed) => {
|
onFinished: (confirmed) => {
|
||||||
|
@ -150,7 +154,7 @@ module.exports = React.createClass({
|
||||||
var ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
|
var ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
|
||||||
Modal.createDialog(ErrorDialog, {
|
Modal.createDialog(ErrorDialog, {
|
||||||
title: title,
|
title: title,
|
||||||
description: body
|
description: body,
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -168,22 +172,20 @@ module.exports = React.createClass({
|
||||||
else if (this.state.progress === "sent_email") {
|
else if (this.state.progress === "sent_email") {
|
||||||
resetPasswordJsx = (
|
resetPasswordJsx = (
|
||||||
<div>
|
<div>
|
||||||
An email has been sent to {this.state.email}. Once you've followed
|
{ _t('An email has been sent to') } {this.state.email}. { _t('Once you've followed the link it contains, click below') }.
|
||||||
the link it contains, click below.
|
|
||||||
<br />
|
<br />
|
||||||
<input className="mx_Login_submit" type="button" onClick={this.onVerify}
|
<input className="mx_Login_submit" type="button" onClick={this.onVerify}
|
||||||
value="I have verified my email address" />
|
value={ _t('I have verified my email address') } />
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
else if (this.state.progress === "complete") {
|
else if (this.state.progress === "complete") {
|
||||||
resetPasswordJsx = (
|
resetPasswordJsx = (
|
||||||
<div>
|
<div>
|
||||||
<p>Your password has been reset.</p>
|
<p>{ _t('Your password has been reset') }.</p>
|
||||||
<p>You have been logged out of all devices and will no longer receive push notifications.
|
<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>
|
||||||
To re-enable notifications, sign in again on each device.</p>
|
|
||||||
<input className="mx_Login_submit" type="button" onClick={this.props.onComplete}
|
<input className="mx_Login_submit" type="button" onClick={this.props.onComplete}
|
||||||
value="Return to login screen" />
|
value={ _t('Return to login screen') } />
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -191,7 +193,7 @@ module.exports = React.createClass({
|
||||||
resetPasswordJsx = (
|
resetPasswordJsx = (
|
||||||
<div>
|
<div>
|
||||||
<div className="mx_Login_prompt">
|
<div className="mx_Login_prompt">
|
||||||
To reset your password, enter the email address linked to your account:
|
{ _t('To reset your password, enter the email address linked to your account') }:
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<form onSubmit={this.onSubmitForm}>
|
<form onSubmit={this.onSubmitForm}>
|
||||||
|
@ -199,21 +201,21 @@ module.exports = React.createClass({
|
||||||
name="reset_email" // define a name so browser's password autofill gets less confused
|
name="reset_email" // define a name so browser's password autofill gets less confused
|
||||||
value={this.state.email}
|
value={this.state.email}
|
||||||
onChange={this.onInputChanged.bind(this, "email")}
|
onChange={this.onInputChanged.bind(this, "email")}
|
||||||
placeholder="Email address" autoFocus />
|
placeholder={ _t('Email address') } autoFocus />
|
||||||
<br />
|
<br />
|
||||||
<input className="mx_Login_field" ref="pass" type="password"
|
<input className="mx_Login_field" ref="pass" type="password"
|
||||||
name="reset_password"
|
name="reset_password"
|
||||||
value={this.state.password}
|
value={this.state.password}
|
||||||
onChange={this.onInputChanged.bind(this, "password")}
|
onChange={this.onInputChanged.bind(this, "password")}
|
||||||
placeholder="New password" />
|
placeholder={ _t('New password') } />
|
||||||
<br />
|
<br />
|
||||||
<input className="mx_Login_field" ref="pass" type="password"
|
<input className="mx_Login_field" ref="pass" type="password"
|
||||||
name="reset_password_confirm"
|
name="reset_password_confirm"
|
||||||
value={this.state.password2}
|
value={this.state.password2}
|
||||||
onChange={this.onInputChanged.bind(this, "password2")}
|
onChange={this.onInputChanged.bind(this, "password2")}
|
||||||
placeholder="Confirm your new password" />
|
placeholder={ _t('Confirm your new password') } />
|
||||||
<br />
|
<br />
|
||||||
<input className="mx_Login_submit" type="submit" value="Send Reset Email" />
|
<input className="mx_Login_submit" type="submit" value={ _t('Send Reset Email') } />
|
||||||
</form>
|
</form>
|
||||||
<ServerConfig ref="serverConfig"
|
<ServerConfig ref="serverConfig"
|
||||||
withToggleButton={true}
|
withToggleButton={true}
|
||||||
|
@ -227,10 +229,10 @@ module.exports = React.createClass({
|
||||||
<div className="mx_Login_error">
|
<div className="mx_Login_error">
|
||||||
</div>
|
</div>
|
||||||
<a className="mx_Login_create" onClick={this.props.onLoginClick} href="#">
|
<a className="mx_Login_create" onClick={this.props.onLoginClick} href="#">
|
||||||
Return to login
|
{_t('Return to login screen')}
|
||||||
</a>
|
</a>
|
||||||
<a className="mx_Login_create" onClick={this.props.onRegisterClick} href="#">
|
<a className="mx_Login_create" onClick={this.props.onRegisterClick} href="#">
|
||||||
Create a new account
|
{ _t('Create an account') }
|
||||||
</a>
|
</a>
|
||||||
<LoginFooter />
|
<LoginFooter />
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -17,13 +17,14 @@ limitations under the License.
|
||||||
|
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
var React = require('react');
|
import React from 'react';
|
||||||
var ReactDOM = require('react-dom');
|
import { _t, _tJsx } from '../../../languageHandler';
|
||||||
var sdk = require('../../../index');
|
import ReactDOM from 'react-dom';
|
||||||
var Login = require("../../../Login");
|
import sdk from '../../../index';
|
||||||
var PasswordLogin = require("../../views/login/PasswordLogin");
|
import Login from '../../../Login';
|
||||||
var CasLogin = require("../../views/login/CasLogin");
|
|
||||||
var ServerConfig = require("../../views/login/ServerConfig");
|
// For validating phone numbers without country codes
|
||||||
|
const PHONE_NUMBER_REGEX = /^[0-9\(\)\-\s]*$/;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A wire component which glues together login UI components and Login logic
|
* A wire component which glues together login UI components and Login logic
|
||||||
|
@ -67,6 +68,7 @@ module.exports = React.createClass({
|
||||||
username: "",
|
username: "",
|
||||||
phoneCountry: null,
|
phoneCountry: null,
|
||||||
phoneNumber: "",
|
phoneNumber: "",
|
||||||
|
currentFlow: "m.login.password",
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -126,26 +128,31 @@ module.exports = React.createClass({
|
||||||
},
|
},
|
||||||
|
|
||||||
onPhoneNumberChanged: function(phoneNumber) {
|
onPhoneNumberChanged: function(phoneNumber) {
|
||||||
this.setState({ phoneNumber: phoneNumber });
|
// Validate the phone number entered
|
||||||
},
|
if (!PHONE_NUMBER_REGEX.test(phoneNumber)) {
|
||||||
|
this.setState({ errorText: 'The phone number entered looks invalid' });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
onHsUrlChanged: function(newHsUrl) {
|
|
||||||
var self = this;
|
|
||||||
this.setState({
|
this.setState({
|
||||||
enteredHomeserverUrl: newHsUrl,
|
phoneNumber: phoneNumber,
|
||||||
errorText: null, // reset err messages
|
errorText: null,
|
||||||
}, function() {
|
|
||||||
self._initLoginLogic(newHsUrl);
|
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
onIsUrlChanged: function(newIsUrl) {
|
onServerConfigChange: function(config) {
|
||||||
var self = this;
|
var self = this;
|
||||||
this.setState({
|
let newState = {
|
||||||
enteredIdentityServerUrl: newIsUrl,
|
|
||||||
errorText: null, // reset err messages
|
errorText: null, // reset err messages
|
||||||
}, function() {
|
};
|
||||||
self._initLoginLogic(null, newIsUrl);
|
if (config.hsUrl !== undefined) {
|
||||||
|
newState.enteredHomeserverUrl = config.hsUrl;
|
||||||
|
}
|
||||||
|
if (config.isUrl !== undefined) {
|
||||||
|
newState.enteredIdentityServerUrl = config.isUrl;
|
||||||
|
}
|
||||||
|
this.setState(newState, function() {
|
||||||
|
self._initLoginLogic(config.hsUrl || null, config.isUrl);
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -161,25 +168,28 @@ module.exports = React.createClass({
|
||||||
});
|
});
|
||||||
this._loginLogic = loginLogic;
|
this._loginLogic = loginLogic;
|
||||||
|
|
||||||
loginLogic.getFlows().then(function(flows) {
|
|
||||||
// old behaviour was to always use the first flow without presenting
|
|
||||||
// options. This works in most cases (we don't have a UI for multiple
|
|
||||||
// logins so let's skip that for now).
|
|
||||||
loginLogic.chooseFlow(0);
|
|
||||||
}, function(err) {
|
|
||||||
self._setStateFromError(err, false);
|
|
||||||
}).finally(function() {
|
|
||||||
self.setState({
|
|
||||||
busy: false
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
this.setState({
|
this.setState({
|
||||||
enteredHomeserverUrl: hsUrl,
|
enteredHomeserverUrl: hsUrl,
|
||||||
enteredIdentityServerUrl: isUrl,
|
enteredIdentityServerUrl: isUrl,
|
||||||
busy: true,
|
busy: true,
|
||||||
loginIncorrect: false,
|
loginIncorrect: false,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
loginLogic.getFlows().then(function(flows) {
|
||||||
|
// old behaviour was to always use the first flow without presenting
|
||||||
|
// options. This works in most cases (we don't have a UI for multiple
|
||||||
|
// logins so let's skip that for now).
|
||||||
|
loginLogic.chooseFlow(0);
|
||||||
|
self.setState({
|
||||||
|
currentFlow: self._getCurrentFlowStep(),
|
||||||
|
});
|
||||||
|
}, function(err) {
|
||||||
|
self._setStateFromError(err, false);
|
||||||
|
}).finally(function() {
|
||||||
|
self.setState({
|
||||||
|
busy: false,
|
||||||
|
});
|
||||||
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
_getCurrentFlowStep: function() {
|
_getCurrentFlowStep: function() {
|
||||||
|
@ -213,14 +223,19 @@ module.exports = React.createClass({
|
||||||
!this.state.enteredHomeserverUrl.startsWith("http")))
|
!this.state.enteredHomeserverUrl.startsWith("http")))
|
||||||
{
|
{
|
||||||
errorText = <span>
|
errorText = <span>
|
||||||
Can't connect to homeserver via HTTP when an HTTPS URL is in your browser bar.
|
{ _tJsx("Can't connect to homeserver via HTTP when an HTTPS URL is in your browser bar. " +
|
||||||
Either use HTTPS or <a href='https://www.google.com/search?&q=enable%20unsafe%20scripts'>enable unsafe scripts</a>
|
"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>; }
|
||||||
|
)}
|
||||||
</span>;
|
</span>;
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
errorText = <span>
|
errorText = <span>
|
||||||
Can't connect to homeserver - please check your connectivity and ensure
|
{ _tJsx("Can't connect to homeserver - please check your connectivity and ensure your <a>homeserver's SSL certificate</a> is trusted.",
|
||||||
your <a href={ this.state.enteredHomeserverUrl }>homeserver's SSL certificate</a> is trusted.
|
/<a>(.*?)<\/a>/,
|
||||||
|
(sub) => { return <a href={this.state.enteredHomeserverUrl}>{ sub }</a>; }
|
||||||
|
)}
|
||||||
</span>;
|
</span>;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -231,6 +246,7 @@ module.exports = React.createClass({
|
||||||
componentForStep: function(step) {
|
componentForStep: function(step) {
|
||||||
switch (step) {
|
switch (step) {
|
||||||
case 'm.login.password':
|
case 'm.login.password':
|
||||||
|
const PasswordLogin = sdk.getComponent('login.PasswordLogin');
|
||||||
return (
|
return (
|
||||||
<PasswordLogin
|
<PasswordLogin
|
||||||
onSubmit={this.onPasswordLogin}
|
onSubmit={this.onPasswordLogin}
|
||||||
|
@ -245,6 +261,7 @@ module.exports = React.createClass({
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
case 'm.login.cas':
|
case 'm.login.cas':
|
||||||
|
const CasLogin = sdk.getComponent('login.CasLogin');
|
||||||
return (
|
return (
|
||||||
<CasLogin onSubmit={this.onCasLogin} />
|
<CasLogin onSubmit={this.onCasLogin} />
|
||||||
);
|
);
|
||||||
|
@ -254,24 +271,24 @@ module.exports = React.createClass({
|
||||||
}
|
}
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
Sorry, this homeserver is using a login which is not
|
{ _t('Sorry, this homeserver is using a login which is not recognised ')}({step})
|
||||||
recognised ({step})
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
render: function() {
|
render: function() {
|
||||||
var Loader = sdk.getComponent("elements.Spinner");
|
const Loader = sdk.getComponent("elements.Spinner");
|
||||||
var LoginHeader = sdk.getComponent("login.LoginHeader");
|
const LoginHeader = sdk.getComponent("login.LoginHeader");
|
||||||
var LoginFooter = sdk.getComponent("login.LoginFooter");
|
const LoginFooter = sdk.getComponent("login.LoginFooter");
|
||||||
var loader = this.state.busy ? <div className="mx_Login_loader"><Loader /></div> : null;
|
const ServerConfig = sdk.getComponent("login.ServerConfig");
|
||||||
|
const loader = this.state.busy ? <div className="mx_Login_loader"><Loader /></div> : null;
|
||||||
|
|
||||||
var loginAsGuestJsx;
|
var loginAsGuestJsx;
|
||||||
if (this.props.enableGuest) {
|
if (this.props.enableGuest) {
|
||||||
loginAsGuestJsx =
|
loginAsGuestJsx =
|
||||||
<a className="mx_Login_create" onClick={this._onLoginAsGuestClick} href="#">
|
<a className="mx_Login_create" onClick={this._onLoginAsGuestClick} href="#">
|
||||||
Login as guest
|
{ _t('Login as guest')}
|
||||||
</a>;
|
</a>;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -279,7 +296,7 @@ module.exports = React.createClass({
|
||||||
if (this.props.onCancelClick) {
|
if (this.props.onCancelClick) {
|
||||||
returnToAppJsx =
|
returnToAppJsx =
|
||||||
<a className="mx_Login_create" onClick={this.props.onCancelClick} href="#">
|
<a className="mx_Login_create" onClick={this.props.onCancelClick} href="#">
|
||||||
Return to app
|
{ _t('Return to app')}
|
||||||
</a>;
|
</a>;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -288,24 +305,23 @@ module.exports = React.createClass({
|
||||||
<div className="mx_Login_box">
|
<div className="mx_Login_box">
|
||||||
<LoginHeader />
|
<LoginHeader />
|
||||||
<div>
|
<div>
|
||||||
<h2>Sign in
|
<h2>{ _t('Sign in')}
|
||||||
{ loader }
|
{ loader }
|
||||||
</h2>
|
</h2>
|
||||||
{ this.componentForStep(this._getCurrentFlowStep()) }
|
{ this.componentForStep(this.state.currentFlow) }
|
||||||
<ServerConfig ref="serverConfig"
|
<ServerConfig ref="serverConfig"
|
||||||
withToggleButton={true}
|
withToggleButton={true}
|
||||||
customHsUrl={this.props.customHsUrl}
|
customHsUrl={this.props.customHsUrl}
|
||||||
customIsUrl={this.props.customIsUrl}
|
customIsUrl={this.props.customIsUrl}
|
||||||
defaultHsUrl={this.props.defaultHsUrl}
|
defaultHsUrl={this.props.defaultHsUrl}
|
||||||
defaultIsUrl={this.props.defaultIsUrl}
|
defaultIsUrl={this.props.defaultIsUrl}
|
||||||
onHsUrlChanged={this.onHsUrlChanged}
|
onServerConfigChange={this.onServerConfigChange}
|
||||||
onIsUrlChanged={this.onIsUrlChanged}
|
|
||||||
delayTimeMs={1000}/>
|
delayTimeMs={1000}/>
|
||||||
<div className="mx_Login_error">
|
<div className="mx_Login_error">
|
||||||
{ this.state.errorText }
|
{ this.state.errorText }
|
||||||
</div>
|
</div>
|
||||||
<a className="mx_Login_create" onClick={this.props.onRegisterClick} href="#">
|
<a className="mx_Login_create" onClick={this.props.onRegisterClick} href="#">
|
||||||
Create a new account
|
{ _t('Create an account')}
|
||||||
</a>
|
</a>
|
||||||
{ loginAsGuestJsx }
|
{ loginAsGuestJsx }
|
||||||
{ returnToAppJsx }
|
{ returnToAppJsx }
|
||||||
|
|
|
@ -16,9 +16,10 @@ limitations under the License.
|
||||||
|
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
var React = require('react');
|
import React from 'react';
|
||||||
var sdk = require('../../../index');
|
import sdk from '../../../index';
|
||||||
var MatrixClientPeg = require('../../../MatrixClientPeg');
|
import MatrixClientPeg from '../../../MatrixClientPeg';
|
||||||
|
import { _t } from '../../../languageHandler';
|
||||||
|
|
||||||
module.exports = React.createClass({
|
module.exports = React.createClass({
|
||||||
displayName: 'PostRegistration',
|
displayName: 'PostRegistration',
|
||||||
|
@ -64,12 +65,12 @@ module.exports = React.createClass({
|
||||||
<div className="mx_Login_box">
|
<div className="mx_Login_box">
|
||||||
<LoginHeader />
|
<LoginHeader />
|
||||||
<div className="mx_Login_profile">
|
<div className="mx_Login_profile">
|
||||||
Set a display name:
|
{ _t('Set a display name:') }
|
||||||
<ChangeDisplayName />
|
<ChangeDisplayName />
|
||||||
Upload an avatar:
|
{ _t('Upload an avatar:') }
|
||||||
<ChangeAvatar
|
<ChangeAvatar
|
||||||
initialAvatarUrl={this.state.avatarUrl} />
|
initialAvatarUrl={this.state.avatarUrl} />
|
||||||
<button onClick={this.props.onComplete}>Continue</button>
|
<button onClick={this.props.onComplete}>{ _t('Continue') }</button>
|
||||||
{this.state.errorString}
|
{this.state.errorString}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -27,6 +27,7 @@ import MatrixClientPeg from '../../../MatrixClientPeg';
|
||||||
import RegistrationForm from '../../views/login/RegistrationForm';
|
import RegistrationForm from '../../views/login/RegistrationForm';
|
||||||
import CaptchaForm from '../../views/login/CaptchaForm';
|
import CaptchaForm from '../../views/login/CaptchaForm';
|
||||||
import RtsClient from '../../../RtsClient';
|
import RtsClient from '../../../RtsClient';
|
||||||
|
import { _t } from '../../../languageHandler';
|
||||||
|
|
||||||
const MIN_PASSWORD_LENGTH = 6;
|
const MIN_PASSWORD_LENGTH = 6;
|
||||||
|
|
||||||
|
@ -123,18 +124,17 @@ module.exports = React.createClass({
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
onHsUrlChanged: function(newHsUrl) {
|
onServerConfigChange: function(config) {
|
||||||
this.setState({
|
let newState = {};
|
||||||
hsUrl: newHsUrl,
|
if (config.hsUrl !== undefined) {
|
||||||
});
|
newState.hsUrl = config.hsUrl;
|
||||||
|
}
|
||||||
|
if (config.isUrl !== undefined) {
|
||||||
|
newState.isUrl = config.isUrl;
|
||||||
|
}
|
||||||
|
this.setState(newState, function() {
|
||||||
this._replaceClient();
|
this._replaceClient();
|
||||||
},
|
|
||||||
|
|
||||||
onIsUrlChanged: function(newIsUrl) {
|
|
||||||
this.setState({
|
|
||||||
isUrl: newIsUrl,
|
|
||||||
});
|
});
|
||||||
this._replaceClient();
|
|
||||||
},
|
},
|
||||||
|
|
||||||
_replaceClient: function() {
|
_replaceClient: function() {
|
||||||
|
@ -163,7 +163,7 @@ module.exports = React.createClass({
|
||||||
msisdn_available |= flow.stages.indexOf('m.login.msisdn') > -1;
|
msisdn_available |= flow.stages.indexOf('m.login.msisdn') > -1;
|
||||||
}
|
}
|
||||||
if (!msisdn_available) {
|
if (!msisdn_available) {
|
||||||
msg = "This server does not support authentication with a phone number";
|
msg = _t('This server does not support authentication with a phone number.');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
this.setState({
|
this.setState({
|
||||||
|
@ -261,29 +261,29 @@ module.exports = React.createClass({
|
||||||
var errMsg;
|
var errMsg;
|
||||||
switch (errCode) {
|
switch (errCode) {
|
||||||
case "RegistrationForm.ERR_PASSWORD_MISSING":
|
case "RegistrationForm.ERR_PASSWORD_MISSING":
|
||||||
errMsg = "Missing password.";
|
errMsg = _t('Missing password.');
|
||||||
break;
|
break;
|
||||||
case "RegistrationForm.ERR_PASSWORD_MISMATCH":
|
case "RegistrationForm.ERR_PASSWORD_MISMATCH":
|
||||||
errMsg = "Passwords don't match.";
|
errMsg = _t('Passwords don\'t match.');
|
||||||
break;
|
break;
|
||||||
case "RegistrationForm.ERR_PASSWORD_LENGTH":
|
case "RegistrationForm.ERR_PASSWORD_LENGTH":
|
||||||
errMsg = `Password too short (min ${MIN_PASSWORD_LENGTH}).`;
|
errMsg = _t('Password too short (min %(MIN_PASSWORD_LENGTH)s).', {MIN_PASSWORD_LENGTH: MIN_PASSWORD_LENGTH});
|
||||||
break;
|
break;
|
||||||
case "RegistrationForm.ERR_EMAIL_INVALID":
|
case "RegistrationForm.ERR_EMAIL_INVALID":
|
||||||
errMsg = "This doesn't look like a valid email address";
|
errMsg = _t('This doesn\'t look like a valid email address.');
|
||||||
break;
|
break;
|
||||||
case "RegistrationForm.ERR_PHONE_NUMBER_INVALID":
|
case "RegistrationForm.ERR_PHONE_NUMBER_INVALID":
|
||||||
errMsg = "This doesn't look like a valid phone number";
|
errMsg = _t('This doesn\'t look like a valid phone number.');
|
||||||
break;
|
break;
|
||||||
case "RegistrationForm.ERR_USERNAME_INVALID":
|
case "RegistrationForm.ERR_USERNAME_INVALID":
|
||||||
errMsg = "User names may only contain letters, numbers, dots, hyphens and underscores.";
|
errMsg = _t('User names may only contain letters, numbers, dots, hyphens and underscores.');
|
||||||
break;
|
break;
|
||||||
case "RegistrationForm.ERR_USERNAME_BLANK":
|
case "RegistrationForm.ERR_USERNAME_BLANK":
|
||||||
errMsg = "You need to enter a user name";
|
errMsg = _t('You need to enter a user name.');
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
console.error("Unknown error code: %s", errCode);
|
console.error("Unknown error code: %s", errCode);
|
||||||
errMsg = "An unknown error occurred.";
|
errMsg = _t('An unknown error occurred.');
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
this.setState({
|
this.setState({
|
||||||
|
@ -390,8 +390,7 @@ module.exports = React.createClass({
|
||||||
customIsUrl={this.props.customIsUrl}
|
customIsUrl={this.props.customIsUrl}
|
||||||
defaultHsUrl={this.props.defaultHsUrl}
|
defaultHsUrl={this.props.defaultHsUrl}
|
||||||
defaultIsUrl={this.props.defaultIsUrl}
|
defaultIsUrl={this.props.defaultIsUrl}
|
||||||
onHsUrlChanged={this.onHsUrlChanged}
|
onServerConfigChange={this.onServerConfigChange}
|
||||||
onIsUrlChanged={this.onIsUrlChanged}
|
|
||||||
delayTimeMs={1000}
|
delayTimeMs={1000}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
@ -402,7 +401,7 @@ module.exports = React.createClass({
|
||||||
if (this.props.onCancelClick) {
|
if (this.props.onCancelClick) {
|
||||||
returnToAppJsx = (
|
returnToAppJsx = (
|
||||||
<a className="mx_Login_create" onClick={this.props.onCancelClick} href="#">
|
<a className="mx_Login_create" onClick={this.props.onCancelClick} href="#">
|
||||||
Return to app
|
{_t('Return to app')}
|
||||||
</a>
|
</a>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -415,10 +414,10 @@ module.exports = React.createClass({
|
||||||
this.state.teamSelected.domain + "/icon.png" :
|
this.state.teamSelected.domain + "/icon.png" :
|
||||||
null}
|
null}
|
||||||
/>
|
/>
|
||||||
<h2>Create an account</h2>
|
<h2>{_t('Create an account')}</h2>
|
||||||
{registerBody}
|
{registerBody}
|
||||||
<a className="mx_Login_create" onClick={this.props.onLoginClick} href="#">
|
<a className="mx_Login_create" onClick={this.props.onLoginClick} href="#">
|
||||||
I already have an account
|
{_t('I already have an account')}
|
||||||
</a>
|
</a>
|
||||||
{returnToAppJsx}
|
{returnToAppJsx}
|
||||||
<LoginFooter />
|
<LoginFooter />
|
||||||
|
|
|
@ -59,7 +59,9 @@ module.exports = React.createClass({
|
||||||
ContentRepo.getHttpUriForMxc(
|
ContentRepo.getHttpUriForMxc(
|
||||||
MatrixClientPeg.get().getHomeserverUrl(),
|
MatrixClientPeg.get().getHomeserverUrl(),
|
||||||
props.oobData.avatarUrl,
|
props.oobData.avatarUrl,
|
||||||
props.width, props.height, props.resizeMethod
|
Math.floor(props.width * window.devicePixelRatio),
|
||||||
|
Math.floor(props.height * window.devicePixelRatio),
|
||||||
|
props.resizeMethod
|
||||||
), // highest priority
|
), // highest priority
|
||||||
this.getRoomAvatarUrl(props),
|
this.getRoomAvatarUrl(props),
|
||||||
this.getOneToOneAvatar(props),
|
this.getOneToOneAvatar(props),
|
||||||
|
@ -74,7 +76,9 @@ module.exports = React.createClass({
|
||||||
|
|
||||||
return props.room.getAvatarUrl(
|
return props.room.getAvatarUrl(
|
||||||
MatrixClientPeg.get().getHomeserverUrl(),
|
MatrixClientPeg.get().getHomeserverUrl(),
|
||||||
props.width, props.height, props.resizeMethod,
|
Math.floor(props.width * window.devicePixelRatio),
|
||||||
|
Math.floor(props.height * window.devicePixelRatio),
|
||||||
|
props.resizeMethod,
|
||||||
false
|
false
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
@ -103,13 +107,17 @@ module.exports = React.createClass({
|
||||||
}
|
}
|
||||||
return theOtherGuy.getAvatarUrl(
|
return theOtherGuy.getAvatarUrl(
|
||||||
MatrixClientPeg.get().getHomeserverUrl(),
|
MatrixClientPeg.get().getHomeserverUrl(),
|
||||||
props.width, props.height, props.resizeMethod,
|
Math.floor(props.width * window.devicePixelRatio),
|
||||||
|
Math.floor(props.height * window.devicePixelRatio),
|
||||||
|
props.resizeMethod,
|
||||||
false
|
false
|
||||||
);
|
);
|
||||||
} else if (userIds.length == 1) {
|
} else if (userIds.length == 1) {
|
||||||
return mlist[userIds[0]].getAvatarUrl(
|
return mlist[userIds[0]].getAvatarUrl(
|
||||||
MatrixClientPeg.get().getHomeserverUrl(),
|
MatrixClientPeg.get().getHomeserverUrl(),
|
||||||
props.width, props.height, props.resizeMethod,
|
Math.floor(props.width * window.devicePixelRatio),
|
||||||
|
Math.floor(props.height * window.devicePixelRatio),
|
||||||
|
props.resizeMethod,
|
||||||
false
|
false
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -16,8 +16,8 @@ limitations under the License.
|
||||||
|
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
var React = require('react');
|
import React from 'react';
|
||||||
|
import { _t } from '../../../languageHandler';
|
||||||
module.exports = React.createClass({
|
module.exports = React.createClass({
|
||||||
displayName: 'CreateRoomButton',
|
displayName: 'CreateRoomButton',
|
||||||
propTypes: {
|
propTypes: {
|
||||||
|
@ -36,7 +36,7 @@ module.exports = React.createClass({
|
||||||
|
|
||||||
render: function() {
|
render: function() {
|
||||||
return (
|
return (
|
||||||
<button className="mx_CreateRoomButton" onClick={this.onClick}>Create Room</button>
|
<button className="mx_CreateRoomButton" onClick={this.onClick}>{_t("Create Room")}</button>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
|
@ -17,6 +17,7 @@ limitations under the License.
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import sdk from '../../../index';
|
import sdk from '../../../index';
|
||||||
import dis from '../../../dispatcher';
|
import dis from '../../../dispatcher';
|
||||||
|
import { _t } from '../../../languageHandler';
|
||||||
import MatrixClientPeg from '../../../MatrixClientPeg';
|
import MatrixClientPeg from '../../../MatrixClientPeg';
|
||||||
import DMRoomMap from '../../../utils/DMRoomMap';
|
import DMRoomMap from '../../../utils/DMRoomMap';
|
||||||
import AccessibleButton from '../elements/AccessibleButton';
|
import AccessibleButton from '../elements/AccessibleButton';
|
||||||
|
@ -86,7 +87,7 @@ export default class ChatCreateOrReuseDialog extends React.Component {
|
||||||
<div className="mx_RoomTile_avatar">
|
<div className="mx_RoomTile_avatar">
|
||||||
<img src="img/create-big.svg" width="26" height="26" />
|
<img src="img/create-big.svg" width="26" height="26" />
|
||||||
</div>
|
</div>
|
||||||
<div className={labelClasses}><i>Start new chat</i></div>
|
<div className={labelClasses}><i>{_t("Start new chat")}</i></div>
|
||||||
</AccessibleButton>;
|
</AccessibleButton>;
|
||||||
|
|
||||||
const BaseDialog = sdk.getComponent('views.dialogs.BaseDialog');
|
const BaseDialog = sdk.getComponent('views.dialogs.BaseDialog');
|
||||||
|
|
|
@ -16,6 +16,7 @@ limitations under the License.
|
||||||
|
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
|
import { _t } from '../../../languageHandler';
|
||||||
import sdk from '../../../index';
|
import sdk from '../../../index';
|
||||||
import { getAddressType, inviteMultipleToRoom } from '../../../Invite';
|
import { getAddressType, inviteMultipleToRoom } from '../../../Invite';
|
||||||
import createRoom from '../../../createRoom';
|
import createRoom from '../../../createRoom';
|
||||||
|
@ -26,14 +27,13 @@ import dis from '../../../dispatcher';
|
||||||
import Modal from '../../../Modal';
|
import Modal from '../../../Modal';
|
||||||
import AccessibleButton from '../elements/AccessibleButton';
|
import AccessibleButton from '../elements/AccessibleButton';
|
||||||
import q from 'q';
|
import q from 'q';
|
||||||
import Fuse from 'fuse.js';
|
|
||||||
|
|
||||||
const TRUNCATE_QUERY_LIST = 40;
|
const TRUNCATE_QUERY_LIST = 40;
|
||||||
|
|
||||||
module.exports = React.createClass({
|
module.exports = React.createClass({
|
||||||
displayName: "ChatInviteDialog",
|
displayName: "ChatInviteDialog",
|
||||||
propTypes: {
|
propTypes: {
|
||||||
title: React.PropTypes.string,
|
title: React.PropTypes.string.isRequired,
|
||||||
description: React.PropTypes.oneOfType([
|
description: React.PropTypes.oneOfType([
|
||||||
React.PropTypes.element,
|
React.PropTypes.element,
|
||||||
React.PropTypes.string,
|
React.PropTypes.string,
|
||||||
|
@ -48,11 +48,7 @@ module.exports = React.createClass({
|
||||||
|
|
||||||
getDefaultProps: function() {
|
getDefaultProps: function() {
|
||||||
return {
|
return {
|
||||||
title: "Start a chat",
|
|
||||||
description: "Who would you like to communicate with?",
|
|
||||||
value: "",
|
value: "",
|
||||||
placeholder: "Email, name or matrix ID",
|
|
||||||
button: "Start Chat",
|
|
||||||
focus: true
|
focus: true
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
@ -77,19 +73,6 @@ module.exports = React.createClass({
|
||||||
// Set the cursor at the end of the text input
|
// Set the cursor at the end of the text input
|
||||||
this.refs.textinput.value = this.props.value;
|
this.refs.textinput.value = this.props.value;
|
||||||
}
|
}
|
||||||
// Create a Fuse instance for fuzzy searching this._userList
|
|
||||||
this._fuse = new Fuse(
|
|
||||||
// Use an empty list at first that will later be populated
|
|
||||||
// (see this._updateUserList)
|
|
||||||
[],
|
|
||||||
{
|
|
||||||
shouldSort: true,
|
|
||||||
location: 0, // The index of the query in the test string
|
|
||||||
distance: 5, // The distance away from location the query can be
|
|
||||||
// 0.0 = exact match, 1.0 = match anything
|
|
||||||
threshold: 0.3,
|
|
||||||
}
|
|
||||||
);
|
|
||||||
this._updateUserList();
|
this._updateUserList();
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -178,7 +161,7 @@ module.exports = React.createClass({
|
||||||
},
|
},
|
||||||
|
|
||||||
onQueryChanged: function(ev) {
|
onQueryChanged: function(ev) {
|
||||||
const query = ev.target.value;
|
const query = ev.target.value.toLowerCase();
|
||||||
let queryList = [];
|
let queryList = [];
|
||||||
|
|
||||||
if (query.length < 2) {
|
if (query.length < 2) {
|
||||||
|
@ -191,24 +174,27 @@ module.exports = React.createClass({
|
||||||
this.queryChangedDebouncer = setTimeout(() => {
|
this.queryChangedDebouncer = setTimeout(() => {
|
||||||
// Only do search if there is something to search
|
// Only do search if there is something to search
|
||||||
if (query.length > 0 && query != '@') {
|
if (query.length > 0 && query != '@') {
|
||||||
// Weighted keys prefer to match userIds when first char is @
|
this._userList.forEach((user) => {
|
||||||
this._fuse.options.keys = [{
|
if (user.userId.toLowerCase().indexOf(query) === -1 &&
|
||||||
name: 'displayName',
|
user.displayName.toLowerCase().indexOf(query) === -1
|
||||||
weight: query[0] === '@' ? 0.1 : 0.9,
|
) {
|
||||||
},{
|
return;
|
||||||
name: 'userId',
|
}
|
||||||
weight: query[0] === '@' ? 0.9 : 0.1,
|
|
||||||
}];
|
|
||||||
queryList = this._fuse.search(query).map((user) => {
|
|
||||||
// Return objects, structure of which is defined
|
// Return objects, structure of which is defined
|
||||||
// by InviteAddressType
|
// by InviteAddressType
|
||||||
return {
|
queryList.push({
|
||||||
addressType: 'mx',
|
addressType: 'mx',
|
||||||
address: user.userId,
|
address: user.userId,
|
||||||
displayName: user.displayName,
|
displayName: user.displayName,
|
||||||
avatarMxc: user.avatarUrl,
|
avatarMxc: user.avatarUrl,
|
||||||
isKnown: true,
|
isKnown: true,
|
||||||
}
|
order: user.getLastActiveTs(),
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
queryList = queryList.sort((a,b) => {
|
||||||
|
return a.order < b.order;
|
||||||
});
|
});
|
||||||
|
|
||||||
// If the query is a valid address, add an entry for that
|
// If the query is a valid address, add an entry for that
|
||||||
|
@ -286,8 +272,8 @@ module.exports = React.createClass({
|
||||||
if (MatrixClientPeg.get().isGuest()) {
|
if (MatrixClientPeg.get().isGuest()) {
|
||||||
var NeedToRegisterDialog = sdk.getComponent("dialogs.NeedToRegisterDialog");
|
var NeedToRegisterDialog = sdk.getComponent("dialogs.NeedToRegisterDialog");
|
||||||
Modal.createDialog(NeedToRegisterDialog, {
|
Modal.createDialog(NeedToRegisterDialog, {
|
||||||
title: "Please Register",
|
title: _t("Please Register"),
|
||||||
description: "Guest users can't invite users. Please register."
|
description: _t("Guest users can't invite users. Please register."),
|
||||||
});
|
});
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -308,8 +294,8 @@ module.exports = React.createClass({
|
||||||
console.error(err.stack);
|
console.error(err.stack);
|
||||||
var ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
|
var ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
|
||||||
Modal.createDialog(ErrorDialog, {
|
Modal.createDialog(ErrorDialog, {
|
||||||
title: "Error",
|
title: _t("Failed to invite"),
|
||||||
description: "Failed to invite",
|
description: ((err && err.message) ? err.message : _t("Operation failed")),
|
||||||
});
|
});
|
||||||
return null;
|
return null;
|
||||||
})
|
})
|
||||||
|
@ -321,8 +307,8 @@ module.exports = React.createClass({
|
||||||
console.error(err.stack);
|
console.error(err.stack);
|
||||||
var ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
|
var ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
|
||||||
Modal.createDialog(ErrorDialog, {
|
Modal.createDialog(ErrorDialog, {
|
||||||
title: "Error",
|
title: _t("Failed to invite user"),
|
||||||
description: "Failed to invite user",
|
description: ((err && err.message) ? err.message : _t("Operation failed")),
|
||||||
});
|
});
|
||||||
return null;
|
return null;
|
||||||
})
|
})
|
||||||
|
@ -342,8 +328,8 @@ module.exports = React.createClass({
|
||||||
console.error(err.stack);
|
console.error(err.stack);
|
||||||
var ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
|
var ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
|
||||||
Modal.createDialog(ErrorDialog, {
|
Modal.createDialog(ErrorDialog, {
|
||||||
title: "Error",
|
title: _t("Failed to invite"),
|
||||||
description: "Failed to invite",
|
description: ((err && err.message) ? err.message : _t("Operation failed")),
|
||||||
});
|
});
|
||||||
return null;
|
return null;
|
||||||
})
|
})
|
||||||
|
@ -354,7 +340,7 @@ module.exports = React.createClass({
|
||||||
this.props.onFinished(true, addrTexts);
|
this.props.onFinished(true, addrTexts);
|
||||||
},
|
},
|
||||||
|
|
||||||
_updateUserList: new rate_limited_func(function() {
|
_updateUserList: function() {
|
||||||
// Get all the users
|
// Get all the users
|
||||||
this._userList = MatrixClientPeg.get().getUsers();
|
this._userList = MatrixClientPeg.get().getUsers();
|
||||||
// Remove current user
|
// Remove current user
|
||||||
|
@ -362,9 +348,7 @@ module.exports = React.createClass({
|
||||||
return u.userId === MatrixClientPeg.get().credentials.userId;
|
return u.userId === MatrixClientPeg.get().credentials.userId;
|
||||||
});
|
});
|
||||||
this._userList.splice(meIx, 1);
|
this._userList.splice(meIx, 1);
|
||||||
|
},
|
||||||
this._fuse.set(this._userList);
|
|
||||||
}, 500),
|
|
||||||
|
|
||||||
_isOnInviteList: function(uid) {
|
_isOnInviteList: function(uid) {
|
||||||
for (let i = 0; i < this.state.inviteList.length; i++) {
|
for (let i = 0; i < this.state.inviteList.length; i++) {
|
||||||
|
@ -401,7 +385,7 @@ module.exports = React.createClass({
|
||||||
if (errorList.length > 0) {
|
if (errorList.length > 0) {
|
||||||
var ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
|
var ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
|
||||||
Modal.createDialog(ErrorDialog, {
|
Modal.createDialog(ErrorDialog, {
|
||||||
title: "Failed to invite the following users to the " + room.name + " room:",
|
title: _t("Failed to invite the following users to the %(roomName)s room:", {roomName: room.name}),
|
||||||
description: errorList.join(", "),
|
description: errorList.join(", "),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -506,7 +490,7 @@ module.exports = React.createClass({
|
||||||
var error;
|
var error;
|
||||||
var addressSelector;
|
var addressSelector;
|
||||||
if (this.state.error) {
|
if (this.state.error) {
|
||||||
error = <div className="mx_ChatInviteDialog_error">You have entered an invalid contact. Try using their Matrix ID or email address.</div>;
|
error = <div className="mx_ChatInviteDialog_error">{_t("You have entered an invalid contact. Try using their Matrix ID or email address.")}</div>;
|
||||||
} else {
|
} else {
|
||||||
const addressSelectorHeader = <div className="mx_ChatInviteDialog_addressSelectHeader">
|
const addressSelectorHeader = <div className="mx_ChatInviteDialog_addressSelectHeader">
|
||||||
Searching known users
|
Searching known users
|
||||||
|
|
|
@ -17,6 +17,7 @@ limitations under the License.
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import sdk from '../../../index';
|
import sdk from '../../../index';
|
||||||
import classnames from 'classnames';
|
import classnames from 'classnames';
|
||||||
|
import { _t } from '../../../languageHandler';
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* A dialog for confirming a redaction.
|
* A dialog for confirming a redaction.
|
||||||
|
@ -42,7 +43,7 @@ export default React.createClass({
|
||||||
render: function() {
|
render: function() {
|
||||||
const BaseDialog = sdk.getComponent('views.dialogs.BaseDialog');
|
const BaseDialog = sdk.getComponent('views.dialogs.BaseDialog');
|
||||||
|
|
||||||
const title = "Confirm Redaction";
|
const title = _t("Confirm Removal");
|
||||||
|
|
||||||
const confirmButtonClass = classnames({
|
const confirmButtonClass = classnames({
|
||||||
'mx_Dialog_primary': true,
|
'mx_Dialog_primary': true,
|
||||||
|
@ -55,16 +56,16 @@ export default React.createClass({
|
||||||
title={title}
|
title={title}
|
||||||
>
|
>
|
||||||
<div className="mx_Dialog_content">
|
<div className="mx_Dialog_content">
|
||||||
Are you sure you wish to redact (delete) this event?
|
{_t("Are you sure you wish to remove (delete) this event? " +
|
||||||
Note that if you redact a room name or topic change, it could undo the change.
|
"Note that if you delete a room name or topic change, it could undo the change.")}
|
||||||
</div>
|
</div>
|
||||||
<div className="mx_Dialog_buttons">
|
<div className="mx_Dialog_buttons">
|
||||||
<button className={confirmButtonClass} onClick={this.onOk}>
|
<button className={confirmButtonClass} onClick={this.onOk}>
|
||||||
Redact
|
{_t("Remove")}
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
<button onClick={this.onCancel}>
|
<button onClick={this.onCancel}>
|
||||||
Cancel
|
{_t("Cancel")}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</BaseDialog>
|
</BaseDialog>
|
||||||
|
|
|
@ -16,6 +16,7 @@ limitations under the License.
|
||||||
|
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import sdk from '../../../index';
|
import sdk from '../../../index';
|
||||||
|
import { _t } from '../../../languageHandler';
|
||||||
import classnames from 'classnames';
|
import classnames from 'classnames';
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
@ -69,7 +70,7 @@ export default React.createClass({
|
||||||
const BaseDialog = sdk.getComponent('views.dialogs.BaseDialog');
|
const BaseDialog = sdk.getComponent('views.dialogs.BaseDialog');
|
||||||
const MemberAvatar = sdk.getComponent("views.avatars.MemberAvatar");
|
const MemberAvatar = sdk.getComponent("views.avatars.MemberAvatar");
|
||||||
|
|
||||||
const title = this.props.action + " this person?";
|
const title = _t("%(actionVerb)s this person?", { actionVerb: this.props.action});
|
||||||
const confirmButtonClass = classnames({
|
const confirmButtonClass = classnames({
|
||||||
'mx_Dialog_primary': true,
|
'mx_Dialog_primary': true,
|
||||||
'danger': this.props.danger,
|
'danger': this.props.danger,
|
||||||
|
@ -82,7 +83,7 @@ export default React.createClass({
|
||||||
<form onSubmit={this.onOk}>
|
<form onSubmit={this.onOk}>
|
||||||
<input className="mx_ConfirmUserActionDialog_reasonField"
|
<input className="mx_ConfirmUserActionDialog_reasonField"
|
||||||
ref={this._collectReasonField}
|
ref={this._collectReasonField}
|
||||||
placeholder="Reason"
|
placeholder={ _t("Reason") }
|
||||||
autoFocus={true}
|
autoFocus={true}
|
||||||
/>
|
/>
|
||||||
</form>
|
</form>
|
||||||
|
@ -111,7 +112,7 @@ export default React.createClass({
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
<button onClick={this.onCancel}>
|
<button onClick={this.onCancel}>
|
||||||
Cancel
|
{ _t("Cancel") }
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</BaseDialog>
|
</BaseDialog>
|
||||||
|
|
|
@ -20,6 +20,7 @@ import sdk from '../../../index';
|
||||||
import MatrixClientPeg from '../../../MatrixClientPeg';
|
import MatrixClientPeg from '../../../MatrixClientPeg';
|
||||||
import * as Lifecycle from '../../../Lifecycle';
|
import * as Lifecycle from '../../../Lifecycle';
|
||||||
import Velocity from 'velocity-vector';
|
import Velocity from 'velocity-vector';
|
||||||
|
import { _t } from '../../../languageHandler';
|
||||||
|
|
||||||
export default class DeactivateAccountDialog extends React.Component {
|
export default class DeactivateAccountDialog extends React.Component {
|
||||||
constructor(props, context) {
|
constructor(props, context) {
|
||||||
|
@ -56,10 +57,10 @@ export default class DeactivateAccountDialog extends React.Component {
|
||||||
Lifecycle.onLoggedOut();
|
Lifecycle.onLoggedOut();
|
||||||
this.props.onFinished(false);
|
this.props.onFinished(false);
|
||||||
}, (err) => {
|
}, (err) => {
|
||||||
let errStr = 'Unknown error';
|
let errStr = _t('Unknown error');
|
||||||
// https://matrix.org/jira/browse/SYN-744
|
// https://matrix.org/jira/browse/SYN-744
|
||||||
if (err.httpStatus == 401 || err.httpStatus == 403) {
|
if (err.httpStatus == 401 || err.httpStatus == 403) {
|
||||||
errStr = 'Incorrect password';
|
errStr = _t('Incorrect password');
|
||||||
Velocity(this._passwordField, "callout.shake", 300);
|
Velocity(this._passwordField, "callout.shake", 300);
|
||||||
}
|
}
|
||||||
this.setState({
|
this.setState({
|
||||||
|
@ -91,23 +92,23 @@ export default class DeactivateAccountDialog extends React.Component {
|
||||||
let cancelButton = null;
|
let cancelButton = null;
|
||||||
if (!this.state.busy) {
|
if (!this.state.busy) {
|
||||||
cancelButton = <button onClick={this._onCancel} autoFocus={true}>
|
cancelButton = <button onClick={this._onCancel} autoFocus={true}>
|
||||||
Cancel
|
{_t("Cancel")}
|
||||||
</button>;
|
</button>;
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="mx_DeactivateAccountDialog">
|
<div className="mx_DeactivateAccountDialog">
|
||||||
<div className="mx_Dialog_title danger">
|
<div className="mx_Dialog_title danger">
|
||||||
Deactivate Account
|
{_t("Deactivate Account")}
|
||||||
</div>
|
</div>
|
||||||
<div className="mx_Dialog_content">
|
<div className="mx_Dialog_content">
|
||||||
<p>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>This action is irreversible.</p>
|
<p>{_t("This action is irreversible.")}</p>
|
||||||
|
|
||||||
<p>To continue, please enter your password.</p>
|
<p>{_t("To continue, please enter your password.")}</p>
|
||||||
|
|
||||||
<p>Password:</p>
|
<p>{_t("Password")}:</p>
|
||||||
<input
|
<input
|
||||||
type="password"
|
type="password"
|
||||||
onChange={this._onPasswordFieldChange}
|
onChange={this._onPasswordFieldChange}
|
||||||
|
|
77
src/components/views/dialogs/DeviceVerifyDialog.js
Normal file
77
src/components/views/dialogs/DeviceVerifyDialog.js
Normal file
|
@ -0,0 +1,77 @@
|
||||||
|
/*
|
||||||
|
Copyright 2016 OpenMarket Ltd
|
||||||
|
Copyright 2017 Vector Creations 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 MatrixClientPeg from '../../../MatrixClientPeg';
|
||||||
|
import sdk from '../../../index';
|
||||||
|
import * as FormattingUtils from '../../../utils/FormattingUtils';
|
||||||
|
import { _t } from '../../../languageHandler';
|
||||||
|
|
||||||
|
export default function DeviceVerifyDialog(props) {
|
||||||
|
const QuestionDialog = sdk.getComponent("dialogs.QuestionDialog");
|
||||||
|
|
||||||
|
const key = FormattingUtils.formatCryptoKey(props.device.getFingerprint());
|
||||||
|
const body = (
|
||||||
|
<div>
|
||||||
|
<p>
|
||||||
|
{_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:")}
|
||||||
|
</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>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
<p>
|
||||||
|
{_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.")}
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
{_t("In future this verification process will be more sophisticated.")}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
|
||||||
|
function onFinished(confirm) {
|
||||||
|
if (confirm) {
|
||||||
|
MatrixClientPeg.get().setDeviceVerified(
|
||||||
|
props.userId, props.device.deviceId, true,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
props.onFinished(confirm);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<QuestionDialog
|
||||||
|
title={_t("Verify device")}
|
||||||
|
description={body}
|
||||||
|
button={_t("I verify that the keys match")}
|
||||||
|
onFinished={onFinished}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
DeviceVerifyDialog.propTypes = {
|
||||||
|
userId: React.PropTypes.string.isRequired,
|
||||||
|
device: React.PropTypes.object.isRequired,
|
||||||
|
onFinished: React.PropTypes.func.isRequired,
|
||||||
|
};
|
|
@ -27,6 +27,7 @@ limitations under the License.
|
||||||
|
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import sdk from '../../../index';
|
import sdk from '../../../index';
|
||||||
|
import { _t } from '../../../languageHandler';
|
||||||
|
|
||||||
export default React.createClass({
|
export default React.createClass({
|
||||||
displayName: 'ErrorDialog',
|
displayName: 'ErrorDialog',
|
||||||
|
@ -43,24 +44,30 @@ export default React.createClass({
|
||||||
|
|
||||||
getDefaultProps: function() {
|
getDefaultProps: function() {
|
||||||
return {
|
return {
|
||||||
title: "Error",
|
|
||||||
description: "An error has occurred.",
|
|
||||||
button: "OK",
|
|
||||||
focus: true,
|
focus: true,
|
||||||
|
title: null,
|
||||||
|
description: null,
|
||||||
|
button: null,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|
||||||
|
componentDidMount: function() {
|
||||||
|
if (this.props.focus) {
|
||||||
|
this.refs.button.focus();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
render: function() {
|
render: function() {
|
||||||
const BaseDialog = sdk.getComponent('views.dialogs.BaseDialog');
|
const BaseDialog = sdk.getComponent('views.dialogs.BaseDialog');
|
||||||
return (
|
return (
|
||||||
<BaseDialog className="mx_ErrorDialog" onFinished={this.props.onFinished}
|
<BaseDialog className="mx_ErrorDialog" onFinished={this.props.onFinished}
|
||||||
title={this.props.title}>
|
title={this.props.title || _t('Error')}>
|
||||||
<div className="mx_Dialog_content">
|
<div className="mx_Dialog_content">
|
||||||
{this.props.description}
|
{this.props.description || _t('An error has occurred.')}
|
||||||
</div>
|
</div>
|
||||||
<div className="mx_Dialog_buttons">
|
<div className="mx_Dialog_buttons">
|
||||||
<button className="mx_Dialog_primary" onClick={this.props.onFinished} autoFocus={this.props.focus}>
|
<button ref="button" className="mx_Dialog_primary" onClick={this.props.onFinished}>
|
||||||
{this.props.button}
|
{this.props.button || _t('OK')}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</BaseDialog>
|
</BaseDialog>
|
||||||
|
|
|
@ -20,6 +20,7 @@ import Matrix from 'matrix-js-sdk';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
|
||||||
import sdk from '../../../index';
|
import sdk from '../../../index';
|
||||||
|
import { _t } from '../../../languageHandler';
|
||||||
|
|
||||||
import AccessibleButton from '../elements/AccessibleButton';
|
import AccessibleButton from '../elements/AccessibleButton';
|
||||||
|
|
||||||
|
@ -46,12 +47,6 @@ export default React.createClass({
|
||||||
title: React.PropTypes.string,
|
title: React.PropTypes.string,
|
||||||
},
|
},
|
||||||
|
|
||||||
getDefaultProps: function() {
|
|
||||||
return {
|
|
||||||
title: "Authentication",
|
|
||||||
};
|
|
||||||
},
|
|
||||||
|
|
||||||
getInitialState: function() {
|
getInitialState: function() {
|
||||||
return {
|
return {
|
||||||
authError: null,
|
authError: null,
|
||||||
|
@ -105,7 +100,7 @@ export default React.createClass({
|
||||||
return (
|
return (
|
||||||
<BaseDialog className="mx_InteractiveAuthDialog"
|
<BaseDialog className="mx_InteractiveAuthDialog"
|
||||||
onFinished={this.props.onFinished}
|
onFinished={this.props.onFinished}
|
||||||
title={this.state.authError ? 'Error' : this.props.title}
|
title={this.state.authError ? 'Error' : (this.props.title || _t('Authentication'))}
|
||||||
>
|
>
|
||||||
{content}
|
{content}
|
||||||
</BaseDialog>
|
</BaseDialog>
|
||||||
|
|
|
@ -26,6 +26,7 @@ limitations under the License.
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import dis from '../../../dispatcher';
|
import dis from '../../../dispatcher';
|
||||||
import sdk from '../../../index';
|
import sdk from '../../../index';
|
||||||
|
import { _t } from '../../../languageHandler';
|
||||||
|
|
||||||
module.exports = React.createClass({
|
module.exports = React.createClass({
|
||||||
displayName: 'NeedToRegisterDialog',
|
displayName: 'NeedToRegisterDialog',
|
||||||
|
@ -38,13 +39,6 @@ module.exports = React.createClass({
|
||||||
onFinished: React.PropTypes.func.isRequired,
|
onFinished: React.PropTypes.func.isRequired,
|
||||||
},
|
},
|
||||||
|
|
||||||
getDefaultProps: function() {
|
|
||||||
return {
|
|
||||||
title: "Registration required",
|
|
||||||
description: "A registered account is required for this action",
|
|
||||||
};
|
|
||||||
},
|
|
||||||
|
|
||||||
onRegisterClicked: function() {
|
onRegisterClicked: function() {
|
||||||
dis.dispatch({
|
dis.dispatch({
|
||||||
action: "start_upgrade_registration",
|
action: "start_upgrade_registration",
|
||||||
|
@ -59,17 +53,17 @@ module.exports = React.createClass({
|
||||||
return (
|
return (
|
||||||
<BaseDialog className="mx_NeedToRegisterDialog"
|
<BaseDialog className="mx_NeedToRegisterDialog"
|
||||||
onFinished={this.props.onFinished}
|
onFinished={this.props.onFinished}
|
||||||
title={this.props.title}
|
title={this.props.title || _t('Registration required')}
|
||||||
>
|
>
|
||||||
<div className="mx_Dialog_content">
|
<div className="mx_Dialog_content">
|
||||||
{this.props.description}
|
{this.props.description || _t('A registered account is required for this action')}
|
||||||
</div>
|
</div>
|
||||||
<div className="mx_Dialog_buttons">
|
<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}>
|
||||||
Cancel
|
{_t("Cancel")}
|
||||||
</button>
|
</button>
|
||||||
<button onClick={this.onRegisterClicked}>
|
<button onClick={this.onRegisterClicked}>
|
||||||
Register
|
{_t("Register")}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</BaseDialog>
|
</BaseDialog>
|
||||||
|
|
|
@ -16,6 +16,7 @@ limitations under the License.
|
||||||
|
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import sdk from '../../../index';
|
import sdk from '../../../index';
|
||||||
|
import { _t } from '../../../languageHandler';
|
||||||
|
|
||||||
export default React.createClass({
|
export default React.createClass({
|
||||||
displayName: 'QuestionDialog',
|
displayName: 'QuestionDialog',
|
||||||
|
@ -33,7 +34,6 @@ export default React.createClass({
|
||||||
title: "",
|
title: "",
|
||||||
description: "",
|
description: "",
|
||||||
extraButtons: null,
|
extraButtons: null,
|
||||||
button: "OK",
|
|
||||||
focus: true,
|
focus: true,
|
||||||
hasCancelButton: true,
|
hasCancelButton: true,
|
||||||
};
|
};
|
||||||
|
@ -51,7 +51,7 @@ export default React.createClass({
|
||||||
const BaseDialog = sdk.getComponent('views.dialogs.BaseDialog');
|
const BaseDialog = sdk.getComponent('views.dialogs.BaseDialog');
|
||||||
const cancelButton = this.props.hasCancelButton ? (
|
const cancelButton = this.props.hasCancelButton ? (
|
||||||
<button onClick={this.onCancel}>
|
<button onClick={this.onCancel}>
|
||||||
Cancel
|
{_t("Cancel")}
|
||||||
</button>
|
</button>
|
||||||
) : null;
|
) : null;
|
||||||
return (
|
return (
|
||||||
|
@ -64,7 +64,7 @@ export default React.createClass({
|
||||||
</div>
|
</div>
|
||||||
<div className="mx_Dialog_buttons">
|
<div className="mx_Dialog_buttons">
|
||||||
<button className="mx_Dialog_primary" onClick={this.onOk} autoFocus={this.props.focus}>
|
<button className="mx_Dialog_primary" onClick={this.onOk} autoFocus={this.props.focus}>
|
||||||
{this.props.button}
|
{this.props.button || _t('OK')}
|
||||||
</button>
|
</button>
|
||||||
{this.props.extraButtons}
|
{this.props.extraButtons}
|
||||||
{cancelButton}
|
{cancelButton}
|
||||||
|
|
|
@ -18,6 +18,7 @@ import React from 'react';
|
||||||
import sdk from '../../../index';
|
import sdk from '../../../index';
|
||||||
import SdkConfig from '../../../SdkConfig';
|
import SdkConfig from '../../../SdkConfig';
|
||||||
import Modal from '../../../Modal';
|
import Modal from '../../../Modal';
|
||||||
|
import { _t } from '../../../languageHandler';
|
||||||
|
|
||||||
|
|
||||||
export default React.createClass({
|
export default React.createClass({
|
||||||
|
@ -51,21 +52,21 @@ export default React.createClass({
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<BaseDialog className="mx_ErrorDialog" onFinished={this.props.onFinished}
|
<BaseDialog className="mx_ErrorDialog" onFinished={this.props.onFinished}
|
||||||
title='Unable to restore session'>
|
title={_t('Unable to restore session')}>
|
||||||
<div className="mx_Dialog_content">
|
<div className="mx_Dialog_content">
|
||||||
<p>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
|
"you continue, you will need to log in again, and encrypted chat " +
|
||||||
history will be unreadable.</p>
|
"history will be unreadable.")}</p>
|
||||||
|
|
||||||
<p>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
|
"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>
|
||||||
<div className="mx_Dialog_buttons">
|
<div className="mx_Dialog_buttons">
|
||||||
<button className="mx_Dialog_primary" onClick={this._continueClicked}>
|
<button className="mx_Dialog_primary" onClick={this._continueClicked}>
|
||||||
Continue anyway
|
{_t("Continue anyway")}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</BaseDialog>
|
</BaseDialog>
|
||||||
|
|
|
@ -18,6 +18,8 @@ import React from 'react';
|
||||||
import sdk from '../../../index';
|
import sdk from '../../../index';
|
||||||
import MatrixClientPeg from '../../../MatrixClientPeg';
|
import MatrixClientPeg from '../../../MatrixClientPeg';
|
||||||
|
|
||||||
|
import { _t } from '../../../languageHandler';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Prompt the user to set a display name.
|
* Prompt the user to set a display name.
|
||||||
*
|
*
|
||||||
|
@ -64,11 +66,11 @@ export default React.createClass({
|
||||||
return (
|
return (
|
||||||
<BaseDialog className="mx_SetDisplayNameDialog"
|
<BaseDialog className="mx_SetDisplayNameDialog"
|
||||||
onFinished={this.props.onFinished}
|
onFinished={this.props.onFinished}
|
||||||
title="Set a Display Name"
|
title={_t("Set a Display Name")}
|
||||||
>
|
>
|
||||||
<div className="mx_Dialog_content">
|
<div className="mx_Dialog_content">
|
||||||
Your display name is how you'll appear to others when you speak in rooms.<br/>
|
{_t("Your display name is how you'll appear to others when you speak in rooms. " +
|
||||||
What would you like it to be?
|
"What would you like it to be?")}
|
||||||
</div>
|
</div>
|
||||||
<form onSubmit={this.onFormSubmit}>
|
<form onSubmit={this.onFormSubmit}>
|
||||||
<div className="mx_Dialog_content">
|
<div className="mx_Dialog_content">
|
||||||
|
|
|
@ -16,6 +16,7 @@ limitations under the License.
|
||||||
|
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import sdk from '../../../index';
|
import sdk from '../../../index';
|
||||||
|
import { _t } from '../../../languageHandler';
|
||||||
|
|
||||||
export default React.createClass({
|
export default React.createClass({
|
||||||
displayName: 'TextInputDialog',
|
displayName: 'TextInputDialog',
|
||||||
|
@ -36,7 +37,6 @@ export default React.createClass({
|
||||||
title: "",
|
title: "",
|
||||||
value: "",
|
value: "",
|
||||||
description: "",
|
description: "",
|
||||||
button: "OK",
|
|
||||||
focus: true,
|
focus: true,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
@ -73,7 +73,7 @@ export default React.createClass({
|
||||||
</div>
|
</div>
|
||||||
<div className="mx_Dialog_buttons">
|
<div className="mx_Dialog_buttons">
|
||||||
<button onClick={this.onCancel}>
|
<button onClick={this.onCancel}>
|
||||||
Cancel
|
{ _t("Cancel") }
|
||||||
</button>
|
</button>
|
||||||
<button className="mx_Dialog_primary" onClick={this.onOk}>
|
<button className="mx_Dialog_primary" onClick={this.onOk}>
|
||||||
{this.props.button}
|
{this.props.button}
|
||||||
|
|
|
@ -20,6 +20,7 @@ import dis from '../../../dispatcher';
|
||||||
import MatrixClientPeg from '../../../MatrixClientPeg';
|
import MatrixClientPeg from '../../../MatrixClientPeg';
|
||||||
import GeminiScrollbar from 'react-gemini-scrollbar';
|
import GeminiScrollbar from 'react-gemini-scrollbar';
|
||||||
import Resend from '../../../Resend';
|
import Resend from '../../../Resend';
|
||||||
|
import { _t } from '../../../languageHandler';
|
||||||
|
|
||||||
function DeviceListEntry(props) {
|
function DeviceListEntry(props) {
|
||||||
const {userId, device} = props;
|
const {userId, device} = props;
|
||||||
|
@ -120,17 +121,17 @@ export default React.createClass({
|
||||||
if (blacklistUnverified) {
|
if (blacklistUnverified) {
|
||||||
warning = (
|
warning = (
|
||||||
<h4>
|
<h4>
|
||||||
You are currently blacklisting unverified devices; to send
|
{_t("You are currently blacklisting unverified devices; to send " +
|
||||||
messages to these devices you must verify them.
|
"messages to these devices you must verify them.")}
|
||||||
</h4>
|
</h4>
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
warning = (
|
warning = (
|
||||||
<div>
|
<div>
|
||||||
<p>
|
<p>
|
||||||
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,
|
"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>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
@ -149,10 +150,10 @@ export default React.createClass({
|
||||||
>
|
>
|
||||||
<GeminiScrollbar autoshow={false} className="mx_Dialog_content">
|
<GeminiScrollbar autoshow={false} className="mx_Dialog_content">
|
||||||
<h4>
|
<h4>
|
||||||
This room contains devices that you haven't seen before.
|
{_t('"%(RoomName)s" contains devices that you haven\'t seen before.', {RoomName: this.props.room.name})}
|
||||||
</h4>
|
</h4>
|
||||||
{ warning }
|
{ warning }
|
||||||
Unknown devices:
|
{_t("Unknown devices")}:
|
||||||
|
|
||||||
<UnknownDeviceList devices={this.props.devices} />
|
<UnknownDeviceList devices={this.props.devices} />
|
||||||
</GeminiScrollbar>
|
</GeminiScrollbar>
|
||||||
|
|
|
@ -32,6 +32,8 @@ export default function AccessibleButton(props) {
|
||||||
};
|
};
|
||||||
restProps.tabIndex = restProps.tabIndex || "0";
|
restProps.tabIndex = restProps.tabIndex || "0";
|
||||||
restProps.role = "button";
|
restProps.role = "button";
|
||||||
|
restProps.className = (restProps.className ? restProps.className + " " : "") +
|
||||||
|
"mx_AccessibleButton";
|
||||||
return React.createElement(element, restProps, children);
|
return React.createElement(element, restProps, children);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
/*
|
/*
|
||||||
Copyright 2015, 2016 OpenMarket Ltd
|
Copyright 2015, 2016 OpenMarket Ltd
|
||||||
|
Copyright 2017 Vector Creations Ltd
|
||||||
|
|
||||||
Licensed under the Apache License, Version 2.0 (the "License");
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
you may not use this file except in compliance with the License.
|
you may not use this file except in compliance with the License.
|
||||||
|
@ -138,7 +139,7 @@ export default React.createClass({
|
||||||
onClick={this.onClick.bind(this, i)}
|
onClick={this.onClick.bind(this, i)}
|
||||||
onMouseEnter={this.onMouseEnter.bind(this, i)}
|
onMouseEnter={this.onMouseEnter.bind(this, i)}
|
||||||
onMouseLeave={this.onMouseLeave}
|
onMouseLeave={this.onMouseLeave}
|
||||||
key={this.props.addressList[i].userId}
|
key={this.props.addressList[i].addressType + "/" + this.props.addressList[i].address}
|
||||||
ref={(ref) => { this.addressListElement = ref; }}
|
ref={(ref) => { this.addressListElement = ref; }}
|
||||||
>
|
>
|
||||||
<AddressTile address={this.props.addressList[i]} justified={true} networkName="vector" networkUrl="img/search-icon-vector.svg" />
|
<AddressTile address={this.props.addressList[i]} justified={true} networkName="vector" networkUrl="img/search-icon-vector.svg" />
|
||||||
|
|
|
@ -16,12 +16,13 @@ limitations under the License.
|
||||||
|
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
var React = require('react');
|
import React from 'react';
|
||||||
var classNames = require('classnames');
|
import classNames from 'classnames';
|
||||||
var sdk = require("../../../index");
|
import sdk from "../../../index";
|
||||||
var Invite = require("../../../Invite");
|
import Invite from "../../../Invite";
|
||||||
var MatrixClientPeg = require("../../../MatrixClientPeg");
|
import MatrixClientPeg from "../../../MatrixClientPeg";
|
||||||
var Avatar = require('../../../Avatar');
|
import Avatar from '../../../Avatar';
|
||||||
|
import { _t } from '../../../languageHandler';
|
||||||
|
|
||||||
// React PropType definition for an object describing
|
// React PropType definition for an object describing
|
||||||
// an address that can be invited to a room (which
|
// an address that can be invited to a room (which
|
||||||
|
@ -142,7 +143,7 @@ export default React.createClass({
|
||||||
});
|
});
|
||||||
|
|
||||||
info = (
|
info = (
|
||||||
<div className={unknownClasses}>Unknown Address</div>
|
<div className={unknownClasses}>{_t("Unknown Address")}</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -18,6 +18,7 @@ import React from 'react';
|
||||||
import MatrixClientPeg from '../../../MatrixClientPeg';
|
import MatrixClientPeg from '../../../MatrixClientPeg';
|
||||||
import sdk from '../../../index';
|
import sdk from '../../../index';
|
||||||
import Modal from '../../../Modal';
|
import Modal from '../../../Modal';
|
||||||
|
import { _t } from '../../../languageHandler';
|
||||||
|
|
||||||
export default React.createClass({
|
export default React.createClass({
|
||||||
displayName: 'DeviceVerifyButtons',
|
displayName: 'DeviceVerifyButtons',
|
||||||
|
@ -50,42 +51,10 @@ export default React.createClass({
|
||||||
},
|
},
|
||||||
|
|
||||||
onVerifyClick: function() {
|
onVerifyClick: function() {
|
||||||
var QuestionDialog = sdk.getComponent("dialogs.QuestionDialog");
|
const DeviceVerifyDialog = sdk.getComponent('views.dialogs.DeviceVerifyDialog');
|
||||||
Modal.createDialog(QuestionDialog, {
|
Modal.createDialog(DeviceVerifyDialog, {
|
||||||
title: "Verify device",
|
userId: this.props.userId,
|
||||||
description: (
|
device: this.state.device,
|
||||||
<div>
|
|
||||||
<p>
|
|
||||||
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:
|
|
||||||
</p>
|
|
||||||
<div className="mx_UserSettings_cryptoSection">
|
|
||||||
<ul>
|
|
||||||
<li><label>Device name:</label> <span>{ this.state.device.getDisplayName() }</span></li>
|
|
||||||
<li><label>Device ID:</label> <span><code>{ this.state.device.deviceId}</code></span></li>
|
|
||||||
<li><label>Device key:</label> <span><code><b>{ this.state.device.getFingerprint() }</b></code></span></li>
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
<p>
|
|
||||||
If it matches, press the verify button below.
|
|
||||||
If it doesnt, then someone else is intercepting this device
|
|
||||||
and you probably want to press the blacklist button instead.
|
|
||||||
</p>
|
|
||||||
<p>
|
|
||||||
In future this verification process will be more sophisticated.
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
),
|
|
||||||
button: "I verify that the keys match",
|
|
||||||
onFinished: confirm=>{
|
|
||||||
if (confirm) {
|
|
||||||
MatrixClientPeg.get().setDeviceVerified(
|
|
||||||
this.props.userId, this.state.device.deviceId, true
|
|
||||||
);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -114,14 +83,14 @@ export default React.createClass({
|
||||||
blacklistButton = (
|
blacklistButton = (
|
||||||
<button className="mx_MemberDeviceInfo_textButton mx_MemberDeviceInfo_unblacklist"
|
<button className="mx_MemberDeviceInfo_textButton mx_MemberDeviceInfo_unblacklist"
|
||||||
onClick={this.onUnblacklistClick}>
|
onClick={this.onUnblacklistClick}>
|
||||||
Unblacklist
|
{_t("Unblacklist")}
|
||||||
</button>
|
</button>
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
blacklistButton = (
|
blacklistButton = (
|
||||||
<button className="mx_MemberDeviceInfo_textButton mx_MemberDeviceInfo_blacklist"
|
<button className="mx_MemberDeviceInfo_textButton mx_MemberDeviceInfo_blacklist"
|
||||||
onClick={this.onBlacklistClick}>
|
onClick={this.onBlacklistClick}>
|
||||||
Blacklist
|
{_t("Blacklist")}
|
||||||
</button>
|
</button>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -130,14 +99,14 @@ export default React.createClass({
|
||||||
verifyButton = (
|
verifyButton = (
|
||||||
<button className="mx_MemberDeviceInfo_textButton mx_MemberDeviceInfo_unverify"
|
<button className="mx_MemberDeviceInfo_textButton mx_MemberDeviceInfo_unverify"
|
||||||
onClick={this.onUnverifyClick}>
|
onClick={this.onUnverifyClick}>
|
||||||
Unverify
|
{_t("Unverify")}
|
||||||
</button>
|
</button>
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
verifyButton = (
|
verifyButton = (
|
||||||
<button className="mx_MemberDeviceInfo_textButton mx_MemberDeviceInfo_verify"
|
<button className="mx_MemberDeviceInfo_textButton mx_MemberDeviceInfo_verify"
|
||||||
onClick={this.onVerifyClick}>
|
onClick={this.onVerifyClick}>
|
||||||
Verify...
|
{_t("Verify...")}
|
||||||
</button>
|
</button>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -93,7 +93,7 @@ export default class DirectorySearchBox extends React.Component {
|
||||||
className="mx_DirectorySearchBox_input"
|
className="mx_DirectorySearchBox_input"
|
||||||
ref={this._collectInput}
|
ref={this._collectInput}
|
||||||
onChange={this._onChange} onKeyUp={this._onKeyUp}
|
onChange={this._onChange} onKeyUp={this._onKeyUp}
|
||||||
placeholder={this.props.placeholder}
|
placeholder={this.props.placeholder} autoFocus
|
||||||
/>
|
/>
|
||||||
{join_button}
|
{join_button}
|
||||||
<span className="mx_DirectorySearchBox_clear_wrapper">
|
<span className="mx_DirectorySearchBox_clear_wrapper">
|
||||||
|
|
|
@ -17,6 +17,7 @@ limitations under the License.
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import classnames from 'classnames';
|
import classnames from 'classnames';
|
||||||
import AccessibleButton from './AccessibleButton';
|
import AccessibleButton from './AccessibleButton';
|
||||||
|
import { _t } from '../../../languageHandler';
|
||||||
|
|
||||||
class MenuOption extends React.Component {
|
class MenuOption extends React.Component {
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
|
@ -114,8 +115,11 @@ export default class Dropdown extends React.Component {
|
||||||
}
|
}
|
||||||
|
|
||||||
componentWillReceiveProps(nextProps) {
|
componentWillReceiveProps(nextProps) {
|
||||||
|
if (!nextProps.children || nextProps.children.length === 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
this._reindexChildren(nextProps.children);
|
this._reindexChildren(nextProps.children);
|
||||||
const firstChild = React.Children.toArray(nextProps.children)[0];
|
const firstChild = nextProps.children[0];
|
||||||
this.setState({
|
this.setState({
|
||||||
highlightedOption: firstChild ? firstChild.key : null,
|
highlightedOption: firstChild ? firstChild.key : null,
|
||||||
});
|
});
|
||||||
|
@ -149,11 +153,13 @@ export default class Dropdown extends React.Component {
|
||||||
}
|
}
|
||||||
|
|
||||||
_onInputClick(ev) {
|
_onInputClick(ev) {
|
||||||
|
if (!this.state.expanded) {
|
||||||
this.setState({
|
this.setState({
|
||||||
expanded: !this.state.expanded,
|
expanded: true,
|
||||||
});
|
});
|
||||||
ev.preventDefault();
|
ev.preventDefault();
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
_onMenuOptionClick(dropdownKey) {
|
_onMenuOptionClick(dropdownKey) {
|
||||||
this.setState({
|
this.setState({
|
||||||
|
@ -248,13 +254,10 @@ export default class Dropdown extends React.Component {
|
||||||
</MenuOption>
|
</MenuOption>
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
if (options.length === 0) {
|
||||||
if (!this.state.searchQuery) {
|
return [<div key="0" className="mx_Dropdown_option">
|
||||||
options.push(
|
{_t("No results")}
|
||||||
<div key="_searchprompt" className="mx_Dropdown_searchPrompt">
|
</div>];
|
||||||
Type to search...
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
return options;
|
return options;
|
||||||
}
|
}
|
||||||
|
@ -267,16 +270,20 @@ export default class Dropdown extends React.Component {
|
||||||
|
|
||||||
let menu;
|
let menu;
|
||||||
if (this.state.expanded) {
|
if (this.state.expanded) {
|
||||||
|
if (this.props.searchEnabled) {
|
||||||
currentValue = <input type="text" className="mx_Dropdown_option"
|
currentValue = <input type="text" className="mx_Dropdown_option"
|
||||||
ref={this._collectInputTextBox} onKeyPress={this._onInputKeyPress}
|
ref={this._collectInputTextBox} onKeyPress={this._onInputKeyPress}
|
||||||
onKeyUp={this._onInputKeyUp}
|
onKeyUp={this._onInputKeyUp}
|
||||||
onChange={this._onInputChange}
|
onChange={this._onInputChange}
|
||||||
value={this.state.searchQuery}
|
value={this.state.searchQuery}
|
||||||
/>;
|
/>;
|
||||||
|
}
|
||||||
menu = <div className="mx_Dropdown_menu" style={menuStyle}>
|
menu = <div className="mx_Dropdown_menu" style={menuStyle}>
|
||||||
{this._getMenuOptions()}
|
{this._getMenuOptions()}
|
||||||
</div>;
|
</div>;
|
||||||
} else {
|
}
|
||||||
|
|
||||||
|
if (!currentValue) {
|
||||||
const selectedChild = this.props.getShortOption ?
|
const selectedChild = this.props.getShortOption ?
|
||||||
this.props.getShortOption(this.props.value) :
|
this.props.getShortOption(this.props.value) :
|
||||||
this.childrenByKey[this.props.value];
|
this.childrenByKey[this.props.value];
|
||||||
|
@ -313,6 +320,7 @@ Dropdown.propTypes = {
|
||||||
onOptionChange: React.PropTypes.func.isRequired,
|
onOptionChange: React.PropTypes.func.isRequired,
|
||||||
// Called when the value of the search field changes
|
// Called when the value of the search field changes
|
||||||
onSearchChange: React.PropTypes.func,
|
onSearchChange: React.PropTypes.func,
|
||||||
|
searchEnabled: React.PropTypes.bool,
|
||||||
// Function that, given the key of an option, returns
|
// Function that, given the key of an option, returns
|
||||||
// a node representing that option to be displayed in the
|
// a node representing that option to be displayed in the
|
||||||
// box itself as the currently-selected option (ie. as
|
// box itself as the currently-selected option (ie. as
|
||||||
|
|
121
src/components/views/elements/LanguageDropdown.js
Normal file
121
src/components/views/elements/LanguageDropdown.js
Normal file
|
@ -0,0 +1,121 @@
|
||||||
|
/*
|
||||||
|
Copyright 2017 Marcel Radzio (MTRNord)
|
||||||
|
Copyright 2017 Vector Creations 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 sdk from '../../../index';
|
||||||
|
import UserSettingsStore from '../../../UserSettingsStore';
|
||||||
|
import { _t } from '../../../languageHandler';
|
||||||
|
import * as languageHandler from '../../../languageHandler';
|
||||||
|
|
||||||
|
function languageMatchesSearchQuery(query, language) {
|
||||||
|
if (language.label.toUpperCase().indexOf(query.toUpperCase()) == 0) return true;
|
||||||
|
if (language.value.toUpperCase() == query.toUpperCase()) return true;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default class LanguageDropdown extends React.Component {
|
||||||
|
constructor(props) {
|
||||||
|
super(props);
|
||||||
|
this._onSearchChange = this._onSearchChange.bind(this);
|
||||||
|
|
||||||
|
this.state = {
|
||||||
|
searchQuery: '',
|
||||||
|
langs: null,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
componentWillMount() {
|
||||||
|
languageHandler.getAllLanguagesFromJson().then((langs) => {
|
||||||
|
langs.sort(function(a, b){
|
||||||
|
if(a.label < b.label) return -1;
|
||||||
|
if(a.label > b.label) return 1;
|
||||||
|
return 0;
|
||||||
|
});
|
||||||
|
this.setState({langs});
|
||||||
|
}).catch(() => {
|
||||||
|
this.setState({langs: ['en']});
|
||||||
|
}).done();
|
||||||
|
|
||||||
|
if (!this.props.value) {
|
||||||
|
// If no value is given, we start with the first
|
||||||
|
// country selected, but our parent component
|
||||||
|
// doesn't know this, therefore we do this.
|
||||||
|
const _localSettings = UserSettingsStore.getLocalSettings();
|
||||||
|
if (_localSettings.hasOwnProperty('language')) {
|
||||||
|
this.props.onOptionChange(_localSettings.language);
|
||||||
|
}else {
|
||||||
|
const language = languageHandler.normalizeLanguageKey(languageHandler.getLanguageFromBrowser());
|
||||||
|
this.props.onOptionChange(language);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_onSearchChange(search) {
|
||||||
|
this.setState({
|
||||||
|
searchQuery: search,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
if (this.state.langs === null) {
|
||||||
|
const Spinner = sdk.getComponent('elements.Spinner');
|
||||||
|
return <Spinner />;
|
||||||
|
}
|
||||||
|
|
||||||
|
const Dropdown = sdk.getComponent('elements.Dropdown');
|
||||||
|
|
||||||
|
let displayedLanguages;
|
||||||
|
if (this.state.searchQuery) {
|
||||||
|
displayedLanguages = this.state.langs.filter((lang) => {
|
||||||
|
return languageMatchesSearchQuery(this.state.searchQuery, lang);
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
displayedLanguages = this.state.langs;
|
||||||
|
}
|
||||||
|
|
||||||
|
const options = displayedLanguages.map((language) => {
|
||||||
|
return <div key={language.value}>
|
||||||
|
{language.label}
|
||||||
|
</div>;
|
||||||
|
});
|
||||||
|
|
||||||
|
// default value here too, otherwise we need to handle null / undefined
|
||||||
|
// values between mounting and the initial value propgating
|
||||||
|
let value = null;
|
||||||
|
const _localSettings = UserSettingsStore.getLocalSettings();
|
||||||
|
if (_localSettings.hasOwnProperty('language')) {
|
||||||
|
value = this.props.value || _localSettings.language;
|
||||||
|
} else {
|
||||||
|
const language = navigator.language || navigator.userLanguage;
|
||||||
|
value = this.props.value || language;
|
||||||
|
}
|
||||||
|
|
||||||
|
return <Dropdown className={this.props.className}
|
||||||
|
onOptionChange={this.props.onOptionChange} onSearchChange={this._onSearchChange}
|
||||||
|
searchEnabled={true} value={value}
|
||||||
|
>
|
||||||
|
{options}
|
||||||
|
</Dropdown>
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
LanguageDropdown.propTypes = {
|
||||||
|
className: React.PropTypes.string,
|
||||||
|
onOptionChange: React.PropTypes.func.isRequired,
|
||||||
|
value: React.PropTypes.string,
|
||||||
|
};
|
|
@ -15,6 +15,7 @@ limitations under the License.
|
||||||
*/
|
*/
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
const MemberAvatar = require('../avatars/MemberAvatar.js');
|
const MemberAvatar = require('../avatars/MemberAvatar.js');
|
||||||
|
import { _t } from '../../../languageHandler';
|
||||||
|
|
||||||
module.exports = React.createClass({
|
module.exports = React.createClass({
|
||||||
displayName: 'MemberEventListSummary',
|
displayName: 'MemberEventListSummary',
|
||||||
|
@ -203,28 +204,146 @@ module.exports = React.createClass({
|
||||||
* @param {boolean} plural whether there were multiple users undergoing the same
|
* @param {boolean} plural whether there were multiple users undergoing the same
|
||||||
* transition.
|
* transition.
|
||||||
* @param {number} repeats the number of times the transition was repeated in a row.
|
* @param {number} repeats the number of times the transition was repeated in a row.
|
||||||
* @returns {string} the written English equivalent of the transition.
|
* @returns {string} the written Human Readable equivalent of the transition.
|
||||||
*/
|
*/
|
||||||
_getDescriptionForTransition(t, plural, repeats) {
|
_getDescriptionForTransition(t, plural, repeats) {
|
||||||
const beConjugated = plural ? "were" : "was";
|
// The empty interpolations 'severalUsers' and 'oneUser'
|
||||||
const invitation = "their invitation" + (plural || (repeats > 1) ? "s" : "");
|
// are there only to show translators to non-English languages
|
||||||
|
// that the verb is conjugated to plural or singular Subject.
|
||||||
let res = null;
|
let res = null;
|
||||||
const map = {
|
switch(t) {
|
||||||
"joined": "joined",
|
case "joined":
|
||||||
"left": "left",
|
if (repeats > 1) {
|
||||||
"joined_and_left": "joined and left",
|
res = (plural)
|
||||||
"left_and_joined": "left and rejoined",
|
? _t("%(severalUsers)sjoined %(repeats)s times", { severalUsers: "", repeats: repeats })
|
||||||
"invite_reject": "rejected " + invitation,
|
: _t("%(oneUser)sjoined %(repeats)s times", { oneUser: "", repeats: repeats });
|
||||||
"invite_withdrawal": "had " + invitation + " withdrawn",
|
} else {
|
||||||
"invited": beConjugated + " invited",
|
res = (plural)
|
||||||
"banned": beConjugated + " banned",
|
? _t("%(severalUsers)sjoined", { severalUsers: "" })
|
||||||
"unbanned": beConjugated + " unbanned",
|
: _t("%(oneUser)sjoined", { oneUser: "" });
|
||||||
"kicked": beConjugated + " kicked",
|
}
|
||||||
};
|
|
||||||
|
|
||||||
if (Object.keys(map).includes(t)) {
|
break;
|
||||||
res = map[t] + (repeats > 1 ? " " + repeats + " times" : "" );
|
case "left":
|
||||||
|
if (repeats > 1) {
|
||||||
|
res = (plural)
|
||||||
|
? _t("%(severalUsers)sleft %(repeats)s times", { severalUsers: "", repeats: repeats })
|
||||||
|
: _t("%(oneUser)sleft %(repeats)s times", { oneUser: "", repeats: repeats });
|
||||||
|
} else {
|
||||||
|
res = (plural)
|
||||||
|
? _t("%(severalUsers)sleft", { severalUsers: "" })
|
||||||
|
: _t("%(oneUser)sleft", { oneUser: "" });
|
||||||
|
} break;
|
||||||
|
case "joined_and_left":
|
||||||
|
if (repeats > 1) {
|
||||||
|
res = (plural)
|
||||||
|
? _t("%(severalUsers)sjoined and left %(repeats)s times", { severalUsers: "", repeats: repeats })
|
||||||
|
: _t("%(oneUser)sjoined and left %(repeats)s times", { oneUser: "", repeats: repeats });
|
||||||
|
} else {
|
||||||
|
res = (plural)
|
||||||
|
? _t("%(severalUsers)sjoined and left", { severalUsers: "" })
|
||||||
|
: _t("%(oneUser)sjoined and left", { oneUser: "" });
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case "left_and_joined":
|
||||||
|
if (repeats > 1) {
|
||||||
|
res = (plural)
|
||||||
|
? _t("%(severalUsers)sleft and rejoined %(repeats)s times", { severalUsers: "", repeats: repeats })
|
||||||
|
: _t("%(oneUser)sleft and rejoined %(repeats)s times", { oneUser: "", repeats: repeats });
|
||||||
|
} else {
|
||||||
|
res = (plural)
|
||||||
|
? _t("%(severalUsers)sleft and rejoined", { severalUsers: "" })
|
||||||
|
: _t("%(oneUser)sleft and rejoined", { oneUser: "" });
|
||||||
|
} break;
|
||||||
|
break;
|
||||||
|
case "invite_reject":
|
||||||
|
if (repeats > 1) {
|
||||||
|
res = (plural)
|
||||||
|
? _t("%(severalUsers)srejected their invitations %(repeats)s times", { severalUsers: "", repeats: repeats })
|
||||||
|
: _t("%(oneUser)srejected their invitation %(repeats)s times", { oneUser: "", repeats: repeats });
|
||||||
|
} else {
|
||||||
|
res = (plural)
|
||||||
|
? _t("%(severalUsers)srejected their invitations", { severalUsers: "" })
|
||||||
|
: _t("%(oneUser)srejected their invitation", { oneUser: "" });
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case "invite_withdrawal":
|
||||||
|
if (repeats > 1) {
|
||||||
|
res = (plural)
|
||||||
|
? _t("%(severalUsers)shad their invitations withdrawn %(repeats)s times", { severalUsers: "", repeats: repeats })
|
||||||
|
: _t("%(oneUser)shad their invitation withdrawn %(repeats)s times", { oneUser: "", repeats: repeats });
|
||||||
|
} else {
|
||||||
|
res = (plural)
|
||||||
|
? _t("%(severalUsers)shad their invitations withdrawn", { severalUsers: "" })
|
||||||
|
: _t("%(oneUser)shad their invitation withdrawn", { oneUser: "" });
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case "invited":
|
||||||
|
if (repeats > 1) {
|
||||||
|
res = (plural)
|
||||||
|
? _t("were invited %(repeats)s times", { repeats: repeats })
|
||||||
|
: _t("was invited %(repeats)s times", { repeats: repeats });
|
||||||
|
} else {
|
||||||
|
res = (plural)
|
||||||
|
? _t("were invited")
|
||||||
|
: _t("was invited");
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case "banned":
|
||||||
|
if (repeats > 1) {
|
||||||
|
res = (plural)
|
||||||
|
? _t("were banned %(repeats)s times", { repeats: repeats })
|
||||||
|
: _t("was banned %(repeats)s times", { repeats: repeats });
|
||||||
|
} else {
|
||||||
|
res = (plural)
|
||||||
|
? _t("were banned")
|
||||||
|
: _t("was banned");
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case "unbanned":
|
||||||
|
if (repeats > 1) {
|
||||||
|
res = (plural)
|
||||||
|
? _t("were unbanned %(repeats)s times", { repeats: repeats })
|
||||||
|
: _t("was unbanned %(repeats)s times", { repeats: repeats });
|
||||||
|
} else {
|
||||||
|
res = (plural)
|
||||||
|
? _t("were unbanned")
|
||||||
|
: _t("was unbanned");
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case "kicked":
|
||||||
|
if (repeats > 1) {
|
||||||
|
res = (plural)
|
||||||
|
? _t("were kicked %(repeats)s times", { repeats: repeats })
|
||||||
|
: _t("was kicked %(repeats)s times", { repeats: repeats });
|
||||||
|
} else {
|
||||||
|
res = (plural)
|
||||||
|
? _t("were kicked")
|
||||||
|
: _t("was kicked");
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case "changed_name":
|
||||||
|
if (repeats > 1) {
|
||||||
|
res = (plural)
|
||||||
|
? _t("%(severalUsers)schanged their name %(repeats)s times", { severalUsers: "", repeats: repeats })
|
||||||
|
: _t("%(oneUser)schanged their name %(repeats)s times", { oneUser: "", repeats: repeats });
|
||||||
|
} else {
|
||||||
|
res = (plural)
|
||||||
|
? _t("%(severalUsers)schanged their name", { severalUsers: "" })
|
||||||
|
: _t("%(oneUser)schanged their name", { oneUser: "" });
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case "changed_avatar":
|
||||||
|
if (repeats > 1) {
|
||||||
|
res = (plural)
|
||||||
|
? _t("%(severalUsers)schanged their avatar %(repeats)s times", { severalUsers: "", repeats: repeats })
|
||||||
|
: _t("%(oneUser)schanged their avatar %(repeats)s times", { oneUser: "", repeats: repeats });
|
||||||
|
} else {
|
||||||
|
res = (plural)
|
||||||
|
? _t("%(severalUsers)schanged their avatar", { severalUsers: "" })
|
||||||
|
: _t("%(oneUser)schanged their avatar", { oneUser: "" });
|
||||||
|
}
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
return res;
|
return res;
|
||||||
|
@ -252,11 +371,12 @@ module.exports = React.createClass({
|
||||||
return items[0];
|
return items[0];
|
||||||
} else if (remaining) {
|
} else if (remaining) {
|
||||||
items = items.slice(0, itemLimit);
|
items = items.slice(0, itemLimit);
|
||||||
const other = " other" + (remaining > 1 ? "s" : "");
|
return (remaining > 1)
|
||||||
return items.join(', ') + ' and ' + remaining + other;
|
? _t("%(items)s and %(remaining)s others", { items: items.join(', '), remaining: remaining } )
|
||||||
|
: _t("%(items)s and one other", { items: items.join(', ') });
|
||||||
} else {
|
} else {
|
||||||
const lastItem = items.pop();
|
const lastItem = items.pop();
|
||||||
return items.join(', ') + ' and ' + lastItem;
|
return _t("%(items)s and %(lastItem)s", { items: items.join(', '), lastItem: lastItem });
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -267,7 +387,7 @@ module.exports = React.createClass({
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
return (
|
return (
|
||||||
<span className="mx_MemberEventListSummary_avatars">
|
<span className="mx_MemberEventListSummary_avatars" onClick={ this._toggleSummary }>
|
||||||
{avatars}
|
{avatars}
|
||||||
</span>
|
</span>
|
||||||
);
|
);
|
||||||
|
@ -289,7 +409,24 @@ module.exports = React.createClass({
|
||||||
switch (e.mxEvent.getContent().membership) {
|
switch (e.mxEvent.getContent().membership) {
|
||||||
case 'invite': return 'invited';
|
case 'invite': return 'invited';
|
||||||
case 'ban': return 'banned';
|
case 'ban': return 'banned';
|
||||||
case 'join': return 'joined';
|
case 'join':
|
||||||
|
if (e.mxEvent.getPrevContent().membership === 'join') {
|
||||||
|
if (e.mxEvent.getContent().displayname !==
|
||||||
|
e.mxEvent.getPrevContent().displayname)
|
||||||
|
{
|
||||||
|
return 'changed_name';
|
||||||
|
}
|
||||||
|
else if (e.mxEvent.getContent().avatar_url !==
|
||||||
|
e.mxEvent.getPrevContent().avatar_url)
|
||||||
|
{
|
||||||
|
return 'changed_avatar';
|
||||||
|
}
|
||||||
|
// console.log("MELS ignoring duplicate membership join event");
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return 'joined';
|
||||||
|
}
|
||||||
case 'leave':
|
case 'leave':
|
||||||
if (e.mxEvent.getSender() === e.mxEvent.getStateKey()) {
|
if (e.mxEvent.getSender() === e.mxEvent.getStateKey()) {
|
||||||
switch (e.mxEvent.getPrevContent().membership) {
|
switch (e.mxEvent.getPrevContent().membership) {
|
||||||
|
@ -350,6 +487,7 @@ module.exports = React.createClass({
|
||||||
|
|
||||||
render: function() {
|
render: function() {
|
||||||
const eventsToRender = this.props.events;
|
const eventsToRender = this.props.events;
|
||||||
|
const eventIds = eventsToRender.map(e => e.getId()).join(',');
|
||||||
const fewEvents = eventsToRender.length < this.props.threshold;
|
const fewEvents = eventsToRender.length < this.props.threshold;
|
||||||
const expanded = this.state.expanded || fewEvents;
|
const expanded = this.state.expanded || fewEvents;
|
||||||
|
|
||||||
|
@ -360,7 +498,7 @@ module.exports = React.createClass({
|
||||||
|
|
||||||
if (fewEvents) {
|
if (fewEvents) {
|
||||||
return (
|
return (
|
||||||
<div className="mx_MemberEventListSummary">
|
<div className="mx_MemberEventListSummary" data-scroll-tokens={eventIds}>
|
||||||
{expandedEvents}
|
{expandedEvents}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
@ -418,7 +556,7 @@ module.exports = React.createClass({
|
||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="mx_MemberEventListSummary">
|
<div className="mx_MemberEventListSummary" data-scroll-tokens={eventIds}>
|
||||||
{toggleButton}
|
{toggleButton}
|
||||||
{summaryContainer}
|
{summaryContainer}
|
||||||
{expanded ? <div className="mx_MemberEventListSummary_line"> </div> : null}
|
{expanded ? <div className="mx_MemberEventListSummary_line"> </div> : null}
|
||||||
|
|
|
@ -16,18 +16,12 @@ limitations under the License.
|
||||||
|
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
var React = require('react');
|
import React from 'react';
|
||||||
|
import * as Roles from '../../../Roles';
|
||||||
var roles = {
|
import { _t } from '../../../languageHandler';
|
||||||
0: 'User',
|
|
||||||
50: 'Moderator',
|
|
||||||
100: 'Admin',
|
|
||||||
};
|
|
||||||
|
|
||||||
|
var LEVEL_ROLE_MAP = {};
|
||||||
var reverseRoles = {};
|
var reverseRoles = {};
|
||||||
Object.keys(roles).forEach(function(key) {
|
|
||||||
reverseRoles[roles[key]] = key;
|
|
||||||
});
|
|
||||||
|
|
||||||
module.exports = React.createClass({
|
module.exports = React.createClass({
|
||||||
displayName: 'PowerSelector',
|
displayName: 'PowerSelector',
|
||||||
|
@ -49,10 +43,17 @@ module.exports = React.createClass({
|
||||||
|
|
||||||
getInitialState: function() {
|
getInitialState: function() {
|
||||||
return {
|
return {
|
||||||
custom: (roles[this.props.value] === undefined),
|
custom: (LEVEL_ROLE_MAP[this.props.value] === undefined),
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|
||||||
|
componentWillMount: function() {
|
||||||
|
LEVEL_ROLE_MAP = Roles.levelRoleMap();
|
||||||
|
Object.keys(LEVEL_ROLE_MAP).forEach(function(key) {
|
||||||
|
reverseRoles[LEVEL_ROLE_MAP[key]] = key;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
onSelectChange: function(event) {
|
onSelectChange: function(event) {
|
||||||
this.setState({ custom: event.target.value === "Custom" });
|
this.setState({ custom: event.target.value === "Custom" });
|
||||||
if (event.target.value !== "Custom") {
|
if (event.target.value !== "Custom") {
|
||||||
|
@ -99,22 +100,34 @@ module.exports = React.createClass({
|
||||||
selectValue = "Custom";
|
selectValue = "Custom";
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
selectValue = roles[this.props.value] || "Custom";
|
selectValue = LEVEL_ROLE_MAP[this.props.value] || "Custom";
|
||||||
}
|
}
|
||||||
var select;
|
var select;
|
||||||
if (this.props.disabled) {
|
if (this.props.disabled) {
|
||||||
select = <span>{ selectValue }</span>;
|
select = <span>{ selectValue }</span>;
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
|
// Each level must have a definition in LEVEL_ROLE_MAP
|
||||||
|
const levels = [0, 50, 100];
|
||||||
|
let options = levels.map((level) => {
|
||||||
|
return {
|
||||||
|
value: LEVEL_ROLE_MAP[level],
|
||||||
|
// Give a userDefault (users_default in the power event) of 0 but
|
||||||
|
// because level !== undefined, this should never be used.
|
||||||
|
text: Roles.textualPowerLevel(level, 0),
|
||||||
|
}
|
||||||
|
});
|
||||||
|
options.push({ value: "Custom", text: _t("Custom level") });
|
||||||
|
options = options.map((op) => {
|
||||||
|
return <option value={op.value} key={op.value}>{op.text}</option>;
|
||||||
|
});
|
||||||
|
|
||||||
select =
|
select =
|
||||||
<select ref="select"
|
<select ref="select"
|
||||||
value={ this.props.controlled ? selectValue : undefined }
|
value={ this.props.controlled ? selectValue : undefined }
|
||||||
defaultValue={ !this.props.controlled ? selectValue : undefined }
|
defaultValue={ !this.props.controlled ? selectValue : undefined }
|
||||||
onChange={ this.onSelectChange }>
|
onChange={ this.onSelectChange }>
|
||||||
<option value="User">User (0)</option>
|
{ options }
|
||||||
<option value="Moderator">Moderator (50)</option>
|
|
||||||
<option value="Admin">Admin (100)</option>
|
|
||||||
<option value="Custom">Custom level</option>
|
|
||||||
</select>;
|
</select>;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -16,7 +16,8 @@ limitations under the License.
|
||||||
|
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
var React = require('react');
|
import React from 'react';
|
||||||
|
import { _t } from '../../../languageHandler';
|
||||||
|
|
||||||
module.exports = React.createClass({
|
module.exports = React.createClass({
|
||||||
displayName: 'UserSelector',
|
displayName: 'UserSelector',
|
||||||
|
@ -59,9 +60,9 @@ module.exports = React.createClass({
|
||||||
return <li key={user_id}>{user_id} - <span onClick={function() {self.removeUser(user_id);}}>X</span></li>;
|
return <li key={user_id}>{user_id} - <span onClick={function() {self.removeUser(user_id);}}>X</span></li>;
|
||||||
})}
|
})}
|
||||||
</ul>
|
</ul>
|
||||||
<input type="text" ref="user_id_input" defaultValue="" className="mx_UserSelector_userIdInput" placeholder="ex. @bob:example.com"/>
|
<input type="text" ref="user_id_input" defaultValue="" className="mx_UserSelector_userIdInput" placeholder={_t("ex. @bob:example.com")}/>
|
||||||
<button onClick={this.onAddUserId} className="mx_UserSelector_AddUserId">
|
<button onClick={this.onAddUserId} className="mx_UserSelector_AddUserId">
|
||||||
Add User
|
{_t("Add User")}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
|
@ -16,7 +16,9 @@ limitations under the License.
|
||||||
|
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
var React = require('react');
|
import React from 'react';
|
||||||
|
import { _t } from '../../../languageHandler';
|
||||||
|
|
||||||
var DIV_ID = 'mx_recaptcha';
|
var DIV_ID = 'mx_recaptcha';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -117,7 +119,7 @@ module.exports = React.createClass({
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div ref="recaptchaContainer">
|
<div ref="recaptchaContainer">
|
||||||
This Home Server would like to make sure you are not a robot
|
{_t("This Home Server would like to make sure you are not a robot")}
|
||||||
<br/>
|
<br/>
|
||||||
<div id={DIV_ID}></div>
|
<div id={DIV_ID}></div>
|
||||||
{error}
|
{error}
|
||||||
|
|
|
@ -16,7 +16,8 @@ limitations under the License.
|
||||||
|
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
var React = require('react');
|
import React from 'react';
|
||||||
|
import { _t } from '../../../languageHandler';
|
||||||
|
|
||||||
module.exports = React.createClass({
|
module.exports = React.createClass({
|
||||||
displayName: 'CasLogin',
|
displayName: 'CasLogin',
|
||||||
|
@ -28,7 +29,7 @@ module.exports = React.createClass({
|
||||||
render: function() {
|
render: function() {
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<button onClick={this.props.onSubmit}>Sign in with CAS</button>
|
<button onClick={this.props.onSubmit}>{_t("Sign in with CAS")}</button>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -19,7 +19,6 @@ import React from 'react';
|
||||||
import sdk from '../../../index';
|
import sdk from '../../../index';
|
||||||
|
|
||||||
import { COUNTRIES } from '../../../phonenumber';
|
import { COUNTRIES } from '../../../phonenumber';
|
||||||
import { charactersToImageNode } from '../../../HtmlUtils';
|
|
||||||
|
|
||||||
const COUNTRIES_BY_ISO2 = new Object(null);
|
const COUNTRIES_BY_ISO2 = new Object(null);
|
||||||
for (const c of COUNTRIES) {
|
for (const c of COUNTRIES) {
|
||||||
|
@ -27,22 +26,27 @@ for (const c of COUNTRIES) {
|
||||||
}
|
}
|
||||||
|
|
||||||
function countryMatchesSearchQuery(query, country) {
|
function countryMatchesSearchQuery(query, country) {
|
||||||
|
// Remove '+' if present (when searching for a prefix)
|
||||||
|
if (query[0] === '+') {
|
||||||
|
query = query.slice(1);
|
||||||
|
}
|
||||||
|
|
||||||
if (country.name.toUpperCase().indexOf(query.toUpperCase()) == 0) return true;
|
if (country.name.toUpperCase().indexOf(query.toUpperCase()) == 0) return true;
|
||||||
if (country.iso2 == query.toUpperCase()) return true;
|
if (country.iso2 == query.toUpperCase()) return true;
|
||||||
if (country.prefix == query) return true;
|
if (country.prefix.indexOf(query) !== -1) return true;
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
const MAX_DISPLAYED_ROWS = 2;
|
|
||||||
|
|
||||||
export default class CountryDropdown extends React.Component {
|
export default class CountryDropdown extends React.Component {
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
super(props);
|
super(props);
|
||||||
this._onSearchChange = this._onSearchChange.bind(this);
|
this._onSearchChange = this._onSearchChange.bind(this);
|
||||||
|
this._onOptionChange = this._onOptionChange.bind(this);
|
||||||
|
this._getShortOption = this._getShortOption.bind(this);
|
||||||
|
|
||||||
this.state = {
|
this.state = {
|
||||||
searchQuery: '',
|
searchQuery: '',
|
||||||
}
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
componentWillMount() {
|
componentWillMount() {
|
||||||
|
@ -50,7 +54,7 @@ export default class CountryDropdown extends React.Component {
|
||||||
// If no value is given, we start with the first
|
// If no value is given, we start with the first
|
||||||
// country selected, but our parent component
|
// country selected, but our parent component
|
||||||
// doesn't know this, therefore we do this.
|
// doesn't know this, therefore we do this.
|
||||||
this.props.onOptionChange(COUNTRIES[0].iso2);
|
this.props.onOptionChange(COUNTRIES[0]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -60,14 +64,26 @@ export default class CountryDropdown extends React.Component {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_onOptionChange(iso2) {
|
||||||
|
this.props.onOptionChange(COUNTRIES_BY_ISO2[iso2]);
|
||||||
|
}
|
||||||
|
|
||||||
_flagImgForIso2(iso2) {
|
_flagImgForIso2(iso2) {
|
||||||
// Unicode Regional Indicator Symbol letter 'A'
|
return <img src={`flags/${iso2}.png`}/>;
|
||||||
const RIS_A = 0x1F1E6;
|
}
|
||||||
const ASCII_A = 65;
|
|
||||||
return charactersToImageNode(iso2,
|
_getShortOption(iso2) {
|
||||||
RIS_A + (iso2.charCodeAt(0) - ASCII_A),
|
if (!this.props.isSmall) {
|
||||||
RIS_A + (iso2.charCodeAt(1) - ASCII_A),
|
return undefined;
|
||||||
);
|
}
|
||||||
|
let countryPrefix;
|
||||||
|
if (this.props.showPrefix) {
|
||||||
|
countryPrefix = '+' + COUNTRIES_BY_ISO2[iso2].prefix;
|
||||||
|
}
|
||||||
|
return <span>
|
||||||
|
{ this._flagImgForIso2(iso2) }
|
||||||
|
{ countryPrefix }
|
||||||
|
</span>;
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
|
@ -93,14 +109,10 @@ export default class CountryDropdown extends React.Component {
|
||||||
displayedCountries = COUNTRIES;
|
displayedCountries = COUNTRIES;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (displayedCountries.length > MAX_DISPLAYED_ROWS) {
|
|
||||||
displayedCountries = displayedCountries.slice(0, MAX_DISPLAYED_ROWS);
|
|
||||||
}
|
|
||||||
|
|
||||||
const options = displayedCountries.map((country) => {
|
const options = displayedCountries.map((country) => {
|
||||||
return <div key={country.iso2}>
|
return <div key={country.iso2}>
|
||||||
{this._flagImgForIso2(country.iso2)}
|
{this._flagImgForIso2(country.iso2)}
|
||||||
{country.name}
|
{country.name} <span>(+{country.prefix})</span>
|
||||||
</div>;
|
</div>;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -108,18 +120,21 @@ export default class CountryDropdown extends React.Component {
|
||||||
// values between mounting and the initial value propgating
|
// values between mounting and the initial value propgating
|
||||||
const value = this.props.value || COUNTRIES[0].iso2;
|
const value = this.props.value || COUNTRIES[0].iso2;
|
||||||
|
|
||||||
return <Dropdown className={this.props.className}
|
return <Dropdown className={this.props.className + " left_aligned"}
|
||||||
onOptionChange={this.props.onOptionChange} onSearchChange={this._onSearchChange}
|
onOptionChange={this._onOptionChange} onSearchChange={this._onSearchChange}
|
||||||
menuWidth={298} getShortOption={this._flagImgForIso2}
|
menuWidth={298} getShortOption={this._getShortOption}
|
||||||
value={value}
|
value={value} searchEnabled={true}
|
||||||
>
|
>
|
||||||
{options}
|
{options}
|
||||||
</Dropdown>
|
</Dropdown>;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
CountryDropdown.propTypes = {
|
CountryDropdown.propTypes = {
|
||||||
className: React.PropTypes.string,
|
className: React.PropTypes.string,
|
||||||
|
isSmall: React.PropTypes.bool,
|
||||||
|
// if isSmall, show +44 in the selected value
|
||||||
|
showPrefix: React.PropTypes.bool,
|
||||||
onOptionChange: React.PropTypes.func.isRequired,
|
onOptionChange: React.PropTypes.func.isRequired,
|
||||||
value: React.PropTypes.string,
|
value: React.PropTypes.string,
|
||||||
};
|
};
|
||||||
|
|
|
@ -14,7 +14,8 @@ See the License for the specific language governing permissions and
|
||||||
limitations under the License.
|
limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
var React = require("react");
|
import React from 'react';
|
||||||
|
import { _t } from '../../../languageHandler';
|
||||||
|
|
||||||
module.exports = React.createClass({
|
module.exports = React.createClass({
|
||||||
displayName: 'CustomServerDialog',
|
displayName: 'CustomServerDialog',
|
||||||
|
@ -23,24 +24,24 @@ module.exports = React.createClass({
|
||||||
return (
|
return (
|
||||||
<div className="mx_ErrorDialog">
|
<div className="mx_ErrorDialog">
|
||||||
<div className="mx_Dialog_title">
|
<div className="mx_Dialog_title">
|
||||||
Custom Server Options
|
{_t("Custom Server Options")}
|
||||||
</div>
|
</div>
|
||||||
<div className="mx_Dialog_content">
|
<div className="mx_Dialog_content">
|
||||||
<span>
|
<span>
|
||||||
You can use the custom server options to sign into other Matrix
|
{_t("You can use the custom server options to sign into other Matrix " +
|
||||||
servers by specifying a different Home server URL.
|
"servers by specifying a different Home server URL.")}
|
||||||
<br/>
|
<br/>
|
||||||
This allows you to use this app with an existing Matrix account on
|
{_t("This allows you to use this app with an existing Matrix account on " +
|
||||||
a different home server.
|
"a different home server.")}
|
||||||
<br/>
|
<br/>
|
||||||
<br/>
|
<br/>
|
||||||
You can also set a custom identity server but this will typically prevent
|
{_t("You can also set a custom identity server but this will typically prevent " +
|
||||||
interaction with users based on email address.
|
"interaction with users based on email address.")}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<div className="mx_Dialog_buttons">
|
<div className="mx_Dialog_buttons">
|
||||||
<button onClick={this.props.onFinished} autoFocus={true}>
|
<button onClick={this.props.onFinished} autoFocus={true}>
|
||||||
Dismiss
|
{_t("Dismiss")}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -20,6 +20,7 @@ import url from 'url';
|
||||||
import classnames from 'classnames';
|
import classnames from 'classnames';
|
||||||
|
|
||||||
import sdk from '../../../index';
|
import sdk from '../../../index';
|
||||||
|
import { _t } from '../../../languageHandler';
|
||||||
|
|
||||||
/* This file contains a collection of components which are used by the
|
/* This file contains a collection of components which are used by the
|
||||||
* InteractiveAuth to prompt the user to enter the information needed
|
* InteractiveAuth to prompt the user to enter the information needed
|
||||||
|
@ -128,8 +129,8 @@ export const PasswordAuthEntry = React.createClass({
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<p>To continue, please enter your password.</p>
|
<p>{_t("To continue, please enter your password.")}</p>
|
||||||
<p>Password:</p>
|
<p>{_t("Password:")}</p>
|
||||||
<form onSubmit={this._onSubmit}>
|
<form onSubmit={this._onSubmit}>
|
||||||
<input
|
<input
|
||||||
ref="passwordField"
|
ref="passwordField"
|
||||||
|
@ -255,8 +256,8 @@ export const EmailIdentityAuthEntry = React.createClass({
|
||||||
} else {
|
} else {
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<p>An email has been sent to <i>{this.props.inputs.emailAddress}</i></p>
|
<p>{_t("An email has been sent to")} <i>{this.props.inputs.emailAddress}</i></p>
|
||||||
<p>Please check your email to continue registration.</p>
|
<p>{_t("Please check your email to continue registration.")}</p>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -348,7 +349,7 @@ export const MsisdnAuthEntry = React.createClass({
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
this.setState({
|
this.setState({
|
||||||
errorText: "Token incorrect",
|
errorText: _t("Token incorrect"),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}).catch((e) => {
|
}).catch((e) => {
|
||||||
|
@ -369,8 +370,8 @@ export const MsisdnAuthEntry = React.createClass({
|
||||||
});
|
});
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<p>A text message has been sent to +<i>{this._msisdn}</i></p>
|
<p>{_t("A text message has been sent to")} +<i>{this._msisdn}</i></p>
|
||||||
<p>Please enter the code it contains:</p>
|
<p>{_t("Please enter the code it contains:")}</p>
|
||||||
<div className="mx_InteractiveAuthEntryComponents_msisdnWrapper">
|
<div className="mx_InteractiveAuthEntryComponents_msisdnWrapper">
|
||||||
<form onSubmit={this._onFormSubmit}>
|
<form onSubmit={this._onFormSubmit}>
|
||||||
<input type="text"
|
<input type="text"
|
||||||
|
@ -379,7 +380,7 @@ export const MsisdnAuthEntry = React.createClass({
|
||||||
onChange={this._onTokenChange}
|
onChange={this._onTokenChange}
|
||||||
/>
|
/>
|
||||||
<br />
|
<br />
|
||||||
<input type="submit" value="Submit"
|
<input type="submit" value={_t("Submit")}
|
||||||
className={submitClasses}
|
className={submitClasses}
|
||||||
disabled={!enableSubmit}
|
disabled={!enableSubmit}
|
||||||
/>
|
/>
|
||||||
|
|
|
@ -16,7 +16,7 @@ limitations under the License.
|
||||||
|
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
var React = require('react');
|
import React from 'react';
|
||||||
|
|
||||||
module.exports = React.createClass({
|
module.exports = React.createClass({
|
||||||
displayName: 'LoginFooter',
|
displayName: 'LoginFooter',
|
||||||
|
@ -24,7 +24,7 @@ module.exports = React.createClass({
|
||||||
render: function() {
|
render: function() {
|
||||||
return (
|
return (
|
||||||
<div className="mx_Login_links">
|
<div className="mx_Login_links">
|
||||||
<a href="https://matrix.org">powered by Matrix</a>
|
<a href="https://matrix.org">{_t("powered by Matrix")}</a>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -19,14 +19,198 @@ import React from 'react';
|
||||||
import ReactDOM from 'react-dom';
|
import ReactDOM from 'react-dom';
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
import sdk from '../../../index';
|
import sdk from '../../../index';
|
||||||
|
import { _t } from '../../../languageHandler';
|
||||||
import {field_input_incorrect} from '../../../UiEffects';
|
import {field_input_incorrect} from '../../../UiEffects';
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A pure UI component which displays a username/password form.
|
* A pure UI component which displays a username/password form.
|
||||||
*/
|
*/
|
||||||
module.exports = React.createClass({displayName: 'PasswordLogin',
|
class PasswordLogin extends React.Component {
|
||||||
propTypes: {
|
static defaultProps = {
|
||||||
|
onUsernameChanged: function() {},
|
||||||
|
onPasswordChanged: function() {},
|
||||||
|
onPhoneCountryChanged: function() {},
|
||||||
|
onPhoneNumberChanged: function() {},
|
||||||
|
initialUsername: "",
|
||||||
|
initialPhoneCountry: "",
|
||||||
|
initialPhoneNumber: "",
|
||||||
|
initialPassword: "",
|
||||||
|
loginIncorrect: false,
|
||||||
|
hsDomain: "",
|
||||||
|
}
|
||||||
|
|
||||||
|
constructor(props) {
|
||||||
|
super(props);
|
||||||
|
this.state = {
|
||||||
|
username: this.props.initialUsername,
|
||||||
|
password: this.props.initialPassword,
|
||||||
|
phoneCountry: this.props.initialPhoneCountry,
|
||||||
|
phoneNumber: this.props.initialPhoneNumber,
|
||||||
|
loginType: PasswordLogin.LOGIN_FIELD_MXID,
|
||||||
|
};
|
||||||
|
|
||||||
|
this.onSubmitForm = this.onSubmitForm.bind(this);
|
||||||
|
this.onUsernameChanged = this.onUsernameChanged.bind(this);
|
||||||
|
this.onLoginTypeChange = this.onLoginTypeChange.bind(this);
|
||||||
|
this.onPhoneCountryChanged = this.onPhoneCountryChanged.bind(this);
|
||||||
|
this.onPhoneNumberChanged = this.onPhoneNumberChanged.bind(this);
|
||||||
|
this.onPasswordChanged = this.onPasswordChanged.bind(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
componentWillMount() {
|
||||||
|
this._passwordField = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
componentWillReceiveProps(nextProps) {
|
||||||
|
if (!this.props.loginIncorrect && nextProps.loginIncorrect) {
|
||||||
|
field_input_incorrect(this._passwordField);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
onSubmitForm(ev) {
|
||||||
|
ev.preventDefault();
|
||||||
|
this.props.onSubmit(
|
||||||
|
this.state.username,
|
||||||
|
this.state.phoneCountry,
|
||||||
|
this.state.phoneNumber,
|
||||||
|
this.state.password,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
onUsernameChanged(ev) {
|
||||||
|
this.setState({username: ev.target.value});
|
||||||
|
this.props.onUsernameChanged(ev.target.value);
|
||||||
|
}
|
||||||
|
|
||||||
|
onLoginTypeChange(loginType) {
|
||||||
|
this.setState({
|
||||||
|
loginType: loginType,
|
||||||
|
username: "" // Reset because email and username use the same state
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
onPhoneCountryChanged(country) {
|
||||||
|
this.setState({
|
||||||
|
phoneCountry: country.iso2,
|
||||||
|
phonePrefix: country.prefix,
|
||||||
|
});
|
||||||
|
this.props.onPhoneCountryChanged(country.iso2);
|
||||||
|
}
|
||||||
|
|
||||||
|
onPhoneNumberChanged(ev) {
|
||||||
|
this.setState({phoneNumber: ev.target.value});
|
||||||
|
this.props.onPhoneNumberChanged(ev.target.value);
|
||||||
|
}
|
||||||
|
|
||||||
|
onPasswordChanged(ev) {
|
||||||
|
this.setState({password: ev.target.value});
|
||||||
|
this.props.onPasswordChanged(ev.target.value);
|
||||||
|
}
|
||||||
|
|
||||||
|
renderLoginField(loginType) {
|
||||||
|
switch(loginType) {
|
||||||
|
case PasswordLogin.LOGIN_FIELD_EMAIL:
|
||||||
|
return <input
|
||||||
|
className="mx_Login_field mx_Login_email"
|
||||||
|
key="email_input"
|
||||||
|
type="text"
|
||||||
|
name="username" // make it a little easier for browser's remember-password
|
||||||
|
onChange={this.onUsernameChanged}
|
||||||
|
placeholder="joe@example.com"
|
||||||
|
value={this.state.username}
|
||||||
|
autoFocus
|
||||||
|
/>;
|
||||||
|
case PasswordLogin.LOGIN_FIELD_MXID:
|
||||||
|
return <input
|
||||||
|
className="mx_Login_field mx_Login_username"
|
||||||
|
key="username_input"
|
||||||
|
type="text"
|
||||||
|
name="username" // make it a little easier for browser's remember-password
|
||||||
|
onChange={this.onUsernameChanged}
|
||||||
|
placeholder={_t('User name')}
|
||||||
|
value={this.state.username}
|
||||||
|
autoFocus
|
||||||
|
/>;
|
||||||
|
case PasswordLogin.LOGIN_FIELD_PHONE:
|
||||||
|
const CountryDropdown = sdk.getComponent('views.login.CountryDropdown');
|
||||||
|
return <div className="mx_Login_phoneSection">
|
||||||
|
<CountryDropdown
|
||||||
|
className="mx_Login_phoneCountry mx_Login_field_prefix"
|
||||||
|
ref="phone_country"
|
||||||
|
onOptionChange={this.onPhoneCountryChanged}
|
||||||
|
value={this.state.phoneCountry}
|
||||||
|
isSmall={true}
|
||||||
|
showPrefix={true}
|
||||||
|
/>
|
||||||
|
<input
|
||||||
|
className="mx_Login_phoneNumberField mx_Login_field mx_Login_field_has_prefix"
|
||||||
|
ref="phoneNumber"
|
||||||
|
key="phone_input"
|
||||||
|
type="text"
|
||||||
|
name="phoneNumber"
|
||||||
|
onChange={this.onPhoneNumberChanged}
|
||||||
|
placeholder={_t("Mobile phone number")}
|
||||||
|
value={this.state.phoneNumber}
|
||||||
|
autoFocus
|
||||||
|
/>
|
||||||
|
</div>;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
var forgotPasswordJsx;
|
||||||
|
|
||||||
|
if (this.props.onForgotPasswordClick) {
|
||||||
|
forgotPasswordJsx = (
|
||||||
|
<a className="mx_Login_forgot" onClick={this.props.onForgotPasswordClick} href="#">
|
||||||
|
{ _t('Forgot your password?') }
|
||||||
|
</a>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const pwFieldClass = classNames({
|
||||||
|
mx_Login_field: true,
|
||||||
|
error: this.props.loginIncorrect,
|
||||||
|
});
|
||||||
|
|
||||||
|
const Dropdown = sdk.getComponent('elements.Dropdown');
|
||||||
|
|
||||||
|
const loginField = this.renderLoginField(this.state.loginType);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<form onSubmit={this.onSubmitForm}>
|
||||||
|
<div className="mx_Login_type_container">
|
||||||
|
<label className="mx_Login_type_label">{ _t('Sign in with') }</label>
|
||||||
|
<Dropdown
|
||||||
|
className="mx_Login_type_dropdown"
|
||||||
|
value={this.state.loginType}
|
||||||
|
onOptionChange={this.onLoginTypeChange}>
|
||||||
|
<span key={PasswordLogin.LOGIN_FIELD_MXID}>{ _t('my Matrix ID') }</span>
|
||||||
|
<span key={PasswordLogin.LOGIN_FIELD_EMAIL}>{ _t('Email address') }</span>
|
||||||
|
<span key={PasswordLogin.LOGIN_FIELD_PHONE}>{ _t('Phone') }</span>
|
||||||
|
</Dropdown>
|
||||||
|
</div>
|
||||||
|
{loginField}
|
||||||
|
<input className={pwFieldClass} ref={(e) => {this._passwordField = e;}} type="password"
|
||||||
|
name="password"
|
||||||
|
value={this.state.password} onChange={this.onPasswordChanged}
|
||||||
|
placeholder={ _t('Password') } />
|
||||||
|
<br />
|
||||||
|
{forgotPasswordJsx}
|
||||||
|
<input className="mx_Login_submit" type="submit" value={ _t('Sign in') } />
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
PasswordLogin.LOGIN_FIELD_EMAIL = "login_field_email";
|
||||||
|
PasswordLogin.LOGIN_FIELD_MXID = "login_field_mxid";
|
||||||
|
PasswordLogin.LOGIN_FIELD_PHONE = "login_field_phone";
|
||||||
|
|
||||||
|
PasswordLogin.propTypes = {
|
||||||
onSubmit: React.PropTypes.func.isRequired, // fn(username, password)
|
onSubmit: React.PropTypes.func.isRequired, // fn(username, password)
|
||||||
onForgotPasswordClick: React.PropTypes.func, // fn()
|
onForgotPasswordClick: React.PropTypes.func, // fn()
|
||||||
initialUsername: React.PropTypes.string,
|
initialUsername: React.PropTypes.string,
|
||||||
|
@ -38,119 +222,6 @@ module.exports = React.createClass({displayName: 'PasswordLogin',
|
||||||
onPhoneNumberChanged: React.PropTypes.func,
|
onPhoneNumberChanged: React.PropTypes.func,
|
||||||
onPasswordChanged: React.PropTypes.func,
|
onPasswordChanged: React.PropTypes.func,
|
||||||
loginIncorrect: React.PropTypes.bool,
|
loginIncorrect: React.PropTypes.bool,
|
||||||
},
|
};
|
||||||
|
|
||||||
getDefaultProps: function() {
|
module.exports = PasswordLogin;
|
||||||
return {
|
|
||||||
onUsernameChanged: function() {},
|
|
||||||
onPasswordChanged: function() {},
|
|
||||||
onPhoneCountryChanged: function() {},
|
|
||||||
onPhoneNumberChanged: function() {},
|
|
||||||
initialUsername: "",
|
|
||||||
initialPhoneCountry: "",
|
|
||||||
initialPhoneNumber: "",
|
|
||||||
initialPassword: "",
|
|
||||||
loginIncorrect: false,
|
|
||||||
};
|
|
||||||
},
|
|
||||||
|
|
||||||
getInitialState: function() {
|
|
||||||
return {
|
|
||||||
username: this.props.initialUsername,
|
|
||||||
password: this.props.initialPassword,
|
|
||||||
phoneCountry: this.props.initialPhoneCountry,
|
|
||||||
phoneNumber: this.props.initialPhoneNumber,
|
|
||||||
};
|
|
||||||
},
|
|
||||||
|
|
||||||
componentWillMount: function() {
|
|
||||||
this._passwordField = null;
|
|
||||||
},
|
|
||||||
|
|
||||||
componentWillReceiveProps: function(nextProps) {
|
|
||||||
if (!this.props.loginIncorrect && nextProps.loginIncorrect) {
|
|
||||||
field_input_incorrect(this._passwordField);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
onSubmitForm: function(ev) {
|
|
||||||
ev.preventDefault();
|
|
||||||
this.props.onSubmit(
|
|
||||||
this.state.username,
|
|
||||||
this.state.phoneCountry,
|
|
||||||
this.state.phoneNumber,
|
|
||||||
this.state.password,
|
|
||||||
);
|
|
||||||
},
|
|
||||||
|
|
||||||
onUsernameChanged: function(ev) {
|
|
||||||
this.setState({username: ev.target.value});
|
|
||||||
this.props.onUsernameChanged(ev.target.value);
|
|
||||||
},
|
|
||||||
|
|
||||||
onPhoneCountryChanged: function(country) {
|
|
||||||
this.setState({phoneCountry: country});
|
|
||||||
this.props.onPhoneCountryChanged(country);
|
|
||||||
},
|
|
||||||
|
|
||||||
onPhoneNumberChanged: function(ev) {
|
|
||||||
this.setState({phoneNumber: ev.target.value});
|
|
||||||
this.props.onPhoneNumberChanged(ev.target.value);
|
|
||||||
},
|
|
||||||
|
|
||||||
onPasswordChanged: function(ev) {
|
|
||||||
this.setState({password: ev.target.value});
|
|
||||||
this.props.onPasswordChanged(ev.target.value);
|
|
||||||
},
|
|
||||||
|
|
||||||
render: function() {
|
|
||||||
var forgotPasswordJsx;
|
|
||||||
|
|
||||||
if (this.props.onForgotPasswordClick) {
|
|
||||||
forgotPasswordJsx = (
|
|
||||||
<a className="mx_Login_forgot" onClick={this.props.onForgotPasswordClick} href="#">
|
|
||||||
Forgot your password?
|
|
||||||
</a>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
const pwFieldClass = classNames({
|
|
||||||
mx_Login_field: true,
|
|
||||||
error: this.props.loginIncorrect,
|
|
||||||
});
|
|
||||||
|
|
||||||
const CountryDropdown = sdk.getComponent('views.login.CountryDropdown');
|
|
||||||
return (
|
|
||||||
<div>
|
|
||||||
<form onSubmit={this.onSubmitForm}>
|
|
||||||
<input className="mx_Login_field mx_Login_username" type="text"
|
|
||||||
name="username" // make it a little easier for browser's remember-password
|
|
||||||
value={this.state.username} onChange={this.onUsernameChanged}
|
|
||||||
placeholder="Email or user name" autoFocus />
|
|
||||||
or
|
|
||||||
<div className="mx_Login_phoneSection">
|
|
||||||
<CountryDropdown ref="phone_country" onOptionChange={this.onPhoneCountryChanged}
|
|
||||||
className="mx_Login_phoneCountry"
|
|
||||||
value={this.state.phoneCountry}
|
|
||||||
/>
|
|
||||||
<input type="text" ref="phoneNumber"
|
|
||||||
onChange={this.onPhoneNumberChanged}
|
|
||||||
placeholder="Mobile phone number"
|
|
||||||
className="mx_Login_phoneNumberField mx_Login_field"
|
|
||||||
value={this.state.phoneNumber}
|
|
||||||
name="phoneNumber"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<br />
|
|
||||||
<input className={pwFieldClass} ref={(e) => {this._passwordField = e;}} type="password"
|
|
||||||
name="password"
|
|
||||||
value={this.state.password} onChange={this.onPasswordChanged}
|
|
||||||
placeholder="Password" />
|
|
||||||
<br />
|
|
||||||
{forgotPasswordJsx}
|
|
||||||
<input className="mx_Login_submit" type="submit" value="Sign in" />
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
|
@ -21,6 +21,7 @@ import sdk from '../../../index';
|
||||||
import Email from '../../../email';
|
import Email from '../../../email';
|
||||||
import { looksValid as phoneNumberLooksValid } from '../../../phonenumber';
|
import { looksValid as phoneNumberLooksValid } from '../../../phonenumber';
|
||||||
import Modal from '../../../Modal';
|
import Modal from '../../../Modal';
|
||||||
|
import { _t } from '../../../languageHandler';
|
||||||
|
|
||||||
const FIELD_EMAIL = 'field_email';
|
const FIELD_EMAIL = 'field_email';
|
||||||
const FIELD_PHONE_COUNTRY = 'field_phone_country';
|
const FIELD_PHONE_COUNTRY = 'field_phone_country';
|
||||||
|
@ -100,13 +101,13 @@ module.exports = React.createClass({
|
||||||
if (this.refs.email.value == '') {
|
if (this.refs.email.value == '') {
|
||||||
var QuestionDialog = sdk.getComponent("dialogs.QuestionDialog");
|
var QuestionDialog = sdk.getComponent("dialogs.QuestionDialog");
|
||||||
Modal.createDialog(QuestionDialog, {
|
Modal.createDialog(QuestionDialog, {
|
||||||
title: "Warning",
|
title: "Warning!",
|
||||||
description:
|
description:
|
||||||
<div>
|
<div>
|
||||||
If you don't specify an email address, you won't be able to reset your password.<br/>
|
{_t("If you don't specify an email address, you won't be able to reset your password. " +
|
||||||
Are you sure?
|
"Are you sure?")}
|
||||||
</div>,
|
</div>,
|
||||||
button: "Continue",
|
button: _t("Continue"),
|
||||||
onFinished: function(confirmed) {
|
onFinished: function(confirmed) {
|
||||||
if (confirmed) {
|
if (confirmed) {
|
||||||
self._doSubmit();
|
self._doSubmit();
|
||||||
|
@ -270,7 +271,8 @@ module.exports = React.createClass({
|
||||||
|
|
||||||
_onPhoneCountryChange(newVal) {
|
_onPhoneCountryChange(newVal) {
|
||||||
this.setState({
|
this.setState({
|
||||||
phoneCountry: newVal,
|
phoneCountry: newVal.iso2,
|
||||||
|
phonePrefix: newVal.prefix,
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -280,7 +282,7 @@ module.exports = React.createClass({
|
||||||
const emailSection = (
|
const emailSection = (
|
||||||
<div>
|
<div>
|
||||||
<input type="text" ref="email"
|
<input type="text" ref="email"
|
||||||
autoFocus={true} placeholder="Email address (optional)"
|
autoFocus={true} placeholder={_t("Email address (optional)")}
|
||||||
defaultValue={this.props.defaultEmail}
|
defaultValue={this.props.defaultEmail}
|
||||||
className={this._classForField(FIELD_EMAIL, 'mx_Login_field')}
|
className={this._classForField(FIELD_EMAIL, 'mx_Login_field')}
|
||||||
onBlur={function() {self.validateField(FIELD_EMAIL);}}
|
onBlur={function() {self.validateField(FIELD_EMAIL);}}
|
||||||
|
@ -303,7 +305,7 @@ module.exports = React.createClass({
|
||||||
} else if (this.state.selectedTeam) {
|
} else if (this.state.selectedTeam) {
|
||||||
belowEmailSection = (
|
belowEmailSection = (
|
||||||
<p className="mx_Login_support">
|
<p className="mx_Login_support">
|
||||||
You are registering with {this.state.selectedTeam.name}
|
{_t("You are registering with %(SelectedTeamName)s", {SelectedTeamName: this.state.selectedTeam.name})}
|
||||||
</p>
|
</p>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -313,14 +315,19 @@ module.exports = React.createClass({
|
||||||
const phoneSection = (
|
const phoneSection = (
|
||||||
<div className="mx_Login_phoneSection">
|
<div className="mx_Login_phoneSection">
|
||||||
<CountryDropdown ref="phone_country" onOptionChange={this._onPhoneCountryChange}
|
<CountryDropdown ref="phone_country" onOptionChange={this._onPhoneCountryChange}
|
||||||
className="mx_Login_phoneCountry"
|
className="mx_Login_phoneCountry mx_Login_field_prefix"
|
||||||
value={this.state.phoneCountry}
|
value={this.state.phoneCountry}
|
||||||
|
isSmall={true}
|
||||||
|
showPrefix={true}
|
||||||
/>
|
/>
|
||||||
<input type="text" ref="phoneNumber"
|
<input type="text" ref="phoneNumber"
|
||||||
placeholder="Mobile phone number (optional)"
|
placeholder={_t("Mobile phone number (optional)")}
|
||||||
defaultValue={this.props.defaultPhoneNumber}
|
defaultValue={this.props.defaultPhoneNumber}
|
||||||
className={this._classForField(
|
className={this._classForField(
|
||||||
FIELD_PHONE_NUMBER, 'mx_Login_phoneNumberField', 'mx_Login_field'
|
FIELD_PHONE_NUMBER,
|
||||||
|
'mx_Login_phoneNumberField',
|
||||||
|
'mx_Login_field',
|
||||||
|
'mx_Login_field_has_prefix'
|
||||||
)}
|
)}
|
||||||
onBlur={function() {self.validateField(FIELD_PHONE_NUMBER);}}
|
onBlur={function() {self.validateField(FIELD_PHONE_NUMBER);}}
|
||||||
value={self.state.phoneNumber}
|
value={self.state.phoneNumber}
|
||||||
|
@ -332,9 +339,9 @@ module.exports = React.createClass({
|
||||||
<input className="mx_Login_submit" type="submit" value="Register" />
|
<input className="mx_Login_submit" type="submit" value="Register" />
|
||||||
);
|
);
|
||||||
|
|
||||||
let placeholderUserName = "User name";
|
let placeholderUserName = _t("User name");
|
||||||
if (this.props.guestUsername) {
|
if (this.props.guestUsername) {
|
||||||
placeholderUserName += " (default: " + this.props.guestUsername + ")";
|
placeholderUserName += " " + _t("(default: %(userName)s)", {userName: this.props.guestUsername});
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
@ -349,15 +356,15 @@ module.exports = React.createClass({
|
||||||
onBlur={function() {self.validateField(FIELD_USERNAME);}} />
|
onBlur={function() {self.validateField(FIELD_USERNAME);}} />
|
||||||
<br />
|
<br />
|
||||||
{ this.props.guestUsername ?
|
{ this.props.guestUsername ?
|
||||||
<div className="mx_Login_fieldLabel">Setting a user name will create a fresh account</div> : null
|
<div className="mx_Login_fieldLabel">{_t("Setting a user name will create a fresh account")}</div> : null
|
||||||
}
|
}
|
||||||
<input type="password" ref="password"
|
<input type="password" ref="password"
|
||||||
className={this._classForField(FIELD_PASSWORD, 'mx_Login_field')}
|
className={this._classForField(FIELD_PASSWORD, 'mx_Login_field')}
|
||||||
onBlur={function() {self.validateField(FIELD_PASSWORD);}}
|
onBlur={function() {self.validateField(FIELD_PASSWORD);}}
|
||||||
placeholder="Password" defaultValue={this.props.defaultPassword} />
|
placeholder={_t("Password")} defaultValue={this.props.defaultPassword} />
|
||||||
<br />
|
<br />
|
||||||
<input type="password" ref="passwordConfirm"
|
<input type="password" ref="passwordConfirm"
|
||||||
placeholder="Confirm password"
|
placeholder={_t("Confirm password")}
|
||||||
className={this._classForField(FIELD_PASSWORD_CONFIRM, 'mx_Login_field')}
|
className={this._classForField(FIELD_PASSWORD_CONFIRM, 'mx_Login_field')}
|
||||||
onBlur={function() {self.validateField(FIELD_PASSWORD_CONFIRM);}}
|
onBlur={function() {self.validateField(FIELD_PASSWORD_CONFIRM);}}
|
||||||
defaultValue={this.props.defaultPassword} />
|
defaultValue={this.props.defaultPassword} />
|
||||||
|
|
|
@ -19,6 +19,7 @@ limitations under the License.
|
||||||
var React = require('react');
|
var React = require('react');
|
||||||
var Modal = require('../../../Modal');
|
var Modal = require('../../../Modal');
|
||||||
var sdk = require('../../../index');
|
var sdk = require('../../../index');
|
||||||
|
import { _t } from '../../../languageHandler';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A pure UI component which displays the HS and IS to use.
|
* A pure UI component which displays the HS and IS to use.
|
||||||
|
@ -27,8 +28,7 @@ module.exports = React.createClass({
|
||||||
displayName: 'ServerConfig',
|
displayName: 'ServerConfig',
|
||||||
|
|
||||||
propTypes: {
|
propTypes: {
|
||||||
onHsUrlChanged: React.PropTypes.func,
|
onServerConfigChange: React.PropTypes.func,
|
||||||
onIsUrlChanged: React.PropTypes.func,
|
|
||||||
|
|
||||||
// default URLs are defined in config.json (or the hardcoded defaults)
|
// default URLs are defined in config.json (or the hardcoded defaults)
|
||||||
// they are used if the user has not overridden them with a custom URL.
|
// they are used if the user has not overridden them with a custom URL.
|
||||||
|
@ -50,8 +50,7 @@ module.exports = React.createClass({
|
||||||
|
|
||||||
getDefaultProps: function() {
|
getDefaultProps: function() {
|
||||||
return {
|
return {
|
||||||
onHsUrlChanged: function() {},
|
onServerConfigChange: function() {},
|
||||||
onIsUrlChanged: function() {},
|
|
||||||
customHsUrl: "",
|
customHsUrl: "",
|
||||||
customIsUrl: "",
|
customIsUrl: "",
|
||||||
withToggleButton: false,
|
withToggleButton: false,
|
||||||
|
@ -75,7 +74,10 @@ module.exports = React.createClass({
|
||||||
this._hsTimeoutId = this._waitThenInvoke(this._hsTimeoutId, function() {
|
this._hsTimeoutId = this._waitThenInvoke(this._hsTimeoutId, function() {
|
||||||
var hsUrl = this.state.hs_url.trim().replace(/\/$/, "");
|
var hsUrl = this.state.hs_url.trim().replace(/\/$/, "");
|
||||||
if (hsUrl === "") hsUrl = this.props.defaultHsUrl;
|
if (hsUrl === "") hsUrl = this.props.defaultHsUrl;
|
||||||
this.props.onHsUrlChanged(hsUrl);
|
this.props.onServerConfigChange({
|
||||||
|
hsUrl : this.state.hs_url,
|
||||||
|
isUrl : this.state.is_url,
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
@ -85,7 +87,10 @@ module.exports = React.createClass({
|
||||||
this._isTimeoutId = this._waitThenInvoke(this._isTimeoutId, function() {
|
this._isTimeoutId = this._waitThenInvoke(this._isTimeoutId, function() {
|
||||||
var isUrl = this.state.is_url.trim().replace(/\/$/, "");
|
var isUrl = this.state.is_url.trim().replace(/\/$/, "");
|
||||||
if (isUrl === "") isUrl = this.props.defaultIsUrl;
|
if (isUrl === "") isUrl = this.props.defaultIsUrl;
|
||||||
this.props.onIsUrlChanged(isUrl);
|
this.props.onServerConfigChange({
|
||||||
|
hsUrl : this.state.hs_url,
|
||||||
|
isUrl : this.state.is_url,
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
@ -102,12 +107,16 @@ module.exports = React.createClass({
|
||||||
configVisible: visible
|
configVisible: visible
|
||||||
});
|
});
|
||||||
if (!visible) {
|
if (!visible) {
|
||||||
this.props.onHsUrlChanged(this.props.defaultHsUrl);
|
this.props.onServerConfigChange({
|
||||||
this.props.onIsUrlChanged(this.props.defaultIsUrl);
|
hsUrl : this.props.defaultHsUrl,
|
||||||
|
isUrl : this.props.defaultIsUrl,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
this.props.onHsUrlChanged(this.state.hs_url);
|
this.props.onServerConfigChange({
|
||||||
this.props.onIsUrlChanged(this.state.is_url);
|
hsUrl : this.state.hs_url,
|
||||||
|
isUrl : this.state.is_url,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -123,19 +132,19 @@ module.exports = React.createClass({
|
||||||
var toggleButton;
|
var toggleButton;
|
||||||
if (this.props.withToggleButton) {
|
if (this.props.withToggleButton) {
|
||||||
toggleButton = (
|
toggleButton = (
|
||||||
<div style={{ textAlign: 'center' }}>
|
<div className="mx_ServerConfig_selector">
|
||||||
<input className="mx_Login_radio" id="basic" name="configVisible" type="radio"
|
<input className="mx_Login_radio" id="basic" name="configVisible" type="radio"
|
||||||
checked={!this.state.configVisible}
|
checked={!this.state.configVisible}
|
||||||
onChange={this.onServerConfigVisibleChange.bind(this, false)} />
|
onChange={this.onServerConfigVisibleChange.bind(this, false)} />
|
||||||
<label className="mx_Login_label" htmlFor="basic">
|
<label className="mx_Login_label" htmlFor="basic">
|
||||||
Default server
|
{_t("Default server")}
|
||||||
</label>
|
</label>
|
||||||
|
|
||||||
<input className="mx_Login_radio" id="advanced" name="configVisible" type="radio"
|
<input className="mx_Login_radio" id="advanced" name="configVisible" type="radio"
|
||||||
checked={this.state.configVisible}
|
checked={this.state.configVisible}
|
||||||
onChange={this.onServerConfigVisibleChange.bind(this, true)} />
|
onChange={this.onServerConfigVisibleChange.bind(this, true)} />
|
||||||
<label className="mx_Login_label" htmlFor="advanced">
|
<label className="mx_Login_label" htmlFor="advanced">
|
||||||
Custom server
|
{_t("Custom server")}
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
@ -147,7 +156,7 @@ module.exports = React.createClass({
|
||||||
<div style={serverConfigStyle}>
|
<div style={serverConfigStyle}>
|
||||||
<div className="mx_ServerConfig">
|
<div className="mx_ServerConfig">
|
||||||
<label className="mx_Login_label mx_ServerConfig_hslabel" htmlFor="hsurl">
|
<label className="mx_Login_label mx_ServerConfig_hslabel" htmlFor="hsurl">
|
||||||
Home server URL
|
{_t("Home server URL")}
|
||||||
</label>
|
</label>
|
||||||
<input className="mx_Login_field" id="hsurl" type="text"
|
<input className="mx_Login_field" id="hsurl" type="text"
|
||||||
placeholder={this.props.defaultHsUrl}
|
placeholder={this.props.defaultHsUrl}
|
||||||
|
@ -155,7 +164,7 @@ module.exports = React.createClass({
|
||||||
value={this.state.hs_url}
|
value={this.state.hs_url}
|
||||||
onChange={this.onHomeserverChanged} />
|
onChange={this.onHomeserverChanged} />
|
||||||
<label className="mx_Login_label mx_ServerConfig_islabel" htmlFor="isurl">
|
<label className="mx_Login_label mx_ServerConfig_islabel" htmlFor="isurl">
|
||||||
Identity server URL
|
{_t("Identity server URL")}
|
||||||
</label>
|
</label>
|
||||||
<input className="mx_Login_field" id="isurl" type="text"
|
<input className="mx_Login_field" id="isurl" type="text"
|
||||||
placeholder={this.props.defaultIsUrl}
|
placeholder={this.props.defaultIsUrl}
|
||||||
|
@ -163,7 +172,7 @@ module.exports = React.createClass({
|
||||||
value={this.state.is_url}
|
value={this.state.is_url}
|
||||||
onChange={this.onIdentityServerChanged} />
|
onChange={this.onIdentityServerChanged} />
|
||||||
<a className="mx_ServerConfig_help" href="#" onClick={this.showHelpPopup}>
|
<a className="mx_ServerConfig_help" href="#" onClick={this.showHelpPopup}>
|
||||||
What does this mean?
|
{_t("What does this mean?")}
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue