Compare commits

..

2 Commits

Author SHA1 Message Date
David Baker
faa5a4df9d Merge remote-tracking branch 'origin/develop' into aviraldg-babelrc 2016-10-17 15:40:37 +01:00
David Baker
28ddde28b9 Add plugin that makes babel 6 not break everything 2016-10-17 15:37:31 +01:00
263 changed files with 2272 additions and 6847 deletions

View File

@@ -1,23 +0,0 @@
# Copyright 2017 Aviral Dasgupta
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
root = true
[*]
charset=utf-8
end_of_line = lf
insert_final_newline = true
indent_style = space
indent_size = 4
trim_trailing_whitespace = true

View File

@@ -1,2 +0,0 @@
src/vector/modernizr.js
src/component-index.js

View File

@@ -1,3 +0,0 @@
module.exports = {
extends: ["./node_modules/matrix-react-sdk/.eslintrc.js"],
}

View File

@@ -1,44 +0,0 @@
<!-- This is a bug report template. By following the instructions below and
filling out the sections with your information, you will help the us to get all
the necessary data to fix your issue.
You can also preview your report before submitting it. You may remove sections
that aren't relevant to your particular case.
Text between <!-- and --> marks will be invisible in the report.
-->
### Description
Describe here the problem that you are experiencing, or the feature you are requesting.
### Steps to reproduce
- For bugs, list the steps
- that reproduce the bug
- using hyphens as bullet points
Describe how what happens differs from what you expected.
Log: sent/not sent? <!-- You can send us the app's logs via the 'Report bug'
link on the 'Settings' page. Very important for hard-to-reproduce bugs. Please
file a bug here too! -->
<!-- Include screenshots if possible: you can drag and drop images below. -->
### Version information
<!-- IMPORTANT: please answer the following questions, to help us narrow down the problem -->
- **Platform**: web (in-browser) or desktop?
For the web app:
- **Browser**: Chrome, Safari, Firefox? which version?
- **OS**: Windows, macOS, Ubuntu, Arch Linux, etc?
- **URL**: riot.im/develop / riot.im/app / somewhere else? If a private server, what version of riot-web?
For the desktop app:
- **OS**: Windows, macOS, Ubuntu, Arch Linux, etc?
- **Version**: 0.x.y <!-- check the user settings panel if unsure -->

13
.gitignore vendored
View File

@@ -1,15 +1,12 @@
/build
/cert.pem
/dist
/karma-reports
/key.pem
/lib
/node_modules
/packages/
/webapp
/.npmrc
/vector/bundle.*
/vector/components.css
/vector/emojione/
/vector/config.json
/vector/olm.js
.DS_Store
npm-debug.log
electron/dist
electron/pub
/config.json

View File

@@ -0,0 +1,3 @@
example
examples
build/.module-cache

View File

@@ -3,5 +3,4 @@ node_js:
- 6 # node v6, to match jenkins
install:
- npm install
- (cd node_modules/matrix-js-sdk && npm install)
- (cd node_modules/matrix-react-sdk && npm install)
- (cd node_modules/matrix-react-sdk && npm run build)

View File

@@ -10,6 +10,3 @@ include:
* Florent VIOLLEAU (https://github.com/floviolleau) <floviolleau at gmail dot com>
Improve README.md for a better understanding of installation instructions
* Michael Telatynski (https://github.com/t3chguy)
Improved consistency of inverted elements in dark theme across browsers

View File

@@ -1,443 +1,3 @@
Changes in [0.9.8](https://github.com/vector-im/riot-web/releases/tag/v0.9.8) (2017-04-12)
==========================================================================================
[Full Changelog](https://github.com/vector-im/riot-web/compare/v0.9.8-rc.3...v0.9.8)
* No changes
Changes in [0.9.8-rc.3](https://github.com/vector-im/riot-web/releases/tag/v0.9.8-rc.3) (2017-04-11)
====================================================================================================
[Full Changelog](https://github.com/vector-im/riot-web/compare/v0.9.8-rc.2...v0.9.8-rc.3)
* Make the clear cache button work on desktop
[\#3598](https://github.com/vector-im/riot-web/pull/3598)
Changes in [0.9.8-rc.2](https://github.com/vector-im/riot-web/releases/tag/v0.9.8-rc.2) (2017-04-10)
====================================================================================================
[Full Changelog](https://github.com/vector-im/riot-web/compare/v0.9.8-rc.1...v0.9.8-rc.2)
* Redacted events bg: black lozenge -> torn paper
[\#3596](https://github.com/vector-im/riot-web/pull/3596)
* Add 'app' parameter to rageshake report
[\#3594](https://github.com/vector-im/riot-web/pull/3594)
Changes in [0.9.8-rc.1](https://github.com/vector-im/riot-web/releases/tag/v0.9.8-rc.1) (2017-04-07)
====================================================================================================
[Full Changelog](https://github.com/vector-im/riot-web/compare/v0.9.7...v0.9.8-rc.1)
* Add support for indexeddb sync in webworker
[\#3578](https://github.com/vector-im/riot-web/pull/3578)
* Add CSS to make Emote sender cursor : pointer
[\#3574](https://github.com/vector-im/riot-web/pull/3574)
* Remove rageshake server
[\#3565](https://github.com/vector-im/riot-web/pull/3565)
* Adjust CSS for matrix-org/matrix-react-sdk#789
[\#3566](https://github.com/vector-im/riot-web/pull/3566)
* Fix tests to reflect recent changes
[\#3537](https://github.com/vector-im/riot-web/pull/3537)
* Do not assume getTs will return comparable integer
[\#3536](https://github.com/vector-im/riot-web/pull/3536)
* Rename ReactPerf to Perf
[\#3535](https://github.com/vector-im/riot-web/pull/3535)
* Don't show phone number as target for email notifs
[\#3530](https://github.com/vector-im/riot-web/pull/3530)
* Fix people section again
[\#3458](https://github.com/vector-im/riot-web/pull/3458)
* dark theme invert inconsistent across browsers
[\#3479](https://github.com/vector-im/riot-web/pull/3479)
* CSS for adding phone number in UserSettings
[\#3451](https://github.com/vector-im/riot-web/pull/3451)
* Support for phone number registration/signin, mk2
[\#3426](https://github.com/vector-im/riot-web/pull/3426)
* Confirm redactions with a dialog
[\#3470](https://github.com/vector-im/riot-web/pull/3470)
* Better CSS for redactions
[\#3453](https://github.com/vector-im/riot-web/pull/3453)
* Fix the people section
[\#3448](https://github.com/vector-im/riot-web/pull/3448)
* Merge the two RoomTile context menus into one
[\#3395](https://github.com/vector-im/riot-web/pull/3395)
* Refactor screen set after login
[\#3385](https://github.com/vector-im/riot-web/pull/3385)
* CSS for redacted EventTiles
[\#3379](https://github.com/vector-im/riot-web/pull/3379)
* Height:100% for welcome pages on Safari
[\#3340](https://github.com/vector-im/riot-web/pull/3340)
* `view_room` dispatch from `onClick` RoomTile
[\#3376](https://github.com/vector-im/riot-web/pull/3376)
* Hide statusAreaBox_line entirely when inCall
[\#3350](https://github.com/vector-im/riot-web/pull/3350)
* Set padding-bottom: 0px for .mx_Dialog spinner
[\#3351](https://github.com/vector-im/riot-web/pull/3351)
* Support InteractiveAuth based registration
[\#3333](https://github.com/vector-im/riot-web/pull/3333)
* Expose notification option for username/MXID
[\#3334](https://github.com/vector-im/riot-web/pull/3334)
* Float the toggle in the top right of MELS
[\#3190](https://github.com/vector-im/riot-web/pull/3190)
* More aggressive rageshake log culling
[\#3311](https://github.com/vector-im/riot-web/pull/3311)
* Don't overflow directory network options
[\#3282](https://github.com/vector-im/riot-web/pull/3282)
* CSS for ban / kick reason prompt
[\#3250](https://github.com/vector-im/riot-web/pull/3250)
* Allow forgetting rooms you're banned from
[\#3246](https://github.com/vector-im/riot-web/pull/3246)
* Fix icon paths in manifest
[\#3245](https://github.com/vector-im/riot-web/pull/3245)
* Fix broken tests caused by adding IndexedDB support
[\#3242](https://github.com/vector-im/riot-web/pull/3242)
* CSS for un-ban button in RoomSettings
[\#3227](https://github.com/vector-im/riot-web/pull/3227)
* Remove z-index property on avatar initials
[\#3239](https://github.com/vector-im/riot-web/pull/3239)
* Reposition certain icons in the status bar
[\#3233](https://github.com/vector-im/riot-web/pull/3233)
* CSS for kick/ban confirmation dialog
[\#3224](https://github.com/vector-im/riot-web/pull/3224)
* Style for split-out interactive auth
[\#3217](https://github.com/vector-im/riot-web/pull/3217)
* Use the teamToken threaded through from react sdk
[\#3196](https://github.com/vector-im/riot-web/pull/3196)
* rageshake: Add file server with basic auth
[\#3169](https://github.com/vector-im/riot-web/pull/3169)
* Fix bug with home icon not appearing when logged in as team member
[\#3162](https://github.com/vector-im/riot-web/pull/3162)
* Add ISSUE_TEMPLATE
[\#2836](https://github.com/vector-im/riot-web/pull/2836)
* Store bug reports in separate directories
[\#3150](https://github.com/vector-im/riot-web/pull/3150)
* Quick and dirty support for custom welcome pages.
[\#2575](https://github.com/vector-im/riot-web/pull/2575)
* RTS Welcome Pages
[\#3103](https://github.com/vector-im/riot-web/pull/3103)
* rageshake: Abide by Go standards
[\#3149](https://github.com/vector-im/riot-web/pull/3149)
* Bug report server script
[\#3072](https://github.com/vector-im/riot-web/pull/3072)
* Bump olm version
[\#3125](https://github.com/vector-im/riot-web/pull/3125)
Changes in [0.9.7](https://github.com/vector-im/riot-web/releases/tag/v0.9.7) (2017-02-04)
==========================================================================================
[Full Changelog](https://github.com/vector-im/riot-web/compare/v0.9.7-rc.3...v0.9.7)
* Update to matrix-js-sdk 0.7.5 (no changes from 0.7.5-rc.3)
* Update to matrix-react-sdk 0.8.6 (no changes from 0.8.6-rc.3)
Changes in [0.9.7-rc.3](https://github.com/vector-im/riot-web/releases/tag/v0.9.7-rc.3) (2017-02-03)
====================================================================================================
[Full Changelog](https://github.com/vector-im/riot-web/compare/v0.9.7-rc.2...v0.9.7-rc.3)
* Update to latest Olm to fix key import/export and use of megolm sessions
created on more recent versions
* Update to latest matrix-react-sdk and matrix-js-sdk to fix e2e device
handling
Changes in [0.9.7-rc.2](https://github.com/vector-im/riot-web/releases/tag/v0.9.7-rc.2) (2017-02-03)
====================================================================================================
[Full Changelog](https://github.com/vector-im/riot-web/compare/v0.9.7-rc.1...v0.9.7-rc.2)
* Update matrix-js-sdk to get new device change
notifications interface for more reliable e2e crypto
Changes in [0.9.7-rc.1](https://github.com/vector-im/riot-web/releases/tag/v0.9.7-rc.1) (2017-02-03)
====================================================================================================
[Full Changelog](https://github.com/vector-im/riot-web/compare/v0.9.6...v0.9.7-rc.1)
* Better user interface for screen readers and keyboard navigation
[\#2946](https://github.com/vector-im/riot-web/pull/2946)
* Allow mxc: URLs for icons in the NetworkDropdown
[\#3118](https://github.com/vector-im/riot-web/pull/3118)
* make TopRightMenu more intuitive
[\#3117](https://github.com/vector-im/riot-web/pull/3117)
* Handle icons with width > height
[\#3110](https://github.com/vector-im/riot-web/pull/3110)
* Fix jenkins build
[\#3105](https://github.com/vector-im/riot-web/pull/3105)
* Add CSS for a support box in login
[\#3081](https://github.com/vector-im/riot-web/pull/3081)
* Allow a custom login logo to be displayed on login
[\#3082](https://github.com/vector-im/riot-web/pull/3082)
* Fix the width of input fields within login/reg box
[\#3080](https://github.com/vector-im/riot-web/pull/3080)
* Set BaseAvatar_image bg colour = #fff
[\#3057](https://github.com/vector-im/riot-web/pull/3057)
* only recalculate favicon if it changes
[\#3067](https://github.com/vector-im/riot-web/pull/3067)
* CSS tweak for email address lookup
[\#3064](https://github.com/vector-im/riot-web/pull/3064)
* Glue the dialog to rageshake: honour sendLogs flag.
[\#3061](https://github.com/vector-im/riot-web/pull/3061)
* Don't use hash-named directory for dev server
[\#3049](https://github.com/vector-im/riot-web/pull/3049)
* Implement bug reporting logic
[\#3000](https://github.com/vector-im/riot-web/pull/3000)
* Add css for bug report dialog
[\#3045](https://github.com/vector-im/riot-web/pull/3045)
* Increase the max-height of the expanded status bar
[\#3043](https://github.com/vector-im/riot-web/pull/3043)
* Hopefully, fix intermittent test failure
[\#3040](https://github.com/vector-im/riot-web/pull/3040)
* CSS for 'searching known users'
[\#2971](https://github.com/vector-im/riot-web/pull/2971)
* Animate status bar max-height and margin-top
[\#2981](https://github.com/vector-im/riot-web/pull/2981)
* Add eslint config
[\#3032](https://github.com/vector-im/riot-web/pull/3032)
* Re-position typing avatars relative to "is typing"
[\#3030](https://github.com/vector-im/riot-web/pull/3030)
* CSS for avatars that appear when users are typing
[\#2998](https://github.com/vector-im/riot-web/pull/2998)
* Add StartupWMClass
[\#3001](https://github.com/vector-im/riot-web/pull/3001)
* Fix link to image for event options menu
[\#3002](https://github.com/vector-im/riot-web/pull/3002)
* Make riot desktop single instance
[\#2999](https://github.com/vector-im/riot-web/pull/2999)
* Add electron tray icon
[\#2997](https://github.com/vector-im/riot-web/pull/2997)
* Fixes to electron desktop notifs
[\#2994](https://github.com/vector-im/riot-web/pull/2994)
* Auto-hide the electron menu bar
[\#2975](https://github.com/vector-im/riot-web/pull/2975)
* A couple of tweaks to the karma config
[\#2987](https://github.com/vector-im/riot-web/pull/2987)
* Deploy script
[\#2974](https://github.com/vector-im/riot-web/pull/2974)
* Use the postcss-webpack-loader
[\#2990](https://github.com/vector-im/riot-web/pull/2990)
* Switch CSS to using postcss, and implement a dark theme.
[\#2973](https://github.com/vector-im/riot-web/pull/2973)
* Update redeploy script to keep old bundles
[\#2969](https://github.com/vector-im/riot-web/pull/2969)
* Include current version in update check explicitly
[\#2967](https://github.com/vector-im/riot-web/pull/2967)
* Add another layer of directory to webpack chunks
[\#2966](https://github.com/vector-im/riot-web/pull/2966)
* Fix links to fonts and images from CSS
[\#2965](https://github.com/vector-im/riot-web/pull/2965)
* Put parent build hash in webpack output filenames
[\#2961](https://github.com/vector-im/riot-web/pull/2961)
* update README to point to new names/locations
[\#2846](https://github.com/vector-im/riot-web/pull/2846)
Changes in [0.9.6](https://github.com/vector-im/riot-web/releases/tag/v0.9.6) (2017-01-16)
==========================================================================================
[Full Changelog](https://github.com/vector-im/riot-web/compare/v0.9.6-rc.1...v0.9.6)
* Update to matrix-js-sdk 0.9.6 for video calling fix
Changes in [0.9.6-rc.1](https://github.com/vector-im/riot-web/releases/tag/v0.9.6-rc.1) (2017-01-13)
====================================================================================================
[Full Changelog](https://github.com/vector-im/riot-web/compare/v0.9.5...v0.9.6-rc.1)
* Build the js-sdk in the CI script
[\#2920](https://github.com/vector-im/riot-web/pull/2920)
* Hopefully fix Windows shortcuts
[\#2917](https://github.com/vector-im/riot-web/pull/2917)
* Update README now the js-sdk has a transpile step
[\#2921](https://github.com/vector-im/riot-web/pull/2921)
* Use the role for 'toggle dev tools'
[\#2915](https://github.com/vector-im/riot-web/pull/2915)
* Enable screen sharing easter-egg in desktop app
[\#2909](https://github.com/vector-im/riot-web/pull/2909)
* make electron send email validation URLs with a nextlink of riot.im
[\#2808](https://github.com/vector-im/riot-web/pull/2808)
* add Debian Stretch install steps to readme
[\#2809](https://github.com/vector-im/riot-web/pull/2809)
* Update desktop build instructions fixes #2792
[\#2793](https://github.com/vector-im/riot-web/pull/2793)
* CSS for the delete threepid button
[\#2784](https://github.com/vector-im/riot-web/pull/2784)
Changes in [0.9.5](https://github.com/vector-im/riot-web/releases/tag/v0.9.5) (2016-12-24)
==========================================================================================
[Full Changelog](https://github.com/vector-im/riot-web/compare/v0.9.4...v0.9.5)
* make electron send email validation URLs with a nextlink of riot.im rather than file:///
* add gnu-tar to debian electron build deps
* fix win32 shortcut in start menu
Changes in [0.9.4](https://github.com/vector-im/riot-web/releases/tag/v0.9.4) (2016-12-22)
==========================================================================================
[Full Changelog](https://github.com/vector-im/riot-web/compare/v0.9.3...v0.9.4)
* Update to libolm 2.1.0. This should help resolve a problem with browser
sessions being logged out ([\#2726](https://github.com/vector-im/riot-web/issues/2726)).
Changes in [0.9.3](https://github.com/vector-im/riot-web/releases/tag/v0.9.3) (2016-12-22)
==========================================================================================
[Full Changelog](https://github.com/vector-im/riot-web/compare/v0.9.2...v0.9.3)
* (from matrix-react-sdk) Fix regression where the date separator would be displayed
at the wrong time of day.
* README.md: fix GFMD for nativefier
[\#2755](https://github.com/vector-im/riot-web/pull/2755)
Changes in [0.9.2](https://github.com/vector-im/riot-web/releases/tag/v0.9.2) (2016-12-16)
==========================================================================================
[Full Changelog](https://github.com/vector-im/riot-web/compare/v0.9.1...v0.9.2)
* Remove the client side filtering from the room dir
[\#2750](https://github.com/vector-im/riot-web/pull/2750)
* Configure olm memory size
[\#2745](https://github.com/vector-im/riot-web/pull/2745)
* Support room dir 3rd party network filtering
[\#2747](https://github.com/vector-im/riot-web/pull/2747)
Changes in [0.9.1](https://github.com/vector-im/riot-web/releases/tag/v0.9.1) (2016-12-09)
==========================================================================================
[Full Changelog](https://github.com/vector-im/riot-web/compare/v0.9.1-rc.2...v0.9.1)
* Update README to say how to build the desktop app
[\#2732](https://github.com/vector-im/riot-web/pull/2732)
* Add signing ID in release_config.yaml
[\#2731](https://github.com/vector-im/riot-web/pull/2731)
* Makeover!
[\#2722](https://github.com/vector-im/riot-web/pull/2722)
* Fix broken tests
[\#2730](https://github.com/vector-im/riot-web/pull/2730)
* Make the 'loading' tests work in isolation
[\#2727](https://github.com/vector-im/riot-web/pull/2727)
* Use a PNG for the icon on non-Windows
[\#2708](https://github.com/vector-im/riot-web/pull/2708)
* Add missing brackets to call to toUpperCase
[\#2703](https://github.com/vector-im/riot-web/pull/2703)
Changes in [0.9.1-rc.2](https://github.com/vector-im/riot-web/releases/tag/v0.9.1-rc.2) (2016-12-06)
====================================================================================================
[Full Changelog](https://github.com/vector-im/riot-web/compare/v0.9.1-rc.1...v0.9.1-rc.2)
* Fix clicking on notifications
[\#2700](https://github.com/vector-im/riot-web/pull/2700)
* Desktop app: Only show window when ready
[\#2697](https://github.com/vector-im/riot-web/pull/2697)
Changes in [0.9.1-rc.1](https://github.com/vector-im/riot-web/releases/tag/v0.9.1-rc.1) (2016-12-05)
====================================================================================================
[Full Changelog](https://github.com/vector-im/riot-web/compare/v0.9.0...v0.9.1-rc.1)
* Final bits to prepare electron distribtion:
[\#2653](https://github.com/vector-im/riot-web/pull/2653)
* Update name & repo to reflect renamed repository
[\#2692](https://github.com/vector-im/riot-web/pull/2692)
* Document cross_origin_renderer_url
[\#2680](https://github.com/vector-im/riot-web/pull/2680)
* Add css for the iframes for e2e attachments
[\#2659](https://github.com/vector-im/riot-web/pull/2659)
* Fix config location in some more places
[\#2670](https://github.com/vector-im/riot-web/pull/2670)
* CSS updates for s/block/blacklist for e2e
[\#2662](https://github.com/vector-im/riot-web/pull/2662)
* Update to electron 1.4.8
[\#2660](https://github.com/vector-im/riot-web/pull/2660)
* Add electron config
[\#2644](https://github.com/vector-im/riot-web/pull/2644)
* Move getDefaultDeviceName into the Platforms
[\#2643](https://github.com/vector-im/riot-web/pull/2643)
* Add Freenode & Mozilla domains
[\#2641](https://github.com/vector-im/riot-web/pull/2641)
* Include config.sample.json in dist tarball
[\#2614](https://github.com/vector-im/riot-web/pull/2614)
Changes in [0.9.0](https://github.com/vector-im/vector-web/releases/tag/v0.9.0) (2016-11-19)
============================================================================================
[Full Changelog](https://github.com/vector-im/vector-web/compare/v0.8.4...v0.9.0)
* Add a cachebuster to /version
[\#2596](https://github.com/vector-im/vector-web/pull/2596)
* Add a 'View decrypted source' button
[\#2587](https://github.com/vector-im/vector-web/pull/2587)
* Fix changelog dialog to read new version format
[\#2577](https://github.com/vector-im/vector-web/pull/2577)
* Build all of the vector dir in the build process
[\#2558](https://github.com/vector-im/vector-web/pull/2558)
* Support for get_app_version
[\#2553](https://github.com/vector-im/vector-web/pull/2553)
* Add CSS for mlist truncation
[\#2565](https://github.com/vector-im/vector-web/pull/2565)
* Add menu option for `external_url` if present
[\#2560](https://github.com/vector-im/vector-web/pull/2560)
* Make auto-update configureable
[\#2555](https://github.com/vector-im/vector-web/pull/2555)
* Missed files electron windows fixes
[\#2556](https://github.com/vector-im/vector-web/pull/2556)
* Add some CSS for scalar error popup
[\#2554](https://github.com/vector-im/vector-web/pull/2554)
* Catch unhandled errors in the electron process
[\#2552](https://github.com/vector-im/vector-web/pull/2552)
* Slight grab-bag of fixes for electron on Windows
[\#2551](https://github.com/vector-im/vector-web/pull/2551)
* Electron app (take 3)
[\#2535](https://github.com/vector-im/vector-web/pull/2535)
* Use webpack-dev-server instead of http-server
[\#2542](https://github.com/vector-im/vector-web/pull/2542)
* Better support no-config when loading from file
[\#2541](https://github.com/vector-im/vector-web/pull/2541)
* Fix loading with no config from HTTP
[\#2540](https://github.com/vector-im/vector-web/pull/2540)
* Move 'new version' support into Platform
[\#2532](https://github.com/vector-im/vector-web/pull/2532)
* Add Notification support to the Web Platform
[\#2533](https://github.com/vector-im/vector-web/pull/2533)
* Use the defaults if given a blank config file
[\#2534](https://github.com/vector-im/vector-web/pull/2534)
* Implement Platforms
[\#2531](https://github.com/vector-im/vector-web/pull/2531)
Changes in [0.8.4](https://github.com/vector-im/vector-web/releases/tag/v0.8.4) (2016-11-04)
============================================================================================
[Full Changelog](https://github.com/vector-im/vector-web/compare/v0.8.4-rc.2...v0.8.4)
* No changes
Changes in [0.8.4-rc.2](https://github.com/vector-im/vector-web/releases/tag/v0.8.4-rc.2) (2016-11-02)
======================================================================================================
[Full Changelog](https://github.com/vector-im/vector-web/compare/v0.8.4-rc.1...v0.8.4-rc.2)
* Fix the version in the generated distribution package
Changes in [0.8.4-rc.1](https://github.com/vector-im/vector-web/releases/tag/v0.8.4-rc.1) (2016-11-02)
======================================================================================================
[Full Changelog](https://github.com/vector-im/vector-web/compare/v0.8.3...v0.8.4-rc.1)
Breaking Changes
----------------
* End-to-end encryption now requires one-time keys to be
signed, so end-to-end encryption will not interoperate
with previous releases of vector-web. End-to-end encryption
remains in beta.
Other Changes
-------------
* Rename the package script/output dir to 'dist'
[\#2528](https://github.com/vector-im/vector-web/pull/2528)
* Avoid errors if olm is missing
[\#2518](https://github.com/vector-im/vector-web/pull/2518)
* Put a cachebuster in the names of CSS and JS files
[\#2515](https://github.com/vector-im/vector-web/pull/2515)
* Bump to olm 2.0.0
[\#2517](https://github.com/vector-im/vector-web/pull/2517)
* Don't include the world in the published packages
[\#2516](https://github.com/vector-im/vector-web/pull/2516)
* Use webpack to copy olm.js
[\#2514](https://github.com/vector-im/vector-web/pull/2514)
* Don't include two copies of the CSS in the tarball
[\#2513](https://github.com/vector-im/vector-web/pull/2513)
* Correct text alignment on room directory search
[\#2512](https://github.com/vector-im/vector-web/pull/2512)
* Correct spelling of 'rel'
[\#2510](https://github.com/vector-im/vector-web/pull/2510)
* readme tweaks
[\#2507](https://github.com/vector-im/vector-web/pull/2507)
* s/vector/riot/ in the readme
[\#2491](https://github.com/vector-im/vector-web/pull/2491)
* Switch to babel 6, again
[\#2480](https://github.com/vector-im/vector-web/pull/2480)
* Revert "Switch to babel 6"
[\#2472](https://github.com/vector-im/vector-web/pull/2472)
* Switch to babel 6
[\#2461](https://github.com/vector-im/vector-web/pull/2461)
Changes in [0.8.3](https://github.com/vector-im/vector-web/releases/tag/v0.8.3) (2016-10-12)
============================================================================================
[Full Changelog](https://github.com/vector-im/vector-web/compare/v0.8.2...v0.8.3)

View File

@@ -1,4 +1,4 @@
Contributing code to Riot
=========================
Contributing code to Vector
===========================
Riot follows the same pattern as https://github.com/matrix-org/synapse/blob/master/CONTRIBUTING.rst.
Vector follows the same pattern as https://github.com/matrix-org/synapse/blob/master/CONTRIBUTING.rst

297
README.md
View File

@@ -1,101 +1,67 @@
Riot
====
Vector/Web
==========
Riot (formerly known as Vector) is a Matrix web client built using the Matrix
React SDK (https://github.com/matrix-org/matrix-react-sdk).
Vector is a Matrix web client built using the Matrix React SDK (https://github.com/matrix-org/matrix-react-sdk).
Getting Started
===============
The easiest way to test Riot is to just use the hosted copy at
https://riot.im/app. The develop branch is continuously deployed by Jenkins at
https://riot.im/develop for those who like living dangerously.
The easiest way to test Vector is to just use the hosted copy at https://vector.im/beta.
The develop branch is continuously deployed by Jenkins at https://vector.im/develop for
those who like living dangerously.
To host your own copy of Riot, the quickest bet is to use a pre-built
released version of Riot:
To host your own copy of Vector, the quickest bet is to use a pre-built released version
of Vector:
1. Download the latest version from https://github.com/vector-im/riot-web/releases
1. Download the latest version from https://vector.im/packages/
1. Untar the tarball on your web server
1. Move (or symlink) the vector-x.x.x directory to an appropriate name
1. If desired, copy `config.sample.json` to `config.json` and edit it
as desired. See below for details.
1. Enter the URL into your browser and log into Riot!
Releases are signed by PGP, and can be checked against the public key
at https://riot.im/packages/keys/riot-master.asc
Note that Chrome does not allow microphone or webcam access for sites served
over http (except localhost), so for working VoIP you will need to serve Riot
over https.
### Installation Steps for Debian Stretch
1. Add the repository to your sources.list using either of the following two options:
- Directly to sources.list: `echo "deb https://riot.im/packages/debian/ stretch main" | sudo tee -a /etc/apt/sources.list`
- As a separate entry in sources.list.d: `echo "deb https://riot.im/packages/debian/ stretch main" | sudo tee /etc/apt/sources.list.d/riot.list`
2. Add the gpg signing key for the riot repository: `curl -s https://riot.im/packages/debian/repo-key.asc | sudo apt-key add -`
3. Update your package lists: `sudo apt-get update`
4. Install Riot: `sudo apt-get install riot-web`
1. Enter the URL into your browser and log into vector!
Important Security Note
=======================
We do not recommend running Riot from the same domain name as your Matrix
homeserver. The reason is the risk of XSS (cross-site-scripting)
vulnerabilities that could occur if someone caused Riot to load and render
malicious user generated content from a Matrix API which then had trusted
access to Riot (or other apps) due to sharing the same domain.
We do not recommend running Vector from the same domain name as your Matrix
homeserver. The reason is the risk of XSS (cross-site-scripting) vulnerabilities
that could occur if someone caused Vector to load and render malicious user generated
content from a Matrix API which then had trusted access to Vector (or other apps) due
to sharing the same domain.
We have put some coarse mitigations into place to try to protect against this
situation, but it's still not good practice to do it in the first place. See
https://github.com/vector-im/riot-web/issues/1977 for more details.
We have put some coarse mitigations into place to try to protect against this situation,
but it's still not good practice to do it in the first place.
See https://github.com/vector-im/vector-web/issues/1977 for more details.
Building From Source
====================
Riot is a modular webapp built with modern ES6 and requires a npm build system
to build.
Vector is a modular webapp built with modern ES6 and requires a npm build system to build.
1. Install or update `node.js` so that your `npm` is at least at version `2.0.0`
1. Clone the repo: `git clone https://github.com/vector-im/riot-web.git`
1. Switch to the riot-web directory: `cd riot-web`
1. Clone the repo: `git clone https://github.com/vector-im/vector-web.git`
1. Switch to the vector directory: `cd vector-web`
1. Install the prerequisites: `npm install`
1. If you are using the `develop` branch of vector-web, you will probably need
to rebuild some of the dependencies, due to
https://github.com/npm/npm/issues/3055:
```
(cd node_modules/matrix-js-sdk && npm install)
(cd node_modules/matrix-react-sdk && npm install)
```
Whenever you git pull on riot-web you will also probably need to force an update
to these dependencies - the easiest way is probably:
```
rm -rf node_modules/matrjx-{js,react}-sdk && npm i
(cd node_modules/matrix-js-sdk && npm install)
(cd node_modules/matrix-react-sdk && npm install)
```
However, we recommend setting up a proper development environment (see "Setting
up a development environment" below) if you want to run your own copy of the
`develop` branch, as it makes it much easier to keep these dependencies
up-to-date. Or just use https://riot.im/develop - the continuous integration
release of the develop branch.
1. Configure the app by copying `config.sample.json` to `config.json` and
modifying it (see below for details)
1. `npm run dist` to build a tarball to deploy. Untaring this file will give
1. If you are using the `develop` branch of vector, you will probably need to
rebuild one of the dependencies, due to https://github.com/npm/npm/issues/3055:
`(cd node_modules/matrix-react-sdk && npm install)`
1. Configure the app by copying `config.sample.json` to `config.json` and modifying
it (see below for details)
1. `npm run package` to build a tarball to deploy. Untaring this file will give
a version-specific directory containing all the files that need to go on your
web server.
Note that `npm run dist` is not supported on Windows, so Windows users can run `npm
run build`, which will build all the necessary files into the `webapp`
directory. The version of Riot will not appear in Settings without
using the dist script. You can then mount the `webapp` directory on your
Note that `npm run package` is not supported on Windows, so Windows users can run `npm
run build`, which will build all the necessary files into the `vector`
directory. The version of Vector will not appear in Settings without
using the package script. You can then mount the vector directory on your
webserver to actually serve up the app, which is entirely static content.
config.json
===========
You can configure the app by copying `config.sample.json` to
`config.json` and customising it:
You can configure the app by copying `vector/config.sample.json` to
`vector/config.json` and customising it:
1. `default_hs_url` is the default home server url.
1. `default_is_url` is the default identity server url (this is the server used
@@ -106,84 +72,62 @@ You can configure the app by copying `config.sample.json` to
addresses) to matrix IDs: see http://matrix.org/docs/spec/identity_service/unstable.html
for more details. Currently the only public matrix identity servers are https://matrix.org
and https://vector.im. In future identity servers will be decentralised.
1. `integrations_ui_url`: URL to the web interface for the integrations server.
1. `integrations_rest_url`: URL to the REST interface for the integrations server.
1. `roomDirectory`: config for the public room directory. This section is optional.
1. `roomDirectory`: config for the public room directory. This section encodes behaviour
on the room directory screen for filtering the list by server / network type and joining
third party networks. This config section will disappear once APIs are available to
get this information for home servers. This section is optional.
1. `roomDirectory.servers`: List of other Home Servers' directories to include in the drop
down list. Optional.
1. `update_base_url` (electron app only): HTTPS URL to a web server to download
updates from. This should be the path to the directory containing `macos`
and `win32` (for update packages, not installer packages).
1. `cross_origin_renderer_url`: URL to a static HTML page hosting code to help display
encrypted file attachments. This MUST be hosted on a completely separate domain to
anything else since it is used to isolate the privileges of file attachments to this
domain. Default: `usercontent.riot.im`. This needs to contain v1.html from
https://github.com/matrix-org/usercontent/blob/master/v1.html
1. `roomDirectory.serverConfig`: Config for each server in `roomDirectory.servers`. Optional.
1. `roomDirectory.serverConfig.<server_name>.networks`: List of networks (named
in `roomDirectory.networks`) to include for this server. Optional.
1. `roomDirectory.networks`: config for each network type. Optional.
1. `roomDirectory.<network_type>.name`: Human-readable name for the network. Required.
1. `roomDirectory.<network_type>.protocol`: Protocol as given by the server in
`/_matrix/client/unstable/thirdparty/protocols` response. Required to be able to join
this type of third party network.
1. `roomDirectory.<network_type>.domain`: Domain as given by the server in
`/_matrix/client/unstable/thirdparty/protocols` response, if present. Required to be
able to join this type of third party network, if present in `thirdparty/protocols`.
1. `roomDirectory.<network_type>.portalRoomPattern`: Regular expression matching aliases
for portal rooms to locations on this network. Required.
1. `roomDirectory.<network_type>.icon`: URL to an icon to be displayed for this network. Required.
1. `roomDirectory.<network_type>.example`: Textual example of a location on this network,
eg. '#channel' for an IRC network. Optional.
1. `roomDirectory.<network_type>.nativePattern`: Regular expression that matches a
valid location on this network. This is used as a hint to the user to indicate
when a valid location has been entered so it's not necessary for this to be
exactly correct. Optional.
Running as a Desktop app
========================
Riot can also be run as a desktop app, wrapped in electron. You can download a
pre-built version from https://riot.im/desktop.html or, if you prefer,
built it yourself.
In future we'll do an official distribution of Vector as an desktop app. Meanwhile,
there are a few options:
To run as a desktop app:
1. Follow the instructions in 'Building From Source' above, but run
`npm run build` instead of `npm run dist` (since we don't need the tarball).
2. Install electron and run it:
```
npm install electron
node_modules/.bin/electron .
```
To build packages, use electron-builder. This is configured to output:
* dmg + zip for macOS
* exe + nupkg for Windows
* deb for Linux
But this can be customised by editing the `build` section of package.json
as per https://github.com/electron-userland/electron-builder/wiki/Options
See https://github.com/electron-userland/electron-builder/wiki/Multi-Platform-Build
for dependencies required for building packages for various platforms.
The only platform that can build packages for all three platforms is macOS:
```
brew install wine --without-x11
brew install mono
brew install gnu-tar
npm install
npm run build:electron
```
For other packages, use electron-builder manually. For example, to build a package
for 64 bit Linux:
1. Follow the instructions in 'Building From Source' above
2. `node_modules/.bin/build -l --x64`
All electron packages go into `electron/dist/`
Many thanks to @aviraldg for the initial work on the electron integration.
Other options for running as a desktop app:
* https://github.com/krisak/vector-electron-desktop
* @asdf:matrix.org points out that you can use nativefier and it just works(tm)
@asdf:matrix.org points out that you can use nativefier and it just works(tm):
```
sudo npm install nativefier -g
nativefier https://riot.im/app/
nativefier https://vector.im/beta/
```
krisa has a dedicated electron project at https://github.com/krisak/vector-electron-desktop
(although you should swap out the 'vector' folder for the latest vector tarball you want to run.
Get a tarball from https://vector.im/packages or build your own - see Building From Source
above).
There's also a (much) older electron distribution at https://github.com/stevenhammerton/vector-desktop
Development
===========
Before attempting to develop on Riot you **must** read the developer guide
Before attempting to develop on Vector you **must** read the developer guide
for `matrix-react-sdk` at https://github.com/matrix-org/matrix-react-sdk, which
also defines the design, architecture and style for Riot too.
also defines the design, architecture and style for Vector too.
The idea of Riot is to be a relatively lightweight "skin" of customisations on
The idea of Vector is to be a relatively lightweight "skin" of customisations on
top of the underlying `matrix-react-sdk`. `matrix-react-sdk` provides both the
higher and lower level React components useful for building Matrix communication
apps using React.
@@ -192,26 +136,26 @@ After creating a new component you must run `npm run reskindex` to regenerate
the `component-index.js` for the app (used in future for skinning)
**However, as of July 2016 this layering abstraction is broken due to rapid
development on Riot forcing `matrix-react-sdk` to move fast at the expense of
maintaining a clear abstraction between the two.** Hacking on Riot inevitably
development on Vector forcing `matrix-react-sdk` to move fast at the expense of
maintaining a clear abstraction between the two.** Hacking on Vector inevitably
means hacking equally on `matrix-react-sdk`, and there are bits of
`matrix-react-sdk` behaviour incorrectly residing in the `riot-web` project
(e.g. matrix-react-sdk specific CSS), and a bunch of Riot specific behaviour
in the `matrix-react-sdk` (grep for `vector` / `riot`). This separation problem will be
solved asap once development on Riot (and thus matrix-react-sdk) has
`matrix-react-sdk` behaviour incorrectly residing in the `vector-web` project
(e.g. matrix-react-sdk specific CSS), and a bunch of Vector specific behaviour
in the `matrix-react-sdk` (grep for Vector). This separation problem will be
solved asap once development on Vector (and thus matrix-react-sdk) has
stabilised. Until then, the two projects should basically be considered as a
single unit. In particular, `matrix-react-sdk` issues are currently filed
against `riot-web` in github.
against `vector-web` in github.
Please note that Riot is intended to run correctly without access to the public
Please note that Vector is intended to run correctly without access to the public
internet. So please don't depend on resources (JS libs, CSS, images, fonts)
hosted by external CDNs or servers but instead please package all dependencies
into Riot itself.
into Vector itself.
Setting up a dev environment
============================
Much of the functionality in Riot is actually in the `matrix-react-sdk` and
Much of the functionality in Vector is actually in the `matrix-react-sdk` and
`matrix-js-sdk` modules. It is possible to set these up in a way that makes it
easy to track the `develop` branches in git and to make local changes without
having to manually rebuild each time.
@@ -234,10 +178,10 @@ Then similarly with `matrix-react-sdk`:
1. `rm -r node_modules/matrix-js-sdk; ln -s ../../matrix-js-sdk node_modules/`
1. `popd`
Finally, build and start Riot itself:
Finally, build and start vector itself:
1. `git clone git@github.com:vector-im/riot-web.git`
1. `cd riot-web`
1. `git clone git@github.com:vector-im/vector-web.git`
1. `cd vector-web`
1. `git checkout develop`
1. `npm install`
1. `rm -r node_modules/matrix-js-sdk; ln -s ../../matrix-js-sdk node_modules/`
@@ -259,19 +203,25 @@ Finally, build and start Riot itself:
Remember, the command will not terminate since it runs the web server
and rebuilds source files when they change. This development server also
disables caching, so do NOT use it in production.
1. Open http://127.0.0.1:8080/ in your browser to see your newly built Riot.
1. Open http://127.0.0.1:8080/ in your browser to see your newly built Vector.
When you make changes to `matrix-react-sdk` or `matrix-js-sdk`, you will need
to run `npm run build` in the relevant directory. You can do this automatically
by instead running `npm start` in the directory, to start a development builder
which will watch for changes to the files and rebuild automatically.
When you make changes to `matrix-react-sdk`, you will need to run `npm run
build` in the relevant directory. You can do this automatically by instead
running `npm start` in the directory, to start a development builder which
will watch for changes to the files and rebuild automatically.
If you add or remove any components from the Riot skin, you will need to rebuild
If you add or remove any components from the Vector skin, you will need to rebuild
the skin's index by running, `npm run reskindex`.
If any of these steps error with, `file table overflow`, you are probably on a mac
which has a very low limit on max open files. Run `ulimit -Sn 1024` and try again.
You'll need to do this in each new terminal you open before building Riot.
You'll need to do this in each new terminal you open before building vector.
Filing issues
=============
All issues for Vector-web and Matrix-react-sdk should be filed at
https://github.com/matrix-org/matrix-react-sdk/issues
Triaging issues
===============
@@ -279,28 +229,41 @@ Triaging issues
Issues will be triaged by the core team using the following primary set of tags:
priority:
* P1: top priority; typically blocks releases
* P2: still need to fix, but lower than P1
* P3: non-urgent
* P4: intereseting idea - bluesky some day
* P5: recorded for posterity/to avoid duplicates. No intention to resolves right now.
P1: top priority; typically blocks releases.
P2: one below that
P3: non-urgent
P4/P5: bluesky some day, who knows.
bug or feature:
bug severity:
* cosmetic - feature works functionally but UI/UX is broken.
* critical - whole app doesn't work
* major - entire feature doesn't work
* minor - partially broken feature (but still usable)
* bug
* feature
* release blocker
bug severity:
* cosmetic - feature works functionally but UI/UX is broken
* critical - whole app doesn't work
* major - entire feature doesn't work
* minor - partially broken feature (but still usable)
* ui/ux (think of this as cosmetic)
additional categories:
* network (specific to network conditions)
* platform (platform specific)
Enabling encryption
===================
End-to-end encryption in Vector and Matrix is not yet considered ready for
day-to-day use; it is experimental and should be considered only as a
proof-of-concept. See https://matrix.org/jira/browse/SPEC-162 for an overview
of the current progress.
To enable the (very experimental) support, check the 'End-to-End Encryption'
box in the 'Labs' section of the user settings (note that the labs are disabled
on http://vector.im/beta: you will need to use http://vector.im/develop or your
own deployment of vector). The Room Settings dialog will then show an
'Encryption' setting; rooms for which you are an administrator will offer you
the option of enabling encryption. Any messages sent in that room will then be
encrypted.
Note that historical encrypted messages cannot currently be decoded - history
is therefore lost when the page is reloaded.
* release blocker
* ui/ux (think of this as cosmetic)
* network (specific to network conditions)
* platform (platform specific)

View File

@@ -1,14 +0,0 @@
{
"default_hs_url": "https://matrix.org",
"default_is_url": "https://vector.im",
"brand": "Riot",
"integrations_ui_url": "https://scalar.vector.im/",
"integrations_rest_url": "https://scalar.vector.im/api",
"bug_report_endpoint_url": "https://vector.im/bugs",
"enableLabs": true,
"roomDirectory": {
"servers": [
"matrix.org"
]
}
}

View File

@@ -1,30 +1,26 @@
#!/usr/bin/env python
#
# auto-deploy script for https://riot.im/develop
#
# Listens for HTTP hits. When it gets one, downloads the artifact from jenkins
# and deploys it as the new version.
#
# Requires the following python packages:
#
# - requests
# - flask
#
from __future__ import print_function
import json, requests, tarfile, argparse, os, errno
import time
from urlparse import urljoin
from flask import Flask, jsonify, request, abort
from deploy import Deployer, DeployException
app = Flask(__name__)
arg_jenkins_url = None
deployer = None
arg_extract_path = None
arg_symlink = None
arg_jenkins_url, arg_extract_path, arg_should_clean, arg_symlink, arg_config_location = (
None, None, None, None, None
)
def download_file(url):
local_filename = url.split('/')[-1]
r = requests.get(url, stream=True)
with open(local_filename, 'wb') as f:
for chunk in r.iter_content(chunk_size=1024):
if chunk: # filter out keep-alive new chunks
f.write(chunk)
return local_filename
def untar_to(tarball, dest):
with tarfile.open(tarball) as tar:
tar.extractall(dest)
def create_symlink(source, linkname):
try:
@@ -61,9 +57,6 @@ def on_receive_jenkins_poke():
abort(400, "Missing or bad build number")
return
return fetch_jenkins_build(job_name, build_num)
def fetch_jenkins_build(job_name, build_num):
artifact_url = urljoin(
arg_jenkins_url, "job/%s/%s/api/json" % (job_name, build_num)
)
@@ -113,45 +106,31 @@ def fetch_jenkins_build(job_name, build_num):
arg_jenkins_url, "job/%s/%s/artifact/%s" % (job_name, build_num, tar_gz_path)
)
# we extract into a directory based on the build number. This avoids the
# problem of multiple builds building the same git version and thus having
# the same tarball name. That would lead to two potential problems:
# (a) sometimes jenkins serves corrupted artifacts; we would replace
# a good deploy with a bad one
# (b) we'll be overwriting the live deployment, which means people might
# see half-written files.
build_dir = os.path.join(arg_extract_path, "%s-#%s" % (job_name, build_num))
try:
extracted_dir = deploy_tarball(tar_gz_url, build_dir)
except DeployException as e:
abort(400, e.message)
print("Retrieving .tar.gz file: %s" % tar_gz_url)
filename = download_file(tar_gz_url)
print("Downloaded file: %s" % filename)
name_str = filename.replace(".tar.gz", "")
untar_location = os.path.join(arg_extract_path, name_str)
untar_to(filename, untar_location)
create_symlink(source=extracted_dir, linkname=arg_symlink)
if arg_should_clean:
os.remove(filename)
# stamp the version somewhere JS can get to it
with open(os.path.join(untar_location, "vector/version"), "w") as stamp_file:
stamp_file.write(name_str)
create_symlink(source=os.path.join(untar_location, "vector"), linkname=arg_symlink)
if arg_config_location:
create_symlink(source=arg_config_location, linkname=os.path.join(untar_location, "vector", 'config.json'))
return jsonify({})
def deploy_tarball(tar_gz_url, build_dir):
"""Download a tarball from jenkins and unpack it
Returns:
(str) the path to the unpacked deployment
"""
if os.path.exists(build_dir):
raise DeployException(
"Not deploying. We have previously deployed this build."
)
os.mkdir(build_dir)
# we rely on the fact that flask only serves one request at a time to
# ensure that we do not overwrite a tarball from a concurrent request.
return deployer.deploy(tar_gz_url, build_dir)
if __name__ == "__main__":
parser = argparse.ArgumentParser("Runs a Vector redeployment server.")
parser.add_argument(
"-j", "--jenkins", dest="jenkins", default="https://matrix.org/jenkins/", help=(
"-j", "--jenkins", dest="jenkins", default="http://matrix.org/jenkins/", help=(
"The base URL of the Jenkins web server. This will be hit to get the\
built artifacts (the .gz file) for redeploying."
)
@@ -166,13 +145,6 @@ if __name__ == "__main__":
"The location to extract .tar.gz files to."
)
)
parser.add_argument(
"-b", "--bundles-dir", dest="bundles_dir", help=(
"A directory to move the contents of the 'bundles' directory to. A \
symlink to the bundles directory will also be written inside the \
extracted tarball. Example: './bundles'."
)
)
parser.add_argument(
"-c", "--clean", dest="clean", action="store_true", default=False, help=(
"Remove .tar.gz files after they have been downloaded and extracted."
@@ -191,47 +163,18 @@ if __name__ == "__main__":
To this location."
)
)
parser.add_argument(
"--test", dest="tarball_uri", help=(
"Don't start an HTTP listener. Instead download a build from Jenkins \
immediately."
),
)
args = parser.parse_args()
if args.jenkins.endswith("/"): # important for urljoin
arg_jenkins_url = args.jenkins
else:
arg_jenkins_url = args.jenkins + "/"
arg_extract_path = args.extract
arg_should_clean = args.clean
arg_symlink = args.symlink
if not os.path.isdir(arg_extract_path):
os.mkdir(arg_extract_path)
deployer = Deployer()
deployer.bundles_path = args.bundles_dir
deployer.should_clean = args.clean
deployer.config_location = args.config
# we don't pgp-sign jenkins artifacts; instead we rely on HTTPS access to
# the jenkins server (and the jenkins server not being compromised and/or
# github not serving it compromised source). If that's not good enough for
# you, don't use riot.im/develop.
deployer.verify_signature = False
if args.tarball_uri is not None:
build_dir = os.path.join(arg_extract_path, "test-%i" % (time.time()))
deploy_tarball(args.tarball_uri, build_dir)
else:
print(
"Listening on port %s. Extracting to %s%s. Symlinking to %s. Jenkins URL: %s. Config location: %s" %
(args.port,
arg_extract_path,
" (clean after)" if deployer.should_clean else "",
arg_symlink,
arg_jenkins_url,
deployer.config_location,
)
)
app.run(host="0.0.0.0", port=args.port, debug=True)
arg_config_location = args.config
print(
"Listening on port %s. Extracting to %s%s. Symlinking to %s. Jenkins URL: %s. Config location: %s" %
(args.port, arg_extract_path,
" (clean after)" if arg_should_clean else "", arg_symlink, arg_jenkins_url, arg_config_location)
)
app.run(host="0.0.0.0", port=args.port, debug=True)

View File

@@ -1,25 +0,0 @@
Theming Riot
============
Themes are a very basic way of providing simple alternative look & feels to the
riot-web app via CSS & custom imagery.
They are *NOT* co be confused with 'skins', which describe apps which sit on top
of matrix-react-sdk - e.g. in theory Riot itself is a react-sdk skin.
As of Jan 2017, skins are not fully supported; riot is the only available skin.
To define a theme for Riot:
1. Pick a name, e.g. `teal`. at time of writing we have `light` and `dark`.
2. Fork `src/skins/vector/css/themes/dark.scss` to be teal.scss
3. Fork `src/skins/vector/css/themes/_base.scss` to be _teal.scss
4. Override variables in _teal.scss as desired. You may wish to delete ones
which don't differ from _base.scss, to make it clear which are being
overridden. If every single colour is being changed (as per _dark.scss)
then you might as well keep them all.
5. Add the theme to the list of entrypoints in webpack.config.js
6. Add the theme to the list of themes in matrix-react-sdk's UserSettings.js
7. Sit back and admire your handywork.
In future, the assets for a theme will probably be gathered together into a
single directory tree.

Binary file not shown.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 102 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 673 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 21 KiB

View File

@@ -1,4 +0,0 @@
This directory contains the config file for the official riot.im distribution
of Riot Desktop. You probably do not want to build with this config unless
you're building the official riot.im distribution, or you'll find your builds
will replace themselves with the riot.im build.

View File

@@ -1,251 +0,0 @@
// @flow
/*
Copyright 2016 Aviral Dasgupta
Copyright 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.
*/
// Squirrel on windows starts the app with various flags
// as hooks to tell us when we've been installed/uninstalled
// etc.
const check_squirrel_hooks = require('./squirrelhooks');
if (check_squirrel_hooks()) return;
const electron = require('electron');
const url = require('url');
const tray = require('./tray');
const VectorMenu = require('./vectormenu');
let vectorConfig = {};
try {
vectorConfig = require('../../webapp/config.json');
} catch (e) {
// it would be nice to check the error code here and bail if the config
// is unparseable, but we get MODULE_NOT_FOUND in the case of a missing
// file or invalid json, so node is just very unhelpful.
// Continue with the defaults (ie. an empty config)
}
const PERMITTED_URL_SCHEMES = [
'http:',
'https:',
'mailto:',
];
const UPDATE_POLL_INTERVAL_MS = 60 * 60 * 1000;
const INITIAL_UPDATE_DELAY_MS = 30 * 1000;
let mainWindow = null;
let appQuitting = false;
function safeOpenURL(target) {
// openExternal passes the target to open/start/xdg-open,
// so put fairly stringent limits on what can be opened
// (for instance, open /bin/sh does indeed open a terminal
// with a shell, albeit with no arguments)
const parsed_url = url.parse(target);
if (PERMITTED_URL_SCHEMES.indexOf(parsed_url.protocol) > -1) {
// explicitly use the URL re-assembled by the url library,
// so we know the url parser has understood all the parts
// of the input string
const new_target = url.format(parsed_url);
electron.shell.openExternal(new_target);
}
}
function onWindowOrNavigate(ev, target) {
// always prevent the default: if something goes wrong,
// we don't want to end up opening it in the electron
// app, as we could end up opening any sort of random
// url in a window that has node scripting access.
ev.preventDefault();
safeOpenURL(target);
}
function onLinkContextMenu(ev, params) {
const popup_menu = new electron.Menu();
popup_menu.append(new electron.MenuItem({
label: params.linkURL,
click() {
safeOpenURL(params.linkURL);
},
}));
popup_menu.popup();
ev.preventDefault();
}
function installUpdate() {
// for some reason, quitAndInstall does not fire the
// before-quit event, so we need to set the flag here.
appQuitting = true;
electron.autoUpdater.quitAndInstall();
}
function pollForUpdates() {
try {
electron.autoUpdater.checkForUpdates();
} catch (e) {
console.log("Couldn't check for update", e);
}
}
function startAutoUpdate(update_base_url) {
if (update_base_url.slice(-1) !== '/') {
update_base_url = update_base_url + '/';
}
try {
// For reasons best known to Squirrel, the way it checks for updates
// is completely different between macOS and windows. On macOS, it
// hits a URL that either gives it a 200 with some json or
// 204 No Content. On windows it takes a base path and looks for
// files under that path.
if (process.platform == 'darwin') {
// include the current version in the URL we hit. Electron doesn't add
// it anywhere (apart from the User-Agent) so it's up to us. We could
// (and previously did) just use the User-Agent, but this doesn't
// rely on NSURLConnection setting the User-Agent to what we expect,
// and also acts as a convenient cache-buster to ensure that when the
// app updates it always gets a fresh value to avoid update-looping.
electron.autoUpdater.setFeedURL(
update_base_url +
'macos/?localVersion=' + encodeURIComponent(electron.app.getVersion())
);
} else if (process.platform == 'win32') {
electron.autoUpdater.setFeedURL(update_base_url + 'win32/' + process.arch + '/');
} else {
// Squirrel / electron only supports auto-update on these two platforms.
// I'm not even going to try to guess which feed style they'd use if they
// implemented it on Linux, or if it would be different again.
console.log("Auto update not supported on this platform");
}
// We check for updates ourselves rather than using 'updater' because we need to
// do it in the main process (and we don't really need to check every 10 minutes:
// every hour should be just fine for a desktop app)
// However, we still let the main window listen for the update events.
// We also wait a short time before checking for updates the first time because
// of squirrel on windows and it taking a small amount of time to release a
// lock file.
setTimeout(pollForUpdates, INITIAL_UPDATE_DELAY_MS);
setInterval(pollForUpdates, UPDATE_POLL_INTERVAL_MS);
} catch (err) {
// will fail if running in debug mode
console.log("Couldn't enable update checking", err);
}
}
// handle uncaught errors otherwise it displays
// stack traces in popup dialogs, which is terrible (which
// it will do any time the auto update poke fails, and there's
// no other way to catch this error).
// Assuming we generally run from the console when developing,
// this is far preferable.
process.on('uncaughtException', function (error) {
console.log("Unhandled exception", error);
});
electron.ipcMain.on('install_update', installUpdate);
electron.app.commandLine.appendSwitch('--enable-usermedia-screen-capturing');
const shouldQuit = electron.app.makeSingleInstance((commandLine, workingDirectory) => {
// Someone tried to run a second instance, we should focus our window.
if (mainWindow) {
if (!mainWindow.isVisible()) mainWindow.show();
if (mainWindow.isMinimized()) mainWindow.restore();
mainWindow.focus();
}
});
if (shouldQuit) {
console.log("Other instance detected: exiting");
electron.app.quit()
}
electron.app.on('ready', () => {
if (vectorConfig.update_base_url) {
console.log("Starting auto update with base URL: " + vectorConfig.update_base_url);
startAutoUpdate(vectorConfig.update_base_url);
} else {
console.log("No update_base_url is defined: auto update is disabled");
}
const icon_path = `${__dirname}/../img/riot.` + (
process.platform == 'win32' ? 'ico' : 'png'
);
mainWindow = new electron.BrowserWindow({
icon: icon_path,
width: 1024, height: 768,
show: false,
autoHideMenuBar: true,
});
mainWindow.loadURL(`file://${__dirname}/../../webapp/index.html`);
electron.Menu.setApplicationMenu(VectorMenu);
// Create trayIcon icon
tray.create(mainWindow, {
icon_path: icon_path,
brand: vectorConfig.brand || 'Riot'
});
if (!process.argv.includes('--hidden')) {
mainWindow.once('ready-to-show', () => {
mainWindow.show();
});
}
mainWindow.on('closed', () => {
mainWindow = null;
});
mainWindow.on('close', (e) => {
if (!appQuitting && (tray.hasTray() || process.platform == 'darwin')) {
// On Mac, closing the window just hides it
// (this is generally how single-window Mac apps
// behave, eg. Mail.app)
e.preventDefault();
mainWindow.hide();
return false;
}
});
mainWindow.webContents.on('new-window', onWindowOrNavigate);
mainWindow.webContents.on('will-navigate', onWindowOrNavigate);
mainWindow.webContents.on('context-menu', function(ev, params) {
if (params.linkURL) {
onLinkContextMenu(ev, params);
}
});
});
electron.app.on('window-all-closed', () => {
electron.app.quit();
});
electron.app.on('activate', () => {
mainWindow.show();
});
electron.app.on('before-quit', () => {
appQuitting = true;
});
// Set the App User Model ID to match what the squirrel
// installer uses for the shortcut icon.
// This makes notifications work on windows 8.1 (and is
// a noop on other platforms).
electron.app.setAppUserModelId('com.squirrel.riot-web.Riot');

View File

@@ -1,51 +0,0 @@
/*
Copyright 2017 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.
*/
const path = require('path');
const spawn = require('child_process').spawn;
const app = require('electron').app;
function run_update_exe(args, done) {
// Invokes Squirrel's Update.exe which will do things for us like create shortcuts
// Note that there's an Update.exe in the app-x.x.x directory and one in the parent
// directory: we need to run the one in the parent directory, because it discovers
// information about the app by inspecting the directory it's run from.
const updateExe = path.resolve(path.dirname(process.execPath), '..', 'Update.exe');
console.log('Spawning `%s` with args `%s`', updateExe, args);
spawn(updateExe, args, {
detached: true
}).on('close', done);
};
function check_squirrel_hooks() {
if (process.platform != 'win32') return false;
const cmd = process.argv[1];
const target = path.basename(process.execPath);
if (cmd === '--squirrel-install' || cmd === '--squirrel-updated') {
run_update_exe(['--createShortcut=' + target + ''], app.quit);
return true;
} else if (cmd === '--squirrel-uninstall') {
run_update_exe(['--removeShortcut=' + target + ''], app.quit);
return true;
} else if (cmd === '--squirrel-obsolete') {
app.quit();
return true;
}
return false;
}
module.exports = check_squirrel_hooks;

View File

@@ -1,67 +0,0 @@
/*
Copyright 2017 Karl Glatz <karl@glatz.biz>
Copyright 2017 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.
*/
const path = require('path');
const electron = require('electron');
const app = electron.app;
const Tray = electron.Tray;
const MenuItem = electron.MenuItem;
let trayIcon = null;
exports.hasTray = function hasTray() {
return (trayIcon !== null);
}
exports.create = function (win, config) {
// no trays on darwin
if (process.platform === 'darwin' || trayIcon) {
return;
}
const toggleWin = function () {
if (win.isVisible() && !win.isMinimized()) {
win.hide();
} else {
if (win.isMinimized()) win.restore();
if (!win.isVisible()) win.show();
win.focus();
}
};
const contextMenu = electron.Menu.buildFromTemplate([
{
label: 'Show/Hide ' + config.brand,
click: toggleWin
},
{
type: 'separator'
},
{
label: 'Quit',
click: function () {
app.quit();
}
}
]);
trayIcon = new Tray(config.icon_path);
trayIcon.setToolTip(config.brand);
trayIcon.setContextMenu(contextMenu);
trayIcon.on('click', toggleWin);
};

View File

@@ -1,197 +0,0 @@
/*
Copyright 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.
*/
const electron = require('electron');
// Menu template from http://electron.atom.io/docs/api/menu/, edited
const template = [
{
label: 'Edit',
submenu: [
{
role: 'undo'
},
{
role: 'redo'
},
{
type: 'separator'
},
{
role: 'cut'
},
{
role: 'copy'
},
{
role: 'paste'
},
{
role: 'pasteandmatchstyle'
},
{
role: 'delete'
},
{
role: 'selectall'
}
]
},
{
label: 'View',
submenu: [
{
type: 'separator'
},
{
role: 'resetzoom'
},
{
role: 'zoomin'
},
{
role: 'zoomout'
},
{
type: 'separator'
},
{
role: 'togglefullscreen'
},
{
role: 'toggledevtools'
}
]
},
{
role: 'window',
submenu: [
{
role: 'minimize'
},
{
role: 'close'
}
]
},
{
role: 'help',
submenu: [
{
label: 'riot.im',
click () { electron.shell.openExternal('https://riot.im/') }
}
]
}
];
// macOS has specific menu conventions...
if (process.platform === 'darwin') {
// first macOS menu is the name of the app
const name = electron.app.getName()
template.unshift({
label: name,
submenu: [
{
role: 'about'
},
{
type: 'separator'
},
{
role: 'services',
submenu: []
},
{
type: 'separator'
},
{
role: 'hide'
},
{
role: 'hideothers'
},
{
role: 'unhide'
},
{
type: 'separator'
},
{
role: 'quit'
}
]
})
// Edit menu.
// This has a 'speech' section on macOS
template[1].submenu.push(
{
type: 'separator'
},
{
label: 'Speech',
submenu: [
{
role: 'startspeaking'
},
{
role: 'stopspeaking'
}
]
}
)
// Window menu.
// This also has specific functionality on macOS
template[3].submenu = [
{
label: 'Close',
accelerator: 'CmdOrCtrl+W',
role: 'close'
},
{
label: 'Minimize',
accelerator: 'CmdOrCtrl+M',
role: 'minimize'
},
{
label: 'Zoom',
role: 'zoom'
},
{
type: 'separator'
},
{
label: 'Bring All to Front',
role: 'front'
}
]
} else {
template.unshift({
label: 'File',
submenu: [
// For some reason, 'about' does not seem to work on windows.
/*{
role: 'about'
},*/
{
role: 'quit'
}
]
});
}
module.exports = electron.Menu.buildFromTemplate(template)

View File

@@ -2,7 +2,7 @@
set -e
export NVM_DIR="$HOME/.nvm"
export NVM_DIR="/home/jenkins/.nvm"
[ -s "$NVM_DIR/nvm.sh" ] && . "$NVM_DIR/nvm.sh"
nvm use 6
@@ -11,30 +11,25 @@ set -x
npm install
# apparently npm 3.10.3 on node 6.4.0 doesn't upgrade #develop target with npm install unless explicitly asked.
npm install matrix-react-sdk matrix-js-sdk olm
npm install matrix-react-sdk matrix-js-sdk
# install olm. A naive 'npm i ./olm/olm-*.tgz' fails because it uses the url
# from our package.json (or even matrix-js-sdk's) in preference.
#
# disabled for now, to avoid the annoying scenario of a release doing something
# different to /develop. Instead, add it to the 'npm install' list above.
# -- rav 2016/02/03
#tar -C olm -xz < olm/olm-*.tgz
#rm -r node_modules/olm
#cp -r olm/package node_modules/olm
tar -C olm -xz < olm/olm-*.tgz
rm -r node_modules/olm
cp -r olm/package node_modules/olm
# we may be using dev branches of js-sdk and react-sdk, in which case we need to build them
(cd node_modules/matrix-js-sdk && npm install)
(cd node_modules/matrix-react-sdk && npm install)
# we may be using a dev branch of react-sdk, in which case we need to build it
(cd node_modules/matrix-react-sdk && npm run build)
# run the mocha tests
npm run test
# run eslint
npm run lintall -- -f checkstyle -o eslint.xml || true
# build our artifacts; dumps them in ./vector
npm run build:dev
rm dist/vector-*.tar.gz || true # rm previous artifacts without failing if it doesn't exist
# gzip up ./vector
rm vector-*.tar.gz || true # rm previous artifacts without failing if it doesn't exist
# node_modules deps from 'npm install' don't have a .git dir so can't
# rev-parse; but they do set the commit in package.json under 'gitHead' which
@@ -44,4 +39,4 @@ JSSDK_SHA=$(grep 'gitHead' node_modules/matrix-js-sdk/package.json | cut -d \" -
VECTOR_SHA=$(git rev-parse --short=12 HEAD) # use the ACTUAL SHA rather than assume develop
DIST_VERSION=$VECTOR_SHA-react-$REACT_SHA-js-$JSSDK_SHA scripts/package.sh -d
tar -zcvhf vector-$VECTOR_SHA-react-$REACT_SHA-js-$JSSDK_SHA.tar.gz vector #g[z]ip, [c]reate archive, [v]erbose, [f]ilename, [h]ard-dereference (do not archive symlinks)

View File

@@ -2,14 +2,13 @@
var path = require('path');
var webpack = require('webpack');
var webpack_config = require('./webpack.config');
/*
* We use webpack to build our tests. It's a pain to have to wait for webpack
* to build everything; however it's the easiest way to load our dependencies
* from node_modules.
*
* If you run karma in multi-run mode (with `npm run test-multi`), it will watch
* If you run karma in multi-run mode (with `npm run test:multi`), it will watch
* the tests for changes, and webpack will rebuild using a cache. This is much quicker
* than a clean rebuild.
*/
@@ -20,41 +19,8 @@ var testFile = process.env.KARMA_TEST_FILE || 'test/all-tests.js';
process.env.PHANTOMJS_BIN = 'node_modules/.bin/phantomjs';
process.env.Q_DEBUG = 1;
/* the webpack config is based on the real one, to (a) try to simulate the
* deployed environment as closely as possible, and (b) to avoid a shedload of
* cut-and-paste.
*/
// find out if we're shipping olm, and where it is, if so.
const olm_entry = webpack_config.entry['olm'];
// remove the default entries - karma provides its own (via the 'files' and
// 'preprocessors' config below)
delete webpack_config['entry'];
// add ./test as a search path for js
webpack_config.module.loaders.unshift({
test: /\.js$/, loader: "babel",
include: [path.resolve('./src'), path.resolve('./test')],
});
// disable parsing for sinon, because it
// tries to do voodoo with 'require' which upsets
// webpack (https://github.com/webpack/webpack/issues/304)
webpack_config.module.noParse.push(/sinon\/pkg\/sinon\.js$/);
// ?
webpack_config.resolve.alias['sinon'] = 'sinon/pkg/sinon.js';
webpack_config.resolve.root = [
path.resolve('./src'),
path.resolve('./test'),
];
webpack_config.devtool = 'inline-source-map';
module.exports = function (config) {
const myconfig = {
config.set({
// frameworks to use
// available frameworks: https://npmjs.org/browse/keyword/karma-adapter
frameworks: ['mocha'],
@@ -63,29 +29,19 @@ module.exports = function (config) {
files: [
'node_modules/babel-polyfill/browser.js',
testFile,
// make the images available via our httpd. They will be avaliable
// below http://localhost:[PORT]/base/. See also `proxies` which
// defines alternative URLs for them.
//
// This isn't required by any of the tests, but it stops karma
// logging warnings when it serves a 404 for them.
{
pattern: 'src/skins/vector/img/*',
watched: false, included: false, served: true, nocache: false,
},
{pattern: 'vector/img/*', watched: false, included: false, served: true, nocache: false},
],
// redirect img links to the karma server
proxies: {
// redirect img links to the karma server. See above.
"/img/": "/base/src/skins/vector/img/",
"/img/": "/base/vector/img/",
},
// preprocess matching files before serving them to the browser
// available preprocessors:
// https://npmjs.org/browse/keyword/karma-preprocessor
preprocessors: {
'{src,test}/**/*.js': ['webpack'],
'test/**/*.js': ['webpack', 'sourcemap']
},
// test results reporter to use
@@ -128,20 +84,59 @@ module.exports = function (config) {
outputDir: 'karma-reports',
},
webpack: webpack_config,
webpack: {
module: {
loaders: [
{ test: /\.json$/, loader: "json" },
{
test: /\.js$/, loader: "babel",
include: [path.resolve('./src'),
path.resolve('./test'),
],
query: {
// we're using babel 5, for consistency with
// the release build, which doesn't use the
// presets.
// presets: ['react', 'es2015'],
},
},
],
noParse: [
// don't parse the languages within highlight.js. They
// cause stack overflows
// (https://github.com/webpack/webpack/issues/1721), and
// there is no need for webpack to parse them - they can
// just be included as-is.
/highlight\.js\/lib\/languages/,
webpackMiddleware: {
stats: {
// don't fill the console up with a mahoosive list of modules
chunks: false,
// also disable parsing for sinon, because it
// tries to do voodoo with 'require' which upsets
// webpack (https://github.com/webpack/webpack/issues/304)
/sinon\/pkg\/sinon\.js$/,
],
},
resolve: {
alias: {
// alias any requires to the react module to the one in our path, otherwise
// we tend to get the react source included twice when using npm link.
react: path.resolve('./node_modules/react'),
// same goes for js-sdk
"matrix-js-sdk": path.resolve('./node_modules/matrix-js-sdk'),
sinon: 'sinon/pkg/sinon.js',
},
root: [
path.resolve('./src'),
path.resolve('./test'),
],
},
plugins: [
// olm may not be installed, so avoid webpack warnings by
// ignoring it.
new webpack.IgnorePlugin(/^olm$/),
],
devtool: 'inline-source-map',
},
};
// include the olm loader if we have it.
if (olm_entry) {
myconfig.files.unshift(olm_entry);
}
config.set(myconfig);
});
};

View File

@@ -1,82 +1,66 @@
{
"name": "riot-web",
"productName": "Riot",
"main": "electron/src/electron-main.js",
"version": "0.9.8",
"description": "A feature-rich client for Matrix.org",
"author": "Vector Creations Ltd.",
"name": "vector-web",
"version": "0.8.3",
"description": "Vector webapp",
"author": "matrix.org",
"repository": {
"type": "git",
"url": "https://github.com/vector-im/riot-web"
"url": "https://github.com/vector-im/vector-web"
},
"license": "Apache-2.0",
"files": [
"AUTHORS.rst",
"CONTRIBUTING.rst",
"deploy",
"docs",
"karma.conf.js",
"lib",
"release.sh",
"scripts",
"src",
"test",
"webpack.config.js"
],
"style": "bundle.css",
"matrix-react-parent": "matrix-react-sdk",
"scripts": {
"reskindex": "reskindex -h src/header",
"build:res": "node scripts/copy-res.js",
"build:emojione": "cpx \"node_modules/emojione/assets/svg/*\" vector/emojione/svg/",
"build:modernizr": "modernizr -c .modernizr.json -d src/vector/modernizr.js",
"build:css": "catw \"src/skins/vector/css/**/*.css\" -o vector/components.css --no-watch",
"build:compile": "babel --source-maps -d lib src",
"build:bundle": "cross-env NODE_ENV=production webpack -p --progress",
"build:bundle:dev": "webpack --optimize-occurence-order --progress",
"build:electron": "npm run clean && npm run build && build -wml --ia32 --x64",
"build": "npm run build:res && npm run build:bundle",
"build:dev": "npm run build:res && npm run build:bundle:dev",
"dist": "scripts/package.sh",
"start:res": "node scripts/copy-res.js -w",
"start:js": "webpack-dev-server --output-filename=bundles/_dev_/[name].js --output-chunk-file=bundles/_dev_/[name].js -w --progress",
"start:js:prod": "cross-env NODE_ENV=production webpack-dev-server -w --progress",
"start": "parallelshell \"npm run start:res\" \"npm run start:js\"",
"start:prod": "parallelshell \"npm run start:res\" \"npm run start:js:prod\"",
"lint": "eslint src/",
"lintall": "eslint src/ test/",
"clean": "rimraf lib webapp electron/dist",
"prepublish": "npm run build:compile",
"build:bundle": "NODE_ENV=production webpack -p lib/vector/index.js vector/bundle.js",
"build:bundle:dev": "webpack --optimize-occurence-order lib/vector/index.js vector/bundle.js",
"build:staticfiles": "cpx -v node_modules/olm/olm.js vector/",
"build": "npm run build:staticfiles && npm run build:emojione && npm run build:css && npm run build:compile && npm run build:bundle",
"build:dev": "npm run build:staticfiles && npm run build:emojione && npm run build:css && npm run build:compile && npm run build:bundle:dev",
"package": "scripts/package.sh",
"start:emojione": "cpx \"node_modules/emojione/assets/svg/*\" vector/emojione/svg/ -w",
"start:js": "webpack -w src/vector/index.js vector/bundle.js",
"start:js:prod": "NODE_ENV=production webpack -w src/vector/index.js vector/bundle.js",
"start:skins:css": "catw \"src/skins/vector/css/**/*.css\" -o vector/components.css",
"start:staticfiles": "cpx -Lwv node_modules/olm/olm.js vector/",
"//cache": "Note the -c 1 below due to https://code.google.com/p/chromium/issues/detail?id=508270",
"start": "parallelshell \"npm run start:staticfiles\" \"npm run start:emojione\" \"npm run start:js\" \"npm run start:skins:css\" \"http-server -c 1 vector\"",
"start:prod": "parallelshell \"npm run start:staticfiles\" \"npm run start:emojione\" \"npm run start:js:prod\" \"npm run start:skins:css\" \"http-server -c 1 vector\"",
"clean": "rimraf lib vector/olm.js vector/bundle.css vector/bundle.js vector/bundle.js.map vector/webpack.css* vector/emojione",
"prepublish": "npm run build:css && npm run build:compile",
"test": "karma start --single-run=true --autoWatch=false --browsers PhantomJS --colors=false",
"test-multi": "karma start"
"test:multi": "karma start"
},
"dependencies": {
"babel-polyfill": "^6.5.0",
"babel-runtime": "^6.11.6",
"browser-request": "^0.3.3",
"classnames": "^2.1.2",
"draft-js": "^0.8.1",
"extract-text-webpack-plugin": "^0.9.1",
"favico.js": "^0.3.10",
"filesize": "3.5.6",
"filesize": "^3.1.2",
"flux": "~2.0.3",
"gemini-scrollbar": "matrix-org/gemini-scrollbar#b302279",
"gfm.css": "^1.1.1",
"highlight.js": "^9.0.0",
"linkifyjs": "^2.1.3",
"matrix-js-sdk": "matrix-org/matrix-js-sdk#develop",
"matrix-react-sdk": "matrix-org/matrix-react-sdk#develop",
"modernizr": "^3.1.0",
"pako": "^1.0.5",
"q": "^1.4.1",
"react": "^15.4.0",
"react": "^15.2.1",
"react-dnd": "^2.1.4",
"react-dnd-html5-backend": "^2.1.2",
"react-dom": "^15.4.0",
"react-gemini-scrollbar": "matrix-org/react-gemini-scrollbar#39d858c",
"react-dom": "^15.2.1",
"react-gemini-scrollbar": "matrix-org/react-gemini-scrollbar#5e97aef",
"sanitize-html": "^1.11.1",
"ua-parser-js": "^0.7.10",
"url": "^0.11.0"
},
"devDependencies": {
"autoprefixer": "^6.6.0",
"babel-cli": "^6.5.2",
"babel-core": "^6.14.0",
"babel-eslint": "^6.1.0",
@@ -91,20 +75,13 @@
"babel-preset-es2017": "^6.16.0",
"babel-preset-react": "^6.16.0",
"babel-preset-stage-2": "^6.17.0",
"chokidar": "^1.6.1",
"catw": "^1.0.1",
"cpx": "^1.3.2",
"cross-env": "^4.0.0",
"css-raw-loader": "^0.1.1",
"electron-builder": "^11.2.4",
"electron-builder-squirrel-windows": "^11.2.1",
"emojione": "^2.2.7",
"eslint": "^3.14.0",
"eslint-config-google": "^0.7.1",
"eslint-plugin-flowtype": "^2.30.0",
"eslint-plugin-react": "^6.9.0",
"emojione": "^2.2.3",
"expect": "^1.16.0",
"fs-extra": "^0.30.0",
"html-webpack-plugin": "^2.24.0",
"http-server": "^0.8.4",
"json-loader": "^0.5.3",
"karma": "^0.13.22",
"karma-chrome-launcher": "^0.2.3",
@@ -112,57 +89,18 @@
"karma-junit-reporter": "^0.4.1",
"karma-mocha": "^0.2.2",
"karma-phantomjs-launcher": "^1.0.0",
"karma-sourcemap-loader": "^0.3.7",
"karma-webpack": "^1.7.0",
"minimist": "^1.2.0",
"mkdirp": "^0.5.1",
"mocha": "^2.4.5",
"parallelshell": "^1.2.0",
"phantomjs-prebuilt": "^2.1.7",
"postcss-extend": "^1.0.5",
"postcss-import": "^9.0.0",
"postcss-loader": "^1.2.2",
"postcss-mixins": "^5.4.1",
"postcss-nested": "^1.0.0",
"postcss-scss": "^0.4.0",
"postcss-simple-vars": "^3.0.0",
"postcss-strip-inline-comments": "^0.1.5",
"react-addons-perf": "^15.4.0",
"react-addons-test-utils": "^15.4.0",
"react-addons-perf": "^15.0",
"react-addons-test-utils": "^15.0.1",
"rimraf": "^2.4.3",
"source-map-loader": "^0.1.5",
"webpack": "^1.12.14",
"webpack-dev-server": "^1.16.2"
"webpack": "^1.12.14"
},
"optionalDependencies": {
"olm": "https://matrix.org/packages/npm/olm/olm-2.2.1.tgz"
},
"build": {
"appId": "im.riot.app",
"category": "Network",
"electronVersion": "1.6.2",
"//asar=false": "https://github.com/electron-userland/electron-builder/issues/675",
"asar": false,
"dereference": true,
"//files": "We bundle everything, so we only need to include webapp/",
"files": [
"electron/src/**",
"electron/img/**",
"webapp/**",
"package.json"
],
"linux": {
"target": "deb",
"maintainer": "support@riot.im",
"desktop": {
"StartupWMClass": "riot-web"
}
},
"win": {
"target": "squirrel"
}
},
"directories": {
"buildResources": "electron/build",
"output": "electron/dist"
"olm": "https://matrix.org/packages/npm/olm/olm-1.3.0.tgz"
}
}

View File

@@ -1,13 +0,0 @@
module.exports = {
plugins: [
require("postcss-import")(),
require("autoprefixer")(),
require("postcss-simple-vars")(),
require("postcss-extend")(),
require("postcss-nested")(),
require("postcss-mixins")(),
require("postcss-strip-inline-comments")(),
],
"parser": "postcss-scss",
"local-plugins": true,
};

View File

@@ -1 +0,0 @@
signing_id: packages@riot.im

Binary file not shown.

Binary file not shown.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 673 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 102 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.9 KiB

View File

@@ -1,82 +0,0 @@
#!/usr/bin/env node
// copies the resources into the webapp directory.
//
// cpx includes globbed parts of the filename in the destination, but excludes
// common parents. Hence, "res/{a,b}/**": the output will be "dest/a/..." and
// "dest/b/...".
const COPY_LIST = [
["res/{media,vector-icons}/**", "webapp"],
["src/skins/vector/{fonts,img}/**", "webapp"],
["node_modules/emojione/assets/svg/*", "webapp/emojione/svg/"],
["node_modules/emojione/assets/png/*", "webapp/emojione/png/"],
["./config.json", "webapp", {directwatch: 1}],
];
const parseArgs = require('minimist');
const Cpx = require('cpx');
const chokidar = require('chokidar');
const argv = parseArgs(
process.argv.slice(2), {}
);
var watch = argv.w;
var verbose = argv.v;
function errCheck(err) {
if (err) {
console.error(err.message);
process.exit(1);
}
}
function next(i, err) {
errCheck(err);
if (i >= COPY_LIST.length) {
return;
}
const ent = COPY_LIST[i];
const source = ent[0];
const dest = ent[1];
const opts = ent[2] || {};
const cpx = new Cpx.Cpx(source, dest);
if (verbose) {
cpx.on("copy", (event) => {
console.log(`Copied: ${event.srcPath} --> ${event.dstPath}`);
});
cpx.on("remove", (event) => {
console.log(`Removed: ${event.path}`);
});
}
const cb = (err) => {next(i+1, err)};
if (watch) {
if (opts.directwatch) {
// cpx -w creates a watcher for the parent of any files specified,
// which in the case of config.json is '.', which inevitably takes
// ages to crawl. So we create our own watcher on the files
// instead.
const copy = () => {cpx.copy(errCheck)};
chokidar.watch(source)
.on('add', copy)
.on('change', copy)
.on('ready', cb)
.on('error', errCheck);
} else {
cpx.on('watch-ready', cb);
cpx.on("watch-error", cb);
cpx.watch();
}
} else {
cpx.copy(cb);
}
}
next(0);

View File

@@ -1,183 +0,0 @@
#!/usr/bin/env python
#
# download and unpack a riot-web tarball.
#
# Allows `bundles` to be extracted to a common directory, and a link to
# config.json to be added.
from __future__ import print_function
import argparse
import os
import os.path
import subprocess
import sys
import tarfile
try:
# python3
from urllib.request import urlretrieve
except ImportError:
# python2
from urllib import urlretrieve
class DeployException(Exception):
pass
def create_relative_symlink(linkname, target):
relpath = os.path.relpath(target, os.path.dirname(linkname))
print ("Symlink %s -> %s" % (linkname, relpath))
os.symlink(relpath, linkname)
def move_bundles(source, dest):
"""Move the contents of the 'bundles' directory to a common dir
We check that we will not be overwriting anything before we proceed.
Args:
source (str): path to 'bundles' within the extracted tarball
dest (str): target common directory
"""
if not os.path.isdir(dest):
os.mkdir(dest)
# build a map from source to destination, checking for non-existence as we go.
renames = {}
for f in os.listdir(source):
dst = os.path.join(dest, f)
if os.path.exists(dst):
raise DeployException(
"Not deploying. The bundle includes '%s' which we have previously deployed."
% f
)
renames[os.path.join(source, f)] = dst
for (src, dst) in renames.iteritems():
print ("Move %s -> %s" % (src, dst))
os.rename(src, dst)
class Deployer:
def __init__(self):
self.packages_path = "."
self.bundles_path = None
self.should_clean = False
self.config_location = None
self.verify_signature = True
def deploy(self, tarball, extract_path):
"""Download a tarball if necessary, and unpack it
Returns:
(str) the path to the unpacked deployment
"""
print("Deploying %s to %s" % (tarball, extract_path))
name_str = os.path.basename(tarball).replace(".tar.gz", "")
extracted_dir = os.path.join(extract_path, name_str)
if os.path.exists(extracted_dir):
raise DeployException('Cannot unpack %s: %s already exists' % (
tarball, extracted_dir))
downloaded = False
if tarball.startswith("http://") or tarball.startswith("https://"):
tarball = self.download_and_verify(tarball)
print("Downloaded file: %s" % tarball)
downloaded = True
try:
with tarfile.open(tarball) as tar:
tar.extractall(extract_path)
finally:
if self.should_clean and downloaded:
os.remove(tarball)
print ("Extracted into: %s" % extracted_dir)
if self.config_location:
create_relative_symlink(
target=self.config_location,
linkname=os.path.join(extracted_dir, 'config.json')
)
if self.bundles_path:
extracted_bundles = os.path.join(extracted_dir, 'bundles')
move_bundles(source=extracted_bundles, dest=self.bundles_path)
# replace the (hopefully now empty) extracted_bundles dir with a
# symlink to the common dir.
os.rmdir(extracted_bundles)
create_relative_symlink(
target=self.bundles_path,
linkname=extracted_bundles,
)
return extracted_dir
def download_and_verify(self, url):
tarball = self.download_file(url)
if self.verify_signature:
sigfile = self.download_file(url + ".asc")
subprocess.check_call(["gpg", "--verify", sigfile, tarball])
return tarball
def download_file(self, url):
if not os.path.isdir(self.packages_path):
os.mkdir(self.packages_path)
local_filename = os.path.join(self.packages_path,
url.split('/')[-1])
sys.stdout.write("Downloading %s -> %s..." % (url, local_filename))
sys.stdout.flush()
urlretrieve(url, local_filename)
print ("Done")
return local_filename
if __name__ == "__main__":
parser = argparse.ArgumentParser("Deploy a Riot build on a web server.")
parser.add_argument(
"-p", "--packages-dir", default="./packages", help=(
"The directory to download the tarball into. (Default: '%(default)s')"
)
)
parser.add_argument(
"-e", "--extract-path", default="./deploys", help=(
"The location to extract .tar.gz files to. (Default: '%(default)s')"
)
)
parser.add_argument(
"-b", "--bundles-dir", nargs='?', default="./bundles", help=(
"A directory to move the contents of the 'bundles' directory to. A \
symlink to the bundles directory will also be written inside the \
extracted tarball. Example: './bundles'. \
(Default: '%(default)s')"
)
)
parser.add_argument(
"-c", "--clean", action="store_true", default=False, help=(
"Remove .tar.gz files after they have been downloaded and extracted. \
(Default: %(default)s)"
)
)
parser.add_argument(
"--config", nargs='?', default='./config.json', help=(
"Write a symlink at config.json in the extracted tarball to this \
location. (Default: '%(default)s')"
)
)
parser.add_argument(
"tarball", help=(
"filename of tarball, or URL to download."
),
)
args = parser.parse_args()
deployer = Deployer()
deployer.packages_path = args.packages_dir
deployer.bundles_path = args.bundles_dir
deployer.should_clean = args.clean
deployer.config_location = args.config
deployer.deploy(args.tarball, args.extract_path)

View File

@@ -1,130 +0,0 @@
#!/bin/bash
set -e
usage() {
echo "Usage: $0 -v <version> -c <config file> [-n]"
echo
echo "version: commit-ish to check out and build"
echo "config file: a path to a json config file to"
echo "ship with the build. In addition, update_base_url:"
echo "from this file is used to set up auto-update."
echo "-n: build with no config file."
echo
echo "Values may also be passed as environment variables"
}
conffile=
version=
skipcfg=0
while getopts "c:v:n" opt; do
case $opt in
c)
conffile=$OPTARG
;;
v)
version=$OPTARG
;;
n)
skipcfg=1
;;
\?)
echo "Invalid option: -$OPTARG" >&2
usage
exit
;;
esac
done
if [ -z "$version" ]; then
echo "No version supplied"
usage
exit
fi
if [ -z "$conffile" ] && [ "$skipcfg" = 0 ]; then
echo "No config file given. Use -c to supply a config file or"
echo "-n to build with no config file (and no auto update)."
exit
fi
if [ -n "$conffile" ]; then
update_base_url=`jq -r .update_base_url $conffile`
if [ -z "$update_base_url" ]; then
echo "No update URL supplied. Use update_base_url: null if you really"
echo "want a build with no auto-update."
usage
exit
fi
# Make sure the base URL ends in a slash if it doesn't already
update_base_url=`echo $update_base_url | sed -e 's#\([^\/]\)$#\1\/#'`
fi
if [ ! -f package.json ]; then
echo "No package.json found. This script must be run from"
echo "the vector-web directory."
exit
fi
echo "Building $version using Update base URL $update_base_url"
projdir=`pwd`
builddir=`mktemp -d 2>/dev/null || mktemp -d -t 'buildtmp'`
pushd "$builddir"
git clone "$projdir" .
git checkout "$version"
# Figure out what version we're building
vername=`jq -r .version package.json`
if [ -n "$conffile" ]; then
popd
cp "$conffile" "$builddir/"
pushd "$builddir"
fi
npm install
npm run build:electron
popd
distdir="$builddir/electron/dist"
pubdir="$projdir/electron/pub"
rm -r "$pubdir" || true
mkdir -p "$pubdir"
# Install packages: what the user downloads the first time,
# (DMGs for mac, exe installer for windows)
mkdir -p "$pubdir/install/macos"
cp $distdir/mac/*.dmg "$pubdir/install/macos/"
mkdir -p "$pubdir/install/win32/ia32/"
cp $distdir/win-ia32/*.exe "$pubdir/install/win32/ia32/"
mkdir -p "$pubdir/install/win32/x64/"
cp $distdir/win/*.exe "$pubdir/install/win32/x64/"
# Packages for auto-update
mkdir -p "$pubdir/update/macos"
cp $distdir/mac/*.zip "$pubdir/update/macos/"
echo "$vername" > "$pubdir/update/macos/latest"
mkdir -p "$pubdir/update/win32/ia32/"
cp $distdir/win-ia32/*.nupkg "$pubdir/update/win32/ia32/"
cp $distdir/win-ia32/RELEASES "$pubdir/update/win32/ia32/"
mkdir -p "$pubdir/update/win32/x64/"
cp $distdir/win/*.nupkg "$pubdir/update/win32/x64/"
cp $distdir/win/RELEASES "$pubdir/update/win32/x64/"
# Move the debs to the main project dir's dist folder
rm -r "$projdir/electron/dist" || true
mkdir -p "$projdir/electron/dist"
cp $distdir/*.deb "$projdir/electron/dist/"
rm -rf "$builddir"
echo "Riot Desktop is ready to go in $pubdir: this directory can be hosted on your web server."
echo "deb archives are in electron/dist/ - these should be added into your debian repository"

View File

@@ -1,93 +0,0 @@
#!/bin/bash
if [ $# != 1 ]
then
echo "Usage: $0 <svg file>"
exit
fi
set -e
set -x
tmpdir=`mktemp -d 2>/dev/null || mktemp -d -t 'icontmp'`
for i in 1024 512 310 256 192 180 152 150 144 128 120 114 96 76 72 70 64 60 57 48 36 32 24 16
do
#convert -background none -density 1000 -resize $i -extent $i -gravity center "$1" "$tmpdir/$i.png"
# Above is the imagemagick command to render an svg to png. Unfortunately, its support for SVGs
# with CSS isn't very good (with rsvg and even moreso the built in renderer) so we use cairosvg.
# This can be installed with:
# pip install cairosvg==1.0.22 # Version 2 doesn't support python 2
# pip install tinycss
# pip install cssselect # These are necessary for CSS support
# You'll also need xmlstarlet from your favourite package manager
#
# Cairosvg doesn't suport rendering at a specific size (https://github.com/Kozea/CairoSVG/issues/83#issuecomment-215720176)
# so we have to 'resize the svg' first (add width and height attributes to the svg element) to make it render at the
# size we need.
# XXX: This will break if the svg already has width and height attributes
cp "$1" "$tmpdir/tmp.svg"
xmlstarlet ed -N x="http://www.w3.org/2000/svg" --insert "/x:svg" --type attr -n width -v $i "$tmpdir/tmp.svg" > "$tmpdir/tmp2.svg"
xmlstarlet ed -N x="http://www.w3.org/2000/svg" --insert "/x:svg" --type attr -n height -v $i "$tmpdir/tmp2.svg" > "$tmpdir/tmp3.svg"
cairosvg -f png -o "$tmpdir/$i.png" "$tmpdir/tmp3.svg"
rm "$tmpdir/tmp.svg" "$tmpdir/tmp2.svg" "$tmpdir/tmp3.svg"
done
# one more for the non-square mstile
cp "$1" "$tmpdir/tmp.svg"
xmlstarlet ed -N x="http://www.w3.org/2000/svg" --insert "/x:svg" --type attr -n width -v 310 "$tmpdir/tmp.svg" > "$tmpdir/tmp2.svg"
xmlstarlet ed -N x="http://www.w3.org/2000/svg" --insert "/x:svg" --type attr -n height -v 150 "$tmpdir/tmp2.svg" > "$tmpdir/tmp3.svg"
cairosvg -f png -o "$tmpdir/310x150.png" "$tmpdir/tmp3.svg"
rm "$tmpdir/tmp.svg" "$tmpdir/tmp2.svg" "$tmpdir/tmp3.svg"
mkdir "$tmpdir/Riot.iconset"
cp "$tmpdir/16.png" "$tmpdir/Riot.iconset/icon_16x16.png"
cp "$tmpdir/32.png" "$tmpdir/Riot.iconset/icon_16x16@2x.png"
cp "$tmpdir/32.png" "$tmpdir/Riot.iconset/icon_32x32.png"
cp "$tmpdir/64.png" "$tmpdir/Riot.iconset/icon_32x32@2x.png"
cp "$tmpdir/128.png" "$tmpdir/Riot.iconset/icon_128x128.png"
cp "$tmpdir/256.png" "$tmpdir/Riot.iconset/icon_128x128@2x.png"
cp "$tmpdir/256.png" "$tmpdir/Riot.iconset/icon_256x256.png"
cp "$tmpdir/512.png" "$tmpdir/Riot.iconset/icon_256x256@2x.png"
cp "$tmpdir/512.png" "$tmpdir/Riot.iconset/icon_512x512.png"
cp "$tmpdir/1024.png" "$tmpdir/Riot.iconset/icon_512x512@2x.png"
iconutil -c icns -o electron/build/icon.icns "$tmpdir/Riot.iconset"
cp "$tmpdir/36.png" "res/vector-icons/android-chrome-36x36.png"
cp "$tmpdir/48.png" "res/vector-icons/android-chrome-48x48.png"
cp "$tmpdir/72.png" "res/vector-icons/android-chrome-72x72.png"
cp "$tmpdir/96.png" "res/vector-icons/android-chrome-96x96.png"
cp "$tmpdir/144.png" "res/vector-icons/android-chrome-144x144.png"
cp "$tmpdir/192.png" "res/vector-icons/android-chrome-192x192.png"
cp "$tmpdir/180.png" "res/vector-icons/apple-touch-icon.png"
cp "$tmpdir/180.png" "res/vector-icons/apple-touch-icon-precomposed.png"
cp "$tmpdir/57.png" "res/vector-icons/apple-touch-icon-57x57.png"
cp "$tmpdir/60.png" "res/vector-icons/apple-touch-icon-60x60.png"
cp "$tmpdir/72.png" "res/vector-icons/apple-touch-icon-72x72.png"
cp "$tmpdir/76.png" "res/vector-icons/apple-touch-icon-76x76.png"
cp "$tmpdir/114.png" "res/vector-icons/apple-touch-icon-114x114.png"
cp "$tmpdir/120.png" "res/vector-icons/apple-touch-icon-120x120.png"
cp "$tmpdir/144.png" "res/vector-icons/apple-touch-icon-144x144.png"
cp "$tmpdir/152.png" "res/vector-icons/apple-touch-icon-152x152.png"
cp "$tmpdir/180.png" "res/vector-icons/apple-touch-icon-180x180.png"
cp "$tmpdir/16.png" "res/vector-icons/favicon-16x16.png"
cp "$tmpdir/32.png" "res/vector-icons/favicon-32x32.png"
cp "$tmpdir/96.png" "res/vector-icons/favicon-96x96.png"
cp "$tmpdir/70.png" "res/vector-icons/mstile-70x70.png"
cp "$tmpdir/144.png" "res/vector-icons/mstile-144x144.png"
cp "$tmpdir/150.png" "res/vector-icons/mstile-150x150.png"
cp "$tmpdir/310.png" "res/vector-icons/mstile-310x310.png"
cp "$tmpdir/310x150.png" "res/vector-icons/mstile-310x150.png"
convert "$tmpdir/16.png" "$tmpdir/32.png" "$tmpdir/64.png" "$tmpdir/128.png" "$tmpdir/256.png" "res/vector-icons/favicon.ico"
cp "res/vector-icons/favicon.ico" "electron/build/icon.ico"
# https://github.com/electron-userland/electron-builder/blob/3f97b86993d4ea5172e562b182230a194de0f621/src/targets/LinuxTargetHelper.ts#L127
for i in 24 96 16 48 64 128 256 512
do
cp "$tmpdir/$i.png" "electron/build/icons/${i}x${i}.png"
done
rm -r "$tmpdir"

View File

@@ -2,29 +2,14 @@
set -e
dev=""
if [ "$1" = '-d' ]; then
dev=":dev"
fi
version=`git describe --dirty --tags || echo unknown`
if [ -n "$DIST_VERSION" ]; then
version=$DIST_VERSION
else
version=`git describe --dirty --tags || echo unknown`
fi
npm run clean
npm run build$dev
# include the sample config in the tarball. Arguably this should be done by
# `npm run build`, but it's just too painful.
cp config.sample.json webapp/
mkdir -p dist
cp -r webapp vector-$version
npm run build
mkdir -p packages
cp -r vector vector-$version
echo $version > vector-$version/version
tar chvzf dist/vector-$version.tar.gz vector-$version
tar chvzf packages/vector-$version.tar.gz vector-$version
rm -r vector-$version
echo
echo "Packaged dist/vector-$version.tar.gz"
echo "Packaged packages/vector-$version.tar.gz"

View File

@@ -1,6 +1,5 @@
/*
Copyright 2015, 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.
@@ -20,69 +19,39 @@ limitations under the License.
* 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 = require('matrix-react-sdk/lib/component-index').components;
import structures$BottomLeftMenu from './components/structures/BottomLeftMenu';
structures$BottomLeftMenu && (module.exports.components['structures.BottomLeftMenu'] = structures$BottomLeftMenu);
import structures$CompatibilityPage from './components/structures/CompatibilityPage';
structures$CompatibilityPage && (module.exports.components['structures.CompatibilityPage'] = structures$CompatibilityPage);
import structures$HomePage from './components/structures/HomePage';
structures$HomePage && (module.exports.components['structures.HomePage'] = structures$HomePage);
import structures$LeftPanel from './components/structures/LeftPanel';
structures$LeftPanel && (module.exports.components['structures.LeftPanel'] = structures$LeftPanel);
import structures$RightPanel from './components/structures/RightPanel';
structures$RightPanel && (module.exports.components['structures.RightPanel'] = structures$RightPanel);
import structures$RoomDirectory from './components/structures/RoomDirectory';
structures$RoomDirectory && (module.exports.components['structures.RoomDirectory'] = structures$RoomDirectory);
import structures$RoomSubList from './components/structures/RoomSubList';
structures$RoomSubList && (module.exports.components['structures.RoomSubList'] = structures$RoomSubList);
import structures$RoomSubListHeader from './components/structures/RoomSubListHeader';
structures$RoomSubListHeader && (module.exports.components['structures.RoomSubListHeader'] = structures$RoomSubListHeader);
import structures$SearchBox from './components/structures/SearchBox';
structures$SearchBox && (module.exports.components['structures.SearchBox'] = structures$SearchBox);
import structures$ViewSource from './components/structures/ViewSource';
structures$ViewSource && (module.exports.components['structures.ViewSource'] = structures$ViewSource);
import views$context_menus$MessageContextMenu from './components/views/context_menus/MessageContextMenu';
views$context_menus$MessageContextMenu && (module.exports.components['views.context_menus.MessageContextMenu'] = views$context_menus$MessageContextMenu);
import views$context_menus$RoomTileContextMenu from './components/views/context_menus/RoomTileContextMenu';
views$context_menus$RoomTileContextMenu && (module.exports.components['views.context_menus.RoomTileContextMenu'] = views$context_menus$RoomTileContextMenu);
import views$dialogs$BugReportDialog from './components/views/dialogs/BugReportDialog';
views$dialogs$BugReportDialog && (module.exports.components['views.dialogs.BugReportDialog'] = views$dialogs$BugReportDialog);
import views$dialogs$ChangelogDialog from './components/views/dialogs/ChangelogDialog';
views$dialogs$ChangelogDialog && (module.exports.components['views.dialogs.ChangelogDialog'] = views$dialogs$ChangelogDialog);
import views$directory$NetworkDropdown from './components/views/directory/NetworkDropdown';
views$directory$NetworkDropdown && (module.exports.components['views.directory.NetworkDropdown'] = views$directory$NetworkDropdown);
import views$elements$ImageView from './components/views/elements/ImageView';
views$elements$ImageView && (module.exports.components['views.elements.ImageView'] = views$elements$ImageView);
import views$elements$Spinner from './components/views/elements/Spinner';
views$elements$Spinner && (module.exports.components['views.elements.Spinner'] = views$elements$Spinner);
import views$globals$GuestWarningBar from './components/views/globals/GuestWarningBar';
views$globals$GuestWarningBar && (module.exports.components['views.globals.GuestWarningBar'] = views$globals$GuestWarningBar);
import views$globals$MatrixToolbar from './components/views/globals/MatrixToolbar';
views$globals$MatrixToolbar && (module.exports.components['views.globals.MatrixToolbar'] = views$globals$MatrixToolbar);
import views$globals$NewVersionBar from './components/views/globals/NewVersionBar';
views$globals$NewVersionBar && (module.exports.components['views.globals.NewVersionBar'] = views$globals$NewVersionBar);
import views$login$VectorCustomServerDialog from './components/views/login/VectorCustomServerDialog';
views$login$VectorCustomServerDialog && (module.exports.components['views.login.VectorCustomServerDialog'] = views$login$VectorCustomServerDialog);
import views$login$VectorLoginFooter from './components/views/login/VectorLoginFooter';
views$login$VectorLoginFooter && (module.exports.components['views.login.VectorLoginFooter'] = views$login$VectorLoginFooter);
import views$login$VectorLoginHeader from './components/views/login/VectorLoginHeader';
views$login$VectorLoginHeader && (module.exports.components['views.login.VectorLoginHeader'] = views$login$VectorLoginHeader);
import views$messages$DateSeparator from './components/views/messages/DateSeparator';
views$messages$DateSeparator && (module.exports.components['views.messages.DateSeparator'] = views$messages$DateSeparator);
import views$messages$MessageTimestamp from './components/views/messages/MessageTimestamp';
views$messages$MessageTimestamp && (module.exports.components['views.messages.MessageTimestamp'] = views$messages$MessageTimestamp);
import views$rooms$DNDRoomTile from './components/views/rooms/DNDRoomTile';
views$rooms$DNDRoomTile && (module.exports.components['views.rooms.DNDRoomTile'] = views$rooms$DNDRoomTile);
import views$rooms$RoomDropTarget from './components/views/rooms/RoomDropTarget';
views$rooms$RoomDropTarget && (module.exports.components['views.rooms.RoomDropTarget'] = views$rooms$RoomDropTarget);
import views$rooms$RoomTooltip from './components/views/rooms/RoomTooltip';
views$rooms$RoomTooltip && (module.exports.components['views.rooms.RoomTooltip'] = views$rooms$RoomTooltip);
import views$rooms$SearchBar from './components/views/rooms/SearchBar';
views$rooms$SearchBar && (module.exports.components['views.rooms.SearchBar'] = views$rooms$SearchBar);
import views$settings$IntegrationsManager from './components/views/settings/IntegrationsManager';
views$settings$IntegrationsManager && (module.exports.components['views.settings.IntegrationsManager'] = views$settings$IntegrationsManager);
import views$settings$Notifications from './components/views/settings/Notifications';
views$settings$Notifications && (module.exports.components['views.settings.Notifications'] = views$settings$Notifications);
module.exports.components['structures.BottomLeftMenu'] = require('./components/structures/BottomLeftMenu');
module.exports.components['structures.CompatibilityPage'] = require('./components/structures/CompatibilityPage');
module.exports.components['structures.LeftPanel'] = require('./components/structures/LeftPanel');
module.exports.components['structures.RightPanel'] = require('./components/structures/RightPanel');
module.exports.components['structures.RoomDirectory'] = require('./components/structures/RoomDirectory');
module.exports.components['structures.RoomSubList'] = require('./components/structures/RoomSubList');
module.exports.components['structures.SearchBox'] = require('./components/structures/SearchBox');
module.exports.components['structures.ViewSource'] = require('./components/structures/ViewSource');
module.exports.components['views.context_menus.MessageContextMenu'] = require('./components/views/context_menus/MessageContextMenu');
module.exports.components['views.context_menus.NotificationStateContextMenu'] = require('./components/views/context_menus/NotificationStateContextMenu');
module.exports.components['views.context_menus.RoomTagContextMenu'] = require('./components/views/context_menus/RoomTagContextMenu');
module.exports.components['views.dialogs.ChangelogDialog'] = require('./components/views/dialogs/ChangelogDialog');
module.exports.components['views.directory.NetworkDropdown'] = require('./components/views/directory/NetworkDropdown');
module.exports.components['views.elements.ImageView'] = require('./components/views/elements/ImageView');
module.exports.components['views.elements.Spinner'] = require('./components/views/elements/Spinner');
module.exports.components['views.globals.GuestWarningBar'] = require('./components/views/globals/GuestWarningBar');
module.exports.components['views.globals.MatrixToolbar'] = require('./components/views/globals/MatrixToolbar');
module.exports.components['views.globals.NewVersionBar'] = require('./components/views/globals/NewVersionBar');
module.exports.components['views.login.VectorCustomServerDialog'] = require('./components/views/login/VectorCustomServerDialog');
module.exports.components['views.login.VectorLoginFooter'] = require('./components/views/login/VectorLoginFooter');
module.exports.components['views.login.VectorLoginHeader'] = require('./components/views/login/VectorLoginHeader');
module.exports.components['views.messages.DateSeparator'] = require('./components/views/messages/DateSeparator');
module.exports.components['views.messages.MessageTimestamp'] = require('./components/views/messages/MessageTimestamp');
module.exports.components['views.rooms.DNDRoomTile'] = require('./components/views/rooms/DNDRoomTile');
module.exports.components['views.rooms.RoomDropTarget'] = require('./components/views/rooms/RoomDropTarget');
module.exports.components['views.rooms.RoomTooltip'] = require('./components/views/rooms/RoomTooltip');
module.exports.components['views.rooms.SearchBar'] = require('./components/views/rooms/SearchBar');
module.exports.components['views.settings.IntegrationsManager'] = require('./components/views/settings/IntegrationsManager');
module.exports.components['views.settings.Notifications'] = require('./components/views/settings/Notifications');

View File

@@ -20,21 +20,18 @@ var React = require('react');
var ReactDOM = require('react-dom');
var sdk = require('matrix-react-sdk')
var dis = require('matrix-react-sdk/lib/dispatcher');
var AccessibleButton = require('matrix-react-sdk/lib/components/views/elements/AccessibleButton');
module.exports = React.createClass({
displayName: 'BottomLeftMenu',
propTypes: {
collapsed: React.PropTypes.bool.isRequired,
teamToken: React.PropTypes.string,
},
getInitialState: function() {
return({
directoryHover : false,
roomsHover : false,
homeHover: false,
peopleHover : false,
settingsHover : false,
});
@@ -65,19 +62,6 @@ module.exports = React.createClass({
this.setState({ roomsHover: false });
},
// Home button events
onHomeClick: function() {
dis.dispatch({ action: 'view_home_page' });
},
onHomeMouseEnter: function() {
this.setState({ homeHover: true });
},
onHomeMouseLeave: function() {
this.setState({ homeHover: false });
},
// People events
onPeopleClick: function() {
dis.dispatch({ action: 'view_create_chat' });
@@ -114,37 +98,25 @@ module.exports = React.createClass({
render: function() {
var TintableSvg = sdk.getComponent('elements.TintableSvg');
var homeButton;
if (this.props.teamToken) {
homeButton = (
<AccessibleButton className="mx_BottomLeftMenu_homePage" onClick={ this.onHomeClick } onMouseEnter={ this.onHomeMouseEnter } onMouseLeave={ this.onHomeMouseLeave } >
<TintableSvg src="img/icons-home.svg" width="25" height="25" />
{ this.getLabel("Welcome page", this.state.homeHover) }
</AccessibleButton>
);
}
return (
<div className="mx_BottomLeftMenu">
<div className="mx_BottomLeftMenu_options">
{ homeButton }
<AccessibleButton className="mx_BottomLeftMenu_people" onClick={ this.onPeopleClick } onMouseEnter={ this.onPeopleMouseEnter } onMouseLeave={ this.onPeopleMouseLeave } >
<div className="mx_BottomLeftMenu_people" onClick={ this.onPeopleClick } onMouseEnter={ this.onPeopleMouseEnter } onMouseLeave={ this.onPeopleMouseLeave } >
<TintableSvg src="img/icons-people.svg" width="25" height="25" />
{ this.getLabel("Start chat", this.state.peopleHover) }
</AccessibleButton>
<AccessibleButton className="mx_BottomLeftMenu_directory" onClick={ this.onDirectoryClick } onMouseEnter={ this.onDirectoryMouseEnter } onMouseLeave={ this.onDirectoryMouseLeave } >
</div>
<div className="mx_BottomLeftMenu_directory" onClick={ this.onDirectoryClick } onMouseEnter={ this.onDirectoryMouseEnter } onMouseLeave={ this.onDirectoryMouseLeave } >
<TintableSvg src="img/icons-directory.svg" width="25" height="25"/>
{ this.getLabel("Room directory", this.state.directoryHover) }
</AccessibleButton>
<AccessibleButton className="mx_BottomLeftMenu_createRoom" onClick={ this.onRoomsClick } onMouseEnter={ this.onRoomsMouseEnter } onMouseLeave={ this.onRoomsMouseLeave } >
</div>
<div className="mx_BottomLeftMenu_createRoom" onClick={ this.onRoomsClick } onMouseEnter={ this.onRoomsMouseEnter } onMouseLeave={ this.onRoomsMouseLeave } >
<TintableSvg src="img/icons-create-room.svg" width="25" height="25" />
{ this.getLabel("Create new room", this.state.roomsHover) }
</AccessibleButton>
<AccessibleButton className="mx_BottomLeftMenu_settings" onClick={ this.onSettingsClick } onMouseEnter={ this.onSettingsMouseEnter } onMouseLeave={ this.onSettingsMouseLeave } >
</div>
<div className="mx_BottomLeftMenu_settings" onClick={ this.onSettingsClick } onMouseEnter={ this.onSettingsMouseEnter } onMouseLeave={ this.onSettingsMouseLeave } >
<TintableSvg src="img/icons-settings.svg" width="25" height="25" />
{ this.getLabel("Settings", this.state.settingsHover) }
</AccessibleButton>
</div>
</div>
</div>
);

View File

@@ -45,8 +45,10 @@ module.exports = React.createClass({
available or experimental in your current browser.
</p>
<p>
Please install <a href="https://www.google.com/chrome">Chrome</a> or <a href="https://getfirefox.com">Firefox</a> for
the best experience. <a href="http://apple.com/safari">Safari</a> and <a href="http://opera.com">Opera</a> work too.
Please install <a href="https://www.google.com/chrome">Chrome</a> or
<a href="https://getfirefox.com">Firefox</a> for the best experience.
<a href="http://apple.com/safari">Safari</a> and
<a href="http://opera.com">Opera</a> work too.
</p>
<p>
With your current browser, the look and feel of the application may

View File

@@ -1,40 +0,0 @@
/*
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.
*/
'use strict';
import React from 'react';
import MatrixClientPeg from 'matrix-react-sdk/lib/MatrixClientPeg';
import sdk from 'matrix-react-sdk';
module.exports = React.createClass({
displayName: 'HomePage',
propTypes: {
teamServerUrl: React.PropTypes.string.isRequired,
teamToken: React.PropTypes.string.isRequired,
collapsedRhs: React.PropTypes.bool,
},
render: function() {
return (
<div className="mx_HomePage">
<iframe src={`${this.props.teamServerUrl}/static/${this.props.teamToken}/home.html`}/>
</div>
);
}
});

View File

@@ -19,22 +19,15 @@ limitations under the License.
var React = require('react');
var DragDropContext = require('react-dnd').DragDropContext;
var HTML5Backend = require('react-dnd-html5-backend');
var KeyCode = require('matrix-react-sdk/lib/KeyCode');
var sdk = require('matrix-react-sdk')
var dis = require('matrix-react-sdk/lib/dispatcher');
var VectorConferenceHandler = require('../../VectorConferenceHandler');
var CallHandler = require("matrix-react-sdk/lib/CallHandler");
var LeftPanel = React.createClass({
displayName: 'LeftPanel',
propTypes: {
collapsed: React.PropTypes.bool.isRequired,
teamToken: React.PropTypes.string,
},
getInitialState: function() {
return {
showCallElement: null,
@@ -42,10 +35,6 @@ var LeftPanel = React.createClass({
};
},
componentWillMount: function() {
this.focusedElement = null;
},
componentDidMount: function() {
this.dispatcherRef = dis.register(this.onAction);
},
@@ -68,91 +57,6 @@ var LeftPanel = React.createClass({
}
},
_onFocus: function(ev) {
this.focusedElement = ev.target;
},
_onBlur: function(ev) {
this.focusedElement = null;
},
_onKeyDown: function(ev) {
if (!this.focusedElement) return;
let handled = false;
switch (ev.keyCode) {
case KeyCode.UP:
this._onMoveFocus(true);
handled = true;
break;
case KeyCode.DOWN:
this._onMoveFocus(false);
handled = true;
break;
}
if (handled) {
ev.stopPropagation();
ev.preventDefault();
}
},
_onMoveFocus: function(up) {
var element = this.focusedElement;
// unclear why this isn't needed
// var descending = (up == this.focusDirection) ? this.focusDescending : !this.focusDescending;
// this.focusDirection = up;
var descending = false; // are we currently descending or ascending through the DOM tree?
var classes;
do {
var child = up ? element.lastElementChild : element.firstElementChild;
var sibling = up ? element.previousElementSibling : element.nextElementSibling;
if (descending) {
if (child) {
element = child;
}
else if (sibling) {
element = sibling;
}
else {
descending = false;
element = element.parentElement;
}
}
else {
if (sibling) {
element = sibling;
descending = true;
}
else {
element = element.parentElement;
}
}
if (element) {
classes = element.classList;
if (classes.contains("mx_LeftPanel")) { // we hit the top
element = up ? element.lastElementChild : element.firstElementChild;
descending = true;
}
}
} while(element && !(
classes.contains("mx_RoomTile") ||
classes.contains("mx_SearchBox_search") ||
classes.contains("mx_RoomSubList_ellipsis")));
if (element) {
element.focus();
this.focusedElement = element;
this.focusedDescending = descending;
}
},
_recheckCallElement: function(selectedRoomId) {
// if we aren't viewing a room with an ongoing call, but there is an
// active call, show the call element - we need to do this to make
@@ -211,8 +115,7 @@ var LeftPanel = React.createClass({
}
return (
<aside className={classes} style={{ opacity: this.props.opacity }}
onKeyDown={ this._onKeyDown } onFocus={ this._onFocus } onBlur={ this._onBlur }>
<aside className={classes} style={{ opacity: this.props.opacity }}>
<SearchBox collapsed={ this.props.collapsed } onSearch={ this.onSearch } />
{ collapseButton }
{ callPreview }
@@ -221,7 +124,7 @@ var LeftPanel = React.createClass({
collapsed={this.props.collapsed}
searchFilter={this.state.searchFilter}
ConferenceHandler={VectorConferenceHandler} />
<BottomLeftMenu collapsed={this.props.collapsed} teamToken={this.props.teamToken}/>
<BottomLeftMenu collapsed={this.props.collapsed}/>
</aside>
);
}

View File

@@ -23,7 +23,6 @@ var dis = require('matrix-react-sdk/lib/dispatcher');
var MatrixClientPeg = require("matrix-react-sdk/lib/MatrixClientPeg");
var rate_limited_func = require('matrix-react-sdk/lib/ratelimitedfunc');
var Modal = require('matrix-react-sdk/lib/Modal');
var AccessibleButton = require('matrix-react-sdk/lib/components/views/elements/AccessibleButton');
module.exports = React.createClass({
displayName: 'RightPanel',
@@ -70,21 +69,45 @@ module.exports = React.createClass({
},
onMemberListButtonClick: function() {
this.setState({ phase: this.Phase.MemberList });
if (this.props.collapsed || this.state.phase !== this.Phase.MemberList) {
this.setState({ phase: this.Phase.MemberList });
dis.dispatch({
action: 'show_right_panel',
});
}
else {
dis.dispatch({
action: 'hide_right_panel',
});
}
},
onFileListButtonClick: function() {
this.setState({ phase: this.Phase.FilePanel });
if (this.props.collapsed || this.state.phase !== this.Phase.FilePanel) {
this.setState({ phase: this.Phase.FilePanel });
dis.dispatch({
action: 'show_right_panel',
});
}
else {
dis.dispatch({
action: 'hide_right_panel',
});
}
},
onNotificationListButtonClick: function() {
this.setState({ phase: this.Phase.NotificationPanel });
},
onCollapseClick: function() {
dis.dispatch({
action: 'hide_right_panel',
});
if (this.props.collapsed || this.state.phase !== this.Phase.NotificationPanel) {
this.setState({ phase: this.Phase.NotificationPanel });
dis.dispatch({
action: 'show_right_panel',
});
}
else {
dis.dispatch({
action: 'hide_right_panel',
});
}
},
onInviteButtonClick: function() {
@@ -184,12 +207,12 @@ module.exports = React.createClass({
if (user_is_in_room) {
inviteGroup =
<AccessibleButton className="mx_RightPanel_invite" onClick={ this.onInviteButtonClick } >
<div className="mx_RightPanel_invite" onClick={ this.onInviteButtonClick } >
<div className="mx_RightPanel_icon" >
<TintableSvg src="img/icon-invite-people.svg" width="35" height="35" />
</div>
<div className="mx_RightPanel_message">Invite to this room</div>
</AccessibleButton>;
</div>;
}
}
@@ -197,28 +220,20 @@ module.exports = React.createClass({
if (this.props.roomId) {
buttonGroup =
<div className="mx_RightPanel_headerButtonGroup">
<AccessibleButton className="mx_RightPanel_headerButton"
title="Members" onClick={ this.onMemberListButtonClick }>
<div className="mx_RightPanel_headerButton" title="Members" onClick={ this.onMemberListButtonClick }>
<div className="mx_RightPanel_headerButton_badge">{ membersBadge ? membersBadge : <span>&nbsp;</span>}</div>
<TintableSvg src="img/icons-people.svg" width="25" height="25"/>
{ membersHighlight }
</AccessibleButton>
<AccessibleButton
className="mx_RightPanel_headerButton mx_RightPanel_filebutton"
title="Files" onClick={ this.onFileListButtonClick }>
</div>
<div className="mx_RightPanel_headerButton mx_RightPanel_filebutton" title="Files" onClick={ this.onFileListButtonClick }>
<div className="mx_RightPanel_headerButton_badge">&nbsp;</div>
<TintableSvg src="img/icons-files.svg" width="25" height="25"/>
{ filesHighlight }
</AccessibleButton>
<AccessibleButton
className="mx_RightPanel_headerButton mx_RightPanel_notificationbutton"
title="Notifications" onClick={ this.onNotificationListButtonClick }>
</div>
<div className="mx_RightPanel_headerButton mx_RightPanel_notificationbutton" title="Notifications" onClick={ this.onNotificationListButtonClick }>
<div className="mx_RightPanel_headerButton_badge">&nbsp;</div>
<TintableSvg src="img/icons-notifications.svg" width="25" height="25"/>
{ notificationsHighlight }
</AccessibleButton>
<div className="mx_RightPanel_headerButton mx_RightPanel_collapsebutton" title="Hide panel" onClick={ this.onCollapseClick }>
<TintableSvg src="img/minimise.svg" width="10" height="16"/>
</div>
</div>;
}
@@ -261,3 +276,4 @@ module.exports = React.createClass({
);
}
});

View File

@@ -23,6 +23,7 @@ var ContentRepo = require("matrix-js-sdk").ContentRepo;
var Modal = require('matrix-react-sdk/lib/Modal');
var sdk = require('matrix-react-sdk');
var dis = require('matrix-react-sdk/lib/dispatcher');
var GeminiScrollbar = require('react-gemini-scrollbar');
var linkify = require('linkifyjs');
var linkifyString = require('linkifyjs/string');
@@ -30,8 +31,6 @@ var linkifyMatrix = require('matrix-react-sdk/lib/linkify-matrix');
var sanitizeHtml = require('sanitize-html');
var q = require('q');
import {instanceForInstanceId, protocolNameForInstanceId} from '../../utils/DirectoryUtils';
linkifyMatrix(linkify);
module.exports = React.createClass({
@@ -43,7 +42,9 @@ module.exports = React.createClass({
getDefaultProps: function() {
return {
config: {},
config: {
networks: [],
},
}
},
@@ -51,26 +52,36 @@ module.exports = React.createClass({
return {
publicRooms: [],
loading: true,
protocolsLoading: true,
instanceId: null,
includeAll: false,
network: null,
roomServer: null,
filterString: null,
}
},
componentWillMount: function() {
// precompile Regexps
this.portalRoomPatterns = {};
this.nativePatterns = {};
if (this.props.config.networks) {
for (const network of Object.keys(this.props.config.networks)) {
const network_info = this.props.config.networks[network];
if (network_info.portalRoomPattern) {
this.portalRoomPatterns[network] = new RegExp(network_info.portalRoomPattern);
}
if (network_info.nativePattern) {
this.nativePatterns[network] = new RegExp(network_info.nativePattern);
}
}
}
this.nextBatch = null;
this.filterTimeout = null;
this.scrollPanel = null;
this.protocols = null;
this.setState({protocolsLoading: true});
MatrixClientPeg.get().getThirdpartyProtocols().done((response) => {
this.protocols = response;
this.setState({protocolsLoading: false});
}, (err) => {
this.setState({protocolsLoading: false});
if (MatrixClientPeg.get().isGuest()) {
// Guests currently aren't allowed to use this API, so
// ignore this as otherwise this error is literally the
@@ -120,11 +131,6 @@ module.exports = React.createClass({
if (my_server != MatrixClientPeg.getHomeServerName()) {
opts.server = my_server;
}
if (this.state.instanceId) {
opts.third_party_instance_id = this.state.instanceId;
} else if (this.state.includeAll) {
opts.include_all_networks = true;
}
if (this.nextBatch) opts.since = this.nextBatch;
if (my_filter_string) opts.filter = { generic_search_term: my_filter_string } ;
return MatrixClientPeg.get().publicRooms(opts).then((data) => {
@@ -161,7 +167,7 @@ module.exports = React.createClass({
var ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
Modal.createDialog(ErrorDialog, {
title: "Failed to get public room list",
description: ((err && err.message) ? err.message : "The server may be unavailable or overloaded"),
description: err.message
});
});
},
@@ -207,10 +213,9 @@ module.exports = React.createClass({
}, function(err) {
modal.close();
this.refreshRoomList();
console.error("Failed to " + step + ": " + err);
Modal.createDialog(ErrorDialog, {
title: "Failed to " + step,
description: ((err && err.message) ? err.message : "The server may be unavailable or overloaded"),
title: "Failed to "+step,
description: err.toString()
});
});
}
@@ -226,7 +231,7 @@ module.exports = React.createClass({
}
},
onOptionChange: function(server, instanceId, includeAll) {
onOptionChange: function(server, network) {
// clear next batch so we don't try to load more rooms
this.nextBatch = null;
this.setState({
@@ -235,8 +240,7 @@ module.exports = React.createClass({
// to clear the list anyway.
publicRooms: [],
roomServer: server,
instanceId: instanceId,
includeAll: includeAll,
network: network,
}, this.refreshRoomList);
// We also refresh the room list each time even though this
// filtering is client-side. It hopefully won't be client side
@@ -267,7 +271,7 @@ module.exports = React.createClass({
this.filterTimeout = setTimeout(() => {
this.filterTimeout = null;
this.refreshRoomList();
}, 700);
}, 300);
},
onFilterClear: function() {
@@ -282,19 +286,14 @@ module.exports = React.createClass({
},
onJoinClick: function(alias) {
// If we don't have a particular instance id selected, just show that rooms alias
if (!this.state.instanceId) {
// If the user specified an alias without a domain, add on whichever server is selected
// in the dropdown
if (alias.indexOf(':') == -1) {
alias = alias + ':' + this.state.roomServer;
}
// If we're on the 'Matrix' network (or all networks),
// just show that rooms alias
if (this.state.network == null || this.state.network == '_matrix') {
this.showRoomAlias(alias);
} else {
// This is a 3rd party protocol. Let's see if we can join it
const protocolName = protocolNameForInstanceId(this.protocols, this.state.instanceId);
const instance = instanceForInstanceId(this.protocols, this.state.instanceId);
const fields = protocolName ? this._getFieldsForThirdPartyLocation(alias, this.protocols[protocolName], instance) : null;
// This is a 3rd party protocol. Let's see if we
// can join it
const fields = this._getFieldsForThirdPartyLocation(alias, this.state.network);
if (!fields) {
const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
Modal.createDialog(ErrorDialog, {
@@ -303,7 +302,8 @@ module.exports = React.createClass({
});
return;
}
MatrixClientPeg.get().getThirdpartyLocation(protocolName, fields).done((resp) => {
const protocol = this._protocolForThirdPartyNetwork(this.state.network);
MatrixClientPeg.get().getThirdpartyLocation(protocol, fields).done((resp) => {
if (resp.length > 0 && resp[0].alias) {
this.showRoomAlias(resp[0].alias);
} else {
@@ -372,7 +372,13 @@ module.exports = React.createClass({
if (!this.state.publicRooms) return [];
var rooms = this.state.publicRooms;
var rooms = this.state.publicRooms.filter((a) => {
if (this.state.network) {
if (!this._isRoomInNetwork(a, this.state.roomServer, this.state.network)) return false;
}
return true;
});
var rows = [];
var self = this;
var guestRead, guestJoin, perms;
@@ -434,57 +440,119 @@ module.exports = React.createClass({
this.scrollPanel = element;
},
_stringLooksLikeId: function(s, field_type) {
/**
* Terrible temporary function that guess what network a public room
* entry is in, until synapse is able to tell us
*/
_isRoomInNetwork: function(room, server, network) {
// We carve rooms into two categories here. 'portal' rooms are
// rooms created by a user joining a bridge 'portal' alias to
// participate in that room or a foreign network. A room is a
// portal room if it has exactly one alias and that alias matches
// a pattern defined in the config. Its network is the key
// of the pattern that it matches.
// All other rooms are considered 'native matrix' rooms, and
// go into the special '_matrix' network.
let roomNetwork = '_matrix';
if (room.aliases && room.aliases.length == 1) {
if (this.props.config.serverConfig && this.props.config.serverConfig[server] && this.props.config.serverConfig[server].networks) {
for (const n of this.props.config.serverConfig[server].networks) {
const pat = this.portalRoomPatterns[n];
if (pat && pat.test(room.aliases[0])) {
roomNetwork = n;
}
}
}
}
return roomNetwork == network;
},
_stringLooksLikeId: function(s, network) {
let pat = /^#[^\s]+:[^\s]/;
if (field_type && field_type.regexp) {
pat = new RegExp(field_type.regexp);
if (
network && network != '_matrix' &&
this.nativePatterns[network]
) {
pat = this.nativePatterns[network];
}
return pat.test(s);
},
_getFieldsForThirdPartyLocation: function(userInput, protocol, instance) {
// make an object with the fields specified by that protocol. We
_protocolForThirdPartyNetwork: function(network) {
if (
this.props.config.networks &&
this.props.config.networks[network] &&
this.props.config.networks[network].protocol
) {
return this.props.config.networks[network].protocol;
}
},
_getFieldsForThirdPartyLocation: function(user_input, network) {
if (!this.props.config.networks || !this.props.config.networks[network]) return null;
const network_info = this.props.config.networks[network];
if (!network_info.protocol) return null;
if (!this.protocols) return null;
let matched_instance;
// Try to find which instance in the 'protocols' response
// matches this network. We look for a matching protocol
// and the existence of a 'domain' field and if present,
// its value.
if (
this.protocols[network_info.protocol] &&
this.protocols[network_info.protocol].instances &&
this.protocols[network_info.protocol].instances.length == 1
) {
const the_instance = this.protocols[network_info.protocol].instances[0];
// If there's only one instance in this protocol, use it
// as long as it has no domain (which we assume to mean it's
// there is only one possible instance).
if (
(
the_instance.fields.domain === undefined &&
network_info.domain === undefined
) ||
(
the_instance.fields.domain !== undefined &&
the_instance.fields.domain == network_info.domain
)
) {
matched_instance = the_instance;
}
} else if (network_info.domain) {
// otherwise, we look for one with a matching domain.
for (const this_instance of this.protocols[network_info.protocol].instances) {
if (this_instance.fields.domain == network_info.domain) {
matched_instance = this_instance;
}
}
}
if (matched_instance === undefined) return null;
// now make an object with the fields specified by that protocol. We
// require that the values of all but the last field come from the
// instance. The last is the user input.
const requiredFields = protocol.location_fields;
if (!requiredFields) return null;
const required_fields = this.protocols[network_info.protocol].location_fields;
const fields = {};
for (let i = 0; i < requiredFields.length - 1; ++i) {
const thisField = requiredFields[i];
if (instance.fields[thisField] === undefined) return null;
fields[thisField] = instance.fields[thisField];
for (let i = 0; i < required_fields.length - 1; ++i) {
const this_field = required_fields[i];
if (matched_instance.fields[this_field] === undefined) return null;
fields[this_field] = matched_instance.fields[this_field];
}
fields[requiredFields[requiredFields.length - 1]] = userInput;
fields[required_fields[required_fields.length - 1]] = user_input;
return fields;
},
/**
* called by the parent component when PageUp/Down/etc is pressed.
*
* We pass it down to the scroll panel.
*/
handleScrollKey: function(ev) {
if (this.scrollPanel) {
this.scrollPanel.handleScrollKey(ev);
}
},
render: function() {
const SimpleRoomHeader = sdk.getComponent('rooms.SimpleRoomHeader');
const Loader = sdk.getComponent("elements.Spinner");
if (this.state.protocolsLoading) {
return (
<div className="mx_RoomDirectory">
<SimpleRoomHeader title="Directory" />
<Loader />
</div>
);
}
let content;
if (this.state.loading) {
const Loader = sdk.getComponent("elements.Spinner");
content = <div className="mx_RoomDirectory">
<Loader />
</div>;
@@ -515,40 +583,31 @@ module.exports = React.createClass({
</ScrollPanel>;
}
const protocolName = protocolNameForInstanceId(this.protocols, this.state.instanceId);
let instance_expected_field_type;
if (
protocolName &&
this.protocols &&
this.protocols[protocolName] &&
this.protocols[protocolName].location_fields.length > 0 &&
this.protocols[protocolName].field_types
) {
const last_field = this.protocols[protocolName].location_fields.slice(-1)[0];
instance_expected_field_type = this.protocols[protocolName].field_types[last_field];
}
let placeholder = 'Search for a room';
if (!this.state.instanceId) {
if (this.state.network === null || this.state.network === '_matrix') {
placeholder = '#example:' + this.state.roomServer;
} else if (instance_expected_field_type) {
placeholder = instance_expected_field_type.placeholder;
} else if (
this.props.config.networks &&
this.props.config.networks[this.state.network] &&
this.props.config.networks[this.state.network].example &&
this._getFieldsForThirdPartyLocation(this.state.filterString, this.state.network)
) {
placeholder = this.props.config.networks[this.state.network].example;
}
let showJoinButton = this._stringLooksLikeId(this.state.filterString, instance_expected_field_type);
if (protocolName) {
const instance = instanceForInstanceId(this.protocols, this.state.instanceId);
if (this._getFieldsForThirdPartyLocation(this.state.filterString, this.protocols[protocolName], instance) === null) {
let showJoinButton = this._stringLooksLikeId(this.state.filterString, this.state.network);
if (this.state.network && this.state.network != '_matrix') {
if (this._getFieldsForThirdPartyLocation(this.state.filterString, this.state.network) === null) {
showJoinButton = false;
}
}
const SimpleRoomHeader = sdk.getComponent('rooms.SimpleRoomHeader');
const NetworkDropdown = sdk.getComponent('directory.NetworkDropdown');
const DirectorySearchBox = sdk.getComponent('elements.DirectorySearchBox');
return (
<div className="mx_RoomDirectory">
<SimpleRoomHeader title="Directory" icon="img/icons-directory.svg"/>
<SimpleRoomHeader title="Directory" />
<div className="mx_RoomDirectory_list">
<div className="mx_RoomDirectory_listheader">
<DirectorySearchBox
@@ -556,7 +615,7 @@ module.exports = React.createClass({
onChange={this.onFilterChange} onClear={this.onFilterClear} onJoinClick={this.onJoinClick}
placeholder={placeholder} showJoinButton={showJoinButton}
/>
<NetworkDropdown config={this.props.config} protocols={this.protocols} onOptionChange={this.onOptionChange} />
<NetworkDropdown config={this.props.config} onOptionChange={this.onOptionChange} />
</div>
{content}
</div>

View File

@@ -26,12 +26,8 @@ var Unread = require('matrix-react-sdk/lib/Unread');
var MatrixClientPeg = require('matrix-react-sdk/lib/MatrixClientPeg');
var RoomNotifs = require('matrix-react-sdk/lib/RoomNotifs');
var FormattingUtils = require('matrix-react-sdk/lib/utils/FormattingUtils');
var AccessibleButton = require('matrix-react-sdk/lib/components/views/elements/AccessibleButton');
var ConstantTimeDispatcher = require('matrix-react-sdk/lib/ConstantTimeDispatcher');
var RoomSubListHeader = require('./RoomSubListHeader.js');
import Modal from 'matrix-react-sdk/lib/Modal';
// turn this on for drag & drop console debugging galore
// turn this on for drop & drag console debugging galore
var debug = false;
const TRUNCATE_AT = 10;
@@ -74,12 +70,14 @@ var RoomSubList = React.createClass({
order: React.PropTypes.string.isRequired,
// undefined if no room is selected (eg we are showing settings)
selectedRoom: React.PropTypes.string,
startAsHidden: React.PropTypes.bool,
showSpinner: React.PropTypes.bool, // true to show a spinner if 0 elements when expanded
collapsed: React.PropTypes.bool.isRequired, // is LeftPanel collapsed?
onHeaderClick: React.PropTypes.func,
alwaysShowHeader: React.PropTypes.bool,
selectedRoom: React.PropTypes.string,
incomingCall: React.PropTypes.object,
onShowMoreRooms: React.PropTypes.func,
searchFilter: React.PropTypes.string,
@@ -101,31 +99,13 @@ var RoomSubList = React.createClass({
},
componentWillMount: function() {
constantTimeDispatcher.register("RoomSubList.sort", this.props.tagName, this.onSort);
constantTimeDispatcher.register("RoomSubList.refreshHeader", this.props.tagName, this.onRefresh);
this.sortList(this.applySearchFilter(this.props.list, this.props.searchFilter), this.props.order);
this._fixUndefinedOrder(this.props.list);
},
componentWillUnmount: function() {
constantTimeDispatcher.unregister("RoomSubList.sort", this.props.tagName, this.onSort);
constantTimeDispatcher.unregister("RoomSubList.refreshHeader", this.props.tagName, this.onRefresh);
},
componentWillReceiveProps: function(newProps) {
// order the room list appropriately before we re-render
//if (debug) console.log("received new props, list = " + newProps.list);
this.sortList(this.applySearchFilter(newProps.list, newProps.searchFilter), newProps.order);
this._fixUndefinedOrder(newProps.list);
},
onSort: function() {
this.sortList(this.applySearchFilter(this.props.list, this.props.searchFilter), this.props.order);
// we deliberately don't waste time trying to fix undefined ordering here
},
onRefresh: function() {
this.forceUpdate();
},
applySearchFilter: function(list, filter) {
@@ -138,7 +118,7 @@ var RoomSubList = React.createClass({
// The header is collapsable if it is hidden or not stuck
// The dataset elements are added in the RoomList _initAndPositionStickyHeaders method
isCollapsableOnClick: function() {
var stuck = this.refs.header.refs.header.dataset.stuck;
var stuck = this.refs.header.dataset.stuck;
if (this.state.hidden || stuck === undefined || stuck === "none") {
return true;
} else {
@@ -161,25 +141,16 @@ var RoomSubList = React.createClass({
this.props.onHeaderClick(isHidden);
} else {
// The header is stuck, so the click is to be interpreted as a scroll to the header
this.props.onHeaderClick(this.state.hidden, this.refs.header.refs.header.dataset.originalPosition);
this.props.onHeaderClick(this.state.hidden, this.refs.header.dataset.originalPosition);
}
},
onRoomTileClick(roomId, ev) {
dis.dispatch({
action: 'view_room',
room_id: roomId,
clear_search: (ev && (ev.keyCode == 13 || ev.keyCode == 32)),
});
},
tsOfNewestEvent: function(room) {
for (var i = room.timeline.length - 1; i >= 0; --i) {
var ev = room.timeline[i];
if (ev.getTs() &&
(Unread.eventTriggersUnreadCount(ev) ||
(ev.getSender() === MatrixClientPeg.get().credentials.userId))
) {
if (Unread.eventTriggersUnreadCount(ev) ||
(ev.sender && ev.sender.userId === MatrixClientPeg.get().credentials.userId))
{
return ev.getTs();
}
}
@@ -187,7 +158,7 @@ var RoomSubList = React.createClass({
// we might only have events that don't trigger the unread indicator,
// in which case use the oldest event even if normally it wouldn't count.
// This is better than just assuming the last event was forever ago.
if (room.timeline.length && room.timeline[0].getTs()) {
if (room.timeline.length) {
return room.timeline[0].getTs();
} else {
return Number.MAX_SAFE_INTEGER;
@@ -231,6 +202,9 @@ var RoomSubList = React.createClass({
if (order === "manual") comparator = this.manualComparator;
if (order === "recent") comparator = this.recentsComparator;
// Fix undefined orders here, and make sure the backend gets updated as well
this._fixUndefinedOrder(list);
//if (debug) console.log("sorting list for sublist " + this.props.label + " with length " + list.length + ", this.props.list = " + this.props.list);
this.setState({ sortedList: list.sort(comparator) });
},
@@ -265,9 +239,10 @@ var RoomSubList = React.createClass({
if (badges) {
result[0] += notificationCount;
if (highlight) {
result[1] = true;
}
}
result[1] |= highlight;
}
return result;
}, [0, false]);
@@ -375,6 +350,7 @@ var RoomSubList = React.createClass({
var self = this;
var DNDRoomTile = sdk.getComponent("rooms.DNDRoomTile");
return this.state.sortedList.map(function(room) {
var selected = room.roomId == self.props.selectedRoom;
// XXX: is it evil to pass in self as a prop to RoomTile?
return (
<DNDRoomTile
@@ -382,16 +358,78 @@ var RoomSubList = React.createClass({
roomSubList={ self }
key={ room.roomId }
collapsed={ self.props.collapsed || false}
selectedRoom={ self.props.selectedRoom }
selected={ selected }
unread={ Unread.doesRoomHaveUnreadMessages(room) }
highlight={ room.getUnreadNotificationCount('highlight') > 0 || self.props.label === 'Invites' }
isInvite={ self.props.label === 'Invites' }
refreshSubList={ self._updateSubListCount }
incomingCall={ null }
onClick={ self.onRoomTileClick }
/>
incomingCall={ null } />
);
});
},
_getHeaderJsx: function() {
var TintableSvg = sdk.getComponent("elements.TintableSvg");
var subListNotifications = this.roomNotificationCount();
var subListNotifCount = subListNotifications[0];
var subListNotifHighlight = subListNotifications[1];
var roomCount = this.props.list.length > 0 ? this.props.list.length : '';
var chevronClasses = classNames({
'mx_RoomSubList_chevron': true,
'mx_RoomSubList_chevronRight': this.state.hidden,
'mx_RoomSubList_chevronDown': !this.state.hidden,
});
var badgeClasses = classNames({
'mx_RoomSubList_badge': true,
'mx_RoomSubList_badgeHighlight': subListNotifHighlight,
});
var badge;
if (subListNotifCount > 0) {
badge = <div className={badgeClasses}>{ FormattingUtils.formatCount(subListNotifCount) }</div>;
}
// When collapsed, allow a long hover on the header to show user
// the full tag name and room count
var title;
if (this.props.collapsed) {
title = this.props.label;
if (roomCount !== '') {
title += " [" + roomCount + "]";
}
}
var incomingCall;
if (this.props.incomingCall) {
var self = this;
// Check if the incoming call is for this section
var incomingCallRoom = this.state.sortedList.filter(function(room) {
return self.props.incomingCall.roomId === room.roomId;
});
if (incomingCallRoom.length === 1) {
var IncomingCallBox = sdk.getComponent("voip.IncomingCallBox");
incomingCall = <IncomingCallBox className="mx_RoomSubList_incomingCall" incomingCall={ this.props.incomingCall }/>;
}
}
return (
<div className="mx_RoomSubList_labelContainer" title={ title } ref="header">
<div onClick={ this.onClick } className="mx_RoomSubList_label">
{ this.props.collapsed ? '' : this.props.label }
<div className="mx_RoomSubList_roomCount">{ roomCount }</div>
<div className={chevronClasses}></div>
{ badge }
{ incomingCall }
</div>
</div>
);
},
_createOverflowTile: function(overflowCount, totalCount) {
var content = <div className="mx_RoomSubList_chevronDown"></div>;
@@ -409,11 +447,11 @@ var RoomSubList = React.createClass({
});
return (
<AccessibleButton className="mx_RoomSubList_ellipsis" onClick={this._showFullMemberList}>
<div className="mx_RoomSubList_ellipsis" onClick={this._showFullMemberList}>
<div className="mx_RoomSubList_line"></div>
<div className="mx_RoomSubList_more">more</div>
<div className={ badgeClasses }>{ content }</div>
</AccessibleButton>
<div className={ badgeClasses }>{ content }</div>
</div>
);
},
@@ -447,7 +485,7 @@ var RoomSubList = React.createClass({
// gets triggered and another list is passed in. Doing it one at a time means that
// we always correctly calculate the highest order for the list - stops multiple
// rooms getting the same order. This is only really relevant for the first time this
// is run with historical room tag data, after that there should only be one undefined
// is run with historical room tag data, after that there should only be undefined
// in the list at a time anyway.
for (let i = 0; i < list.length; i++) {
if (list[i].tags[self.props.tagName] && list[i].tags[self.props.tagName].order === undefined) {
@@ -455,10 +493,9 @@ var RoomSubList = React.createClass({
// Do any final stuff here
}).fail(function(err) {
var ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
console.error("Failed to add tag " + self.props.tagName + " to room" + err);
Modal.createDialog(ErrorDialog, {
title: "Failed to add tag " + self.props.tagName + " to room",
description: ((err && err.message) ? err.message : "Operation failed"),
description: err.toString()
});
});
break;
@@ -481,16 +518,6 @@ var RoomSubList = React.createClass({
target = <RoomDropTarget label={ 'Drop here to ' + this.props.verb }/>;
}
var roomCount = this.props.list.length > 0 ? this.props.list.length : '';
var isIncomingCallRoom;
if (this.props.incomingCall) {
// Check if the incoming call is for this section
isIncomingCallRoom = this.props.list.find(room=>{
return this.props.incomingCall.roomId === room.roomId;
}) ? true : false;
}
if (this.state.sortedList.length > 0 || this.props.editable) {
var subList;
var classes = "mx_RoomSubList";
@@ -509,19 +536,7 @@ var RoomSubList = React.createClass({
return connectDropTarget(
<div>
<RoomSubListHeader
ref='header'
label={ this.props.label }
tagName={ this.props.tagName }
roomCount={ roomCount }
collapsed={ this.props.collapsed }
hidden={ this.state.hidden }
incomingCall={ this.props.incomingCall }
isIncomingCallRoom={ isIncomingCallRoom }
roomNotificationCount={ this.roomNotificationCount() }
onClick={ this.onClick }
onHeaderClick={ this.props.onHeaderClick }
/>
{ this._getHeaderJsx() }
{ subList }
</div>
);
@@ -530,20 +545,7 @@ var RoomSubList = React.createClass({
var Loader = sdk.getComponent("elements.Spinner");
return (
<div className="mx_RoomSubList">
{ this.props.alwaysShowHeader ?
<RoomSubListHeader
ref='header'
label={ this.props.label }
tagName={ this.props.tagName }
roomCount={ roomCount }
collapsed={ this.props.collapsed }
hidden={ this.state.hidden }
isIncomingCallRoom={ isIncomingCallRoom }
roomNotificationCount={ this.roomNotificationCount() }
onClick={ this.onClick }
onHeaderClick={ this.props.onHeaderClick }
/>
: undefined }
{ this.props.alwaysShowHeader ? this._getHeaderJsx() : undefined }
{ (this.props.showSpinner && !this.state.hidden) ? <Loader /> : undefined }
</div>
);

View File

@@ -1,121 +0,0 @@
/*
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.
*/
'use strict';
var React = require('react');
var ReactDOM = require('react-dom');
var classNames = require('classnames');
var sdk = require('matrix-react-sdk')
var FormattingUtils = require('matrix-react-sdk/lib/utils/FormattingUtils');
var RoomNotifs = require('matrix-react-sdk/lib/RoomNotifs');
var AccessibleButton = require('matrix-react-sdk/lib/components/views/elements/AccessibleButton');
var ConstantTimeDispatcher = require('matrix-react-sdk/lib/ConstantTimeDispatcher');
module.exports = React.createClass({
displayName: 'RoomSubListHeader',
propTypes: {
label: React.PropTypes.string.isRequired,
tagName: React.PropTypes.string,
roomCount: React.PropTypes.oneOfType([
React.PropTypes.string,
React.PropTypes.number
]),
collapsed: React.PropTypes.bool.isRequired, // is LeftPanel collapsed?
incomingCall: React.PropTypes.object,
isIncomingCallRoom: React.PropTypes.bool,
roomNotificationCount: React.PropTypes.array,
hidden: React.PropTypes.bool,
onClick: React.PropTypes.func,
onHeaderClick: React.PropTypes.func,
},
getDefaultProps: function() {
return {
onHeaderClick: function() {}, // NOP
};
},
componentWillMount: function() {
// constantTimeDispatcher.register("RoomSubList.refreshHeader", this.props.tagName, this.onRefresh);
},
componentWillUnmount: function() {
// constantTimeDispatcher.unregister("RoomSubList.refreshHeader", this.props.tagName, this.onRefresh);
},
// onRefresh: function() {
// this.forceUpdate();
// },
render: function() {
var TintableSvg = sdk.getComponent("elements.TintableSvg");
var subListNotifications = this.props.roomNotificationCount;
var subListNotifCount = subListNotifications[0];
var subListNotifHighlight = subListNotifications[1];
var chevronClasses = classNames({
'mx_RoomSubList_chevron': true,
'mx_RoomSubList_chevronRight': this.props.hidden,
'mx_RoomSubList_chevronDown': !this.props.hidden,
});
var badgeClasses = classNames({
'mx_RoomSubList_badge': true,
'mx_RoomSubList_badgeHighlight': subListNotifHighlight,
});
var badge;
if (subListNotifCount > 0) {
badge = <div className={badgeClasses}>{ FormattingUtils.formatCount(subListNotifCount) }</div>;
}
else if (subListNotifHighlight) {
badge = <div className={badgeClasses}>!</div>;
}
// When collapsed, allow a long hover on the header to show user
// the full tag name and room count
var title;
var roomCount = this.props.roomCount;
if (this.props.collapsed) {
title = this.props.label;
if (roomCount !== '') {
title += " [" + roomCount + "]";
}
}
var incomingCall;
if (this.props.isIncomingCallRoom) {
var IncomingCallBox = sdk.getComponent("voip.IncomingCallBox");
incomingCall = <IncomingCallBox className="mx_RoomSubList_incomingCall" incomingCall={ this.props.incomingCall }/>;
}
return (
<div className="mx_RoomSubList_labelContainer" title={ title } ref="header">
<AccessibleButton onClick={ this.props.onClick } className="mx_RoomSubList_label" tabIndex="0">
{ this.props.collapsed ? '' : this.props.label }
<div className="mx_RoomSubList_roomCount">{ roomCount }</div>
<div className={chevronClasses}></div>
{ badge }
</AccessibleButton>
{ incomingCall }
</div>
);
},
});

View File

@@ -20,8 +20,6 @@ var React = require('react');
var sdk = require('matrix-react-sdk')
var dis = require('matrix-react-sdk/lib/dispatcher');
var rate_limited_func = require('matrix-react-sdk/lib/ratelimitedfunc');
var AccessibleButton = require('matrix-react-sdk/lib/components/views/elements/AccessibleButton');
var KeyCode = require('matrix-react-sdk/lib/KeyCode');
module.exports = React.createClass({
displayName: 'SearchBox',
@@ -37,27 +35,6 @@ module.exports = React.createClass({
};
},
componentDidMount: function() {
this.dispatcherRef = dis.register(this.onAction);
document.addEventListener('keydown', this._onKeyDown);
},
componentWillUnmount: function() {
dis.unregister(this.dispatcherRef);
document.removeEventListener('keydown', this._onKeyDown);
},
onAction: function(payload) {
switch (payload.action) {
// Clear up the text field when a room is selected.
case 'view_room':
if (payload.clear_search && this.refs.search) {
this._clearSearch();
}
break;
}
},
onChange: function() {
if (!this.refs.search) return;
this.setState({ searchTerm: this.refs.search.value });
@@ -84,74 +61,35 @@ module.exports = React.createClass({
}
},
_clearSearch: function() {
this.refs.search.value = "";
this.onChange();
},
_onKeyDown: function(ev) {
let handled = false;
const isMac = navigator.platform.toUpperCase().indexOf('MAC') >= 0;
let ctrlCmdOnly;
if (isMac) {
ctrlCmdOnly = ev.metaKey && !ev.altKey && !ev.ctrlKey && !ev.shiftKey;
} else {
ctrlCmdOnly = ev.ctrlKey && !ev.altKey && !ev.metaKey && !ev.shiftKey;
}
switch (ev.keyCode) {
case KeyCode.ESCAPE:
this._clearSearch();
dis.dispatch({action: 'focus_composer'});
break;
case KeyCode.KEY_K:
if (ctrlCmdOnly) {
if (this.refs.search) {
this.refs.search.focus();
this.refs.search.select();
}
handled = true;
}
break;
}
if (handled) {
ev.stopPropagation();
ev.preventDefault();
}
},
render: function() {
var TintableSvg = sdk.getComponent('elements.TintableSvg');
var collapseTabIndex = this.refs.search && this.refs.search.value !== "" ? "-1" : "0";
var toggleCollapse;
if (this.props.collapsed) {
toggleCollapse =
<AccessibleButton className="mx_SearchBox_maximise" tabIndex={collapseTabIndex} onClick={ this.onToggleCollapse.bind(this, true) }>
<TintableSvg src="img/maximise.svg" width="10" height="16" alt="Expand panel"/>
</AccessibleButton>
<div className="mx_SearchBox_maximise" onClick={ this.onToggleCollapse.bind(this, true) }>
<TintableSvg src="img/maximise.svg" width="10" height="16" alt="&lt;"/>
</div>
}
else {
toggleCollapse =
<AccessibleButton className="mx_SearchBox_minimise" tabIndex={collapseTabIndex} onClick={ this.onToggleCollapse.bind(this, false) }>
<TintableSvg src="img/minimise.svg" width="10" height="16" alt="Collapse panel"/>
</AccessibleButton>
<div className="mx_SearchBox_minimise" onClick={ this.onToggleCollapse.bind(this, false) }>
<TintableSvg src="img/minimise.svg" width="10" height="16" alt="&lt;"/>
</div>
}
var searchControls;
if (!this.props.collapsed) {
searchControls = [
this.state.searchTerm.length > 0 ?
<AccessibleButton key="button"
className="mx_SearchBox_closeButton"
onClick={ ()=>{ this._clearSearch(); } }>
<div key="button"
className="mx_SearchBox_closeButton"
onClick={ ()=>{ this.refs.search.value = ""; this.onChange(); } }>
<TintableSvg
className="mx_SearchBox_searchButton"
src="img/icons-close.svg" width="24" height="24"
/>
</AccessibleButton>
</div>
:
<TintableSvg
key="button"

View File

@@ -22,8 +22,7 @@ module.exports = React.createClass({
displayName: 'ViewSource',
propTypes: {
content: React.PropTypes.object.isRequired,
onFinished: React.PropTypes.func.isRequired,
onFinished: React.PropTypes.func.isRequired
},
componentDidMount: function() {
@@ -46,9 +45,10 @@ module.exports = React.createClass({
return (
<div className="mx_ViewSource">
<pre>
{JSON.stringify(this.props.content, null, 2)}
{JSON.stringify(this.props.mxEvent.event, null, 2)}
</pre>
</div>
);
}
});

View File

@@ -47,39 +47,25 @@ module.exports = React.createClass({
onViewSourceClick: function() {
var ViewSource = sdk.getComponent('structures.ViewSource');
Modal.createDialog(ViewSource, {
content: this.props.mxEvent.event,
}, 'mx_Dialog_viewsource');
if (this.props.onFinished) this.props.onFinished();
},
onViewClearSourceClick: function() {
const ViewSource = sdk.getComponent('structures.ViewSource');
Modal.createDialog(ViewSource, {
// FIXME: _clearEvent is private
content: this.props.mxEvent._clearEvent,
mxEvent: this.props.mxEvent
}, 'mx_Dialog_viewsource');
if (this.props.onFinished) this.props.onFinished();
},
onRedactClick: function() {
const ConfirmRedactDialog = sdk.getComponent("dialogs.ConfirmRedactDialog");
Modal.createDialog(ConfirmRedactDialog, {
onFinished: (proceed) => {
if (!proceed) return;
MatrixClientPeg.get().redactEvent(
this.props.mxEvent.getRoomId(), this.props.mxEvent.getId()
).catch(function(e) {
var ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
// display error message stating you couldn't delete this.
var code = e.errcode || e.statusCode;
Modal.createDialog(ErrorDialog, {
title: "Error",
description: "You cannot delete this message. (" + code + ")"
});
}).done();
},
}, 'mx_Dialog_confirmredact');
MatrixClientPeg.get().redactEvent(
this.props.mxEvent.getRoomId(), this.props.mxEvent.getId()
).done(function() {
// message should disappear by itself
}, function(e) {
var ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
// display error message stating you couldn't delete this.
var code = e.errcode || e.statusCode;
Modal.createDialog(ErrorDialog, {
title: "Error",
description: "You cannot delete this message. (" + code + ")"
});
});
if (this.props.onFinished) this.props.onFinished();
},
@@ -88,7 +74,7 @@ module.exports = React.createClass({
if (this.props.onFinished) this.props.onFinished();
},
closeMenu: function() {
onPermalinkClick: function() {
if (this.props.onFinished) this.props.onFinished();
},
@@ -111,12 +97,10 @@ module.exports = React.createClass({
var eventStatus = this.props.mxEvent.status;
var resendButton;
var viewSourceButton;
var viewClearSourceButton;
var redactButton;
var cancelButton;
var permalinkButton;
var unhidePreviewButton;
var externalURLButton;
if (eventStatus === 'not_sent') {
resendButton = (
@@ -126,7 +110,7 @@ module.exports = React.createClass({
);
}
if (!eventStatus && !this.props.mxEvent.isRedacted()) { // sent and not redacted
if (!eventStatus) { // sent
redactButton = (
<div className="mx_MessageContextMenu_field" onClick={this.onRedactClick}>
Redact
@@ -148,14 +132,6 @@ module.exports = React.createClass({
</div>
);
if (this.props.mxEvent.getType() !== this.props.mxEvent.getWireType()) {
viewClearSourceButton = (
<div className="mx_MessageContextMenu_field" onClick={this.onViewClearSourceClick}>
View Decrypted Source
</div>
);
}
if (this.props.eventTileOps) {
if (this.props.eventTileOps.isWidgetHidden()) {
unhidePreviewButton = (
@@ -170,7 +146,7 @@ module.exports = React.createClass({
permalinkButton = (
<div className="mx_MessageContextMenu_field">
<a href={ "https://matrix.to/#/" + this.props.mxEvent.getRoomId() +"/"+ this.props.mxEvent.getId() }
target="_blank" rel="noopener" onClick={ this.closeMenu }>Permalink</a>
target="_blank" onClick={ this.onPermalinkClick }>Permalink</a>
</div>
);
@@ -180,28 +156,15 @@ module.exports = React.createClass({
</div>
);
// Bridges can provide a 'external_url' to link back to the source.
if( typeof(this.props.mxEvent.event.content.external_url) === "string") {
externalURLButton = (
<div className="mx_MessageContextMenu_field">
<a href={ this.props.mxEvent.event.content.external_url }
rel="noopener" target="_blank" onClick={ this.closeMenu }>Source URL</a>
</div>
);
}
return (
<div>
{resendButton}
{redactButton}
{cancelButton}
{viewSourceButton}
{viewClearSourceButton}
{unhidePreviewButton}
{permalinkButton}
{UserSettingsStore.isFeatureEnabled('rich_text_editor') ? quoteButton : null}
{externalURLButton}
</div>
);
}

View File

@@ -0,0 +1,144 @@
/*
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.
*/
'use strict';
var q = require("q");
var React = require('react');
var classNames = require('classnames');
var RoomNotifs = require('matrix-react-sdk/lib/RoomNotifs');
var MatrixClientPeg = require('matrix-react-sdk/lib/MatrixClientPeg');
module.exports = React.createClass({
displayName: 'NotificationStateContextMenu',
propTypes: {
room: React.PropTypes.object.isRequired,
/* callback called when the menu is dismissed */
onFinished: React.PropTypes.func,
},
getInitialState() {
return {
roomNotifState: RoomNotifs.getRoomNotifsState(this.props.room.roomId),
}
},
componentWillMount: function() {
this._unmounted = false;
},
componentWillUnmount: function() {
this._unmounted = true;
},
_save: function(newState) {
const oldState = this.state.roomNotifState;
const roomId = this.props.room.roomId;
var cli = MatrixClientPeg.get();
if (cli.isGuest()) return;
this.setState({
roomNotifState: newState,
});
RoomNotifs.setRoomNotifsState(this.props.room.roomId, newState).done(() => {
// delay slightly so that the user can see their state change
// before closing the menu
return q.delay(500).then(() => {
if (this._unmounted) return;
// Close the context menu
if (this.props.onFinished) {
this.props.onFinished();
};
});
}, (error) => {
// TODO: some form of error notification to the user
// to inform them that their state change failed.
// For now we at least set the state back
if (this._unmounted) return;
this.setState({
roomNotifState: oldState,
});
});
},
_onClickAlertMe: function() {
this._save(RoomNotifs.ALL_MESSAGES_LOUD);
},
_onClickAllNotifs: function() {
this._save(RoomNotifs.ALL_MESSAGES);
},
_onClickMentions: function() {
this._save(RoomNotifs.MENTIONS_ONLY);
},
_onClickMute: function() {
this._save(RoomNotifs.MUTE);
},
render: function() {
var alertMeClasses = classNames({
'mx_NotificationStateContextMenu_field': true,
'mx_NotificationStateContextMenu_fieldSet': this.state.roomNotifState == RoomNotifs.ALL_MESSAGES_LOUD,
});
var allNotifsClasses = classNames({
'mx_NotificationStateContextMenu_field': true,
'mx_NotificationStateContextMenu_fieldSet': this.state.roomNotifState == RoomNotifs.ALL_MESSAGES,
});
var mentionsClasses = classNames({
'mx_NotificationStateContextMenu_field': true,
'mx_NotificationStateContextMenu_fieldSet': this.state.roomNotifState == RoomNotifs.MENTIONS_ONLY,
});
var muteNotifsClasses = classNames({
'mx_NotificationStateContextMenu_field': true,
'mx_NotificationStateContextMenu_fieldSet': this.state.roomNotifState == RoomNotifs.MUTE,
});
return (
<div>
<div className="mx_NotificationStateContextMenu_picker" >
<img src="img/notif-slider.svg" width="20" height="107" />
</div>
<div className={ alertMeClasses } onClick={this._onClickAlertMe} >
<img className="mx_NotificationStateContextMenu_activeIcon" src="img/notif-active.svg" width="12" height="12" />
<img className="mx_NotificationStateContextMenu_icon" src="img/icon-context-mute-off-copy.svg" width="16" height="12" />
All messages (loud)
</div>
<div className={ allNotifsClasses } onClick={this._onClickAllNotifs} >
<img className="mx_NotificationStateContextMenu_activeIcon" src="img/notif-active.svg" width="12" height="12" />
<img className="mx_NotificationStateContextMenu_icon" src="img/icon-context-mute-off.svg" width="16" height="12" />
All messages
</div>
<div className={ mentionsClasses } onClick={this._onClickMentions} >
<img className="mx_NotificationStateContextMenu_activeIcon" src="img/notif-active.svg" width="12" height="12" />
<img className="mx_NotificationStateContextMenu_icon" src="img/icon-context-mute-mentions.svg" width="16" height="12" />
Mentions only
</div>
<div className={ muteNotifsClasses } onClick={this._onClickMute} >
<img className="mx_NotificationStateContextMenu_activeIcon" src="img/notif-active.svg" width="12" height="12" />
<img className="mx_NotificationStateContextMenu_icon" src="img/icon-context-mute.svg" width="16" height="12" />
Mute
</div>
</div>
);
}
});

View File

@@ -0,0 +1,253 @@
/*
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.
*/
'use strict';
var q = require("q");
var React = require('react');
var classNames = require('classnames');
var MatrixClientPeg = require('matrix-react-sdk/lib/MatrixClientPeg');
var dis = require('matrix-react-sdk/lib/dispatcher');
var DMRoomMap = require('matrix-react-sdk/lib/utils/DMRoomMap');
var Rooms = require('matrix-react-sdk/lib/Rooms');
module.exports = React.createClass({
displayName: 'RoomTagContextMenu',
propTypes: {
room: React.PropTypes.object.isRequired,
/* callback called when the menu is dismissed */
onFinished: React.PropTypes.func,
},
getInitialState: function() {
const dmRoomMap = new DMRoomMap(MatrixClientPeg.get());
return {
isFavourite: this.props.room.tags.hasOwnProperty("m.favourite"),
isLowPriority: this.props.room.tags.hasOwnProperty("m.lowpriority"),
isDirectMessage: Boolean(dmRoomMap.getUserIdForRoomId(this.props.room.roomId)),
};
},
_toggleTag: function(tagNameOn, tagNameOff) {
var self = this;
const roomId = this.props.room.roomId;
var cli = MatrixClientPeg.get();
if (!cli.isGuest()) {
q.delay(500).then(function() {
if (tagNameOff !== null && tagNameOff !== undefined) {
cli.deleteRoomTag(roomId, tagNameOff).finally(function() {
// Close the context menu
if (self.props.onFinished) {
self.props.onFinished();
};
}).fail(function(err) {
var ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
Modal.createDialog(ErrorDialog, {
title: "Failed to remove tag " + tagNameOff + " from room",
description: err.toString()
});
});
}
if (tagNameOn !== null && tagNameOn !== undefined) {
// If the tag ordering meta data is required, it is added by
// the RoomSubList when it sorts its rooms
cli.setRoomTag(roomId, tagNameOn, {}).finally(function() {
// Close the context menu
if (self.props.onFinished) {
self.props.onFinished();
};
}).fail(function(err) {
var ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
Modal.createDialog(ErrorDialog, {
title: "Failed to add tag " + tagNameOn + " to room",
description: err.toString()
});
});
}
});
}
},
_onClickFavourite: function() {
// Tag room as 'Favourite'
if (!this.state.isFavourite && this.state.isLowPriority) {
this.setState({
isFavourite: true,
isLowPriority: false,
});
this._toggleTag("m.favourite", "m.lowpriority");
} else if (this.state.isFavourite) {
this.setState({isFavourite: false});
this._toggleTag(null, "m.favourite");
} else if (!this.state.isFavourite) {
this.setState({isFavourite: true});
this._toggleTag("m.favourite");
}
},
_onClickLowPriority: function() {
// Tag room as 'Low Priority'
if (!this.state.isLowPriority && this.state.isFavourite) {
this.setState({
isFavourite: false,
isLowPriority: true,
});
this._toggleTag("m.lowpriority", "m.favourite");
} else if (this.state.isLowPriority) {
this.setState({isLowPriority: false});
this._toggleTag(null, "m.lowpriority");
} else if (!this.state.isLowPriority) {
this.setState({isLowPriority: true});
this._toggleTag("m.lowpriority");
}
},
_onClickDM: function() {
const newIsDirectMessage = !this.state.isDirectMessage;
this.setState({
isDirectMessage: newIsDirectMessage,
});
if (MatrixClientPeg.get().isGuest()) return;
let newTarget;
if (newIsDirectMessage) {
const guessedTarget = Rooms.guessDMRoomTarget(
this.props.room,
this.props.room.getMember(MatrixClientPeg.get().credentials.userId),
);
newTarget = guessedTarget.userId;
} else {
newTarget = null;
}
// give some time for the user to see the icon change first, since
// this will hide the context menu once it completes
q.delay(500).done(() => {
return Rooms.setDMRoom(this.props.room.roomId, newTarget).finally(() => {
// Close the context menu
if (this.props.onFinished) {
this.props.onFinished();
};
}, (err) => {
var ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
Modal.createDialog(ErrorDialog, {
title: "Failed to set Direct Message status of room",
description: err.toString()
});
});
});
},
_onClickLeave: function() {
// Leave room
dis.dispatch({
action: 'leave_room',
room_id: this.props.room.roomId,
});
// Close the context menu
if (this.props.onFinished) {
this.props.onFinished();
};
},
_onClickForget: function() {
// FIXME: duplicated with RoomSettings (and dead code in RoomView)
MatrixClientPeg.get().forget(this.props.room.roomId).done(function() {
dis.dispatch({ action: 'view_next_room' });
}, function(err) {
var errCode = err.errcode || "unknown error code";
var ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
Modal.createDialog(ErrorDialog, {
title: "Error",
description: `Failed to forget room (${errCode})`
});
});
// Close the context menu
if (this.props.onFinished) {
this.props.onFinished();
};
},
render: function() {
const myUserId = MatrixClientPeg.get().credentials.userId;
const myMember = this.props.room.getMember(myUserId);
const favouriteClasses = classNames({
'mx_RoomTagContextMenu_field': true,
'mx_RoomTagContextMenu_fieldSet': this.state.isFavourite,
'mx_RoomTagContextMenu_fieldDisabled': false,
});
const lowPriorityClasses = classNames({
'mx_RoomTagContextMenu_field': true,
'mx_RoomTagContextMenu_fieldSet': this.state.isLowPriority,
'mx_RoomTagContextMenu_fieldDisabled': false,
});
const leaveClasses = classNames({
'mx_RoomTagContextMenu_field': true,
'mx_RoomTagContextMenu_fieldSet': false,
'mx_RoomTagContextMenu_fieldDisabled': false,
});
const dmClasses = classNames({
'mx_RoomTagContextMenu_field': true,
'mx_RoomTagContextMenu_fieldSet': this.state.isDirectMessage,
'mx_RoomTagContextMenu_fieldDisabled': false,
});
if (myMember && myMember.membership === "leave") {
return (
<div>
<div className={ leaveClasses } onClick={ this._onClickForget } >
<img className="mx_RoomTagContextMenu_icon" src="img/icon_context_delete.svg" width="15" height="15" />
Forget
</div>
</div>
);
}
return (
<div>
<div className={ favouriteClasses } onClick={this._onClickFavourite} >
<img className="mx_RoomTagContextMenu_icon" src="img/icon_context_fave.svg" width="15" height="15" />
<img className="mx_RoomTagContextMenu_icon_set" src="img/icon_context_fave_on.svg" width="15" height="15" />
Favourite
</div>
<div className={ lowPriorityClasses } onClick={this._onClickLowPriority} >
<img className="mx_RoomTagContextMenu_icon" src="img/icon_context_low.svg" width="15" height="15" />
<img className="mx_RoomTagContextMenu_icon_set" src="img/icon_context_low_on.svg" width="15" height="15" />
Low Priority
</div>
<div className={ dmClasses } onClick={this._onClickDM} >
<img className="mx_RoomTagContextMenu_icon" src="img/icon_context_person.svg" width="15" height="15" />
<img className="mx_RoomTagContextMenu_icon_set" src="img/icon_context_person_on.svg" width="15" height="15" />
Direct Chat
</div>
<hr className="mx_RoomTagContextMenu_separator" />
<div className={ leaveClasses } onClick={(myMember && myMember.membership === "join") ? this._onClickLeave : null} >
<img className="mx_RoomTagContextMenu_icon" src="img/icon_context_delete.svg" width="15" height="15" />
Leave
</div>
</div>
);
}
});

View File

@@ -1,392 +0,0 @@
/*
Copyright 2015, 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.
*/
'use strict';
import q from 'q';
import React from 'react';
import classNames from 'classnames';
import sdk from 'matrix-react-sdk';
import MatrixClientPeg from 'matrix-react-sdk/lib/MatrixClientPeg';
import dis from 'matrix-react-sdk/lib/dispatcher';
import DMRoomMap from 'matrix-react-sdk/lib/utils/DMRoomMap';
import * as Rooms from 'matrix-react-sdk/lib/Rooms';
import * as RoomNotifs from 'matrix-react-sdk/lib/RoomNotifs';
import Modal from 'matrix-react-sdk/lib/Modal';
module.exports = React.createClass({
displayName: 'RoomTileContextMenu',
propTypes: {
room: React.PropTypes.object.isRequired,
/* callback called when the menu is dismissed */
onFinished: React.PropTypes.func,
},
getInitialState() {
const dmRoomMap = new DMRoomMap(MatrixClientPeg.get());
return {
roomNotifState: RoomNotifs.getRoomNotifsState(this.props.room.roomId),
isFavourite: this.props.room.tags.hasOwnProperty("m.favourite"),
isLowPriority: this.props.room.tags.hasOwnProperty("m.lowpriority"),
isDirectMessage: Boolean(dmRoomMap.getUserIdForRoomId(this.props.room.roomId)),
}
},
componentWillMount: function() {
this._unmounted = false;
},
componentWillUnmount: function() {
this._unmounted = true;
},
_toggleTag: function(tagNameOn, tagNameOff) {
var self = this;
const roomId = this.props.room.roomId;
var cli = MatrixClientPeg.get();
if (!cli.isGuest()) {
q.delay(500).then(function() {
if (tagNameOff !== null && tagNameOff !== undefined) {
cli.deleteRoomTag(roomId, tagNameOff).finally(function() {
// Close the context menu
if (self.props.onFinished) {
self.props.onFinished();
};
}).fail(function(err) {
var ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
Modal.createDialog(ErrorDialog, {
title: "Failed to remove tag " + tagNameOff + " from room",
description: ((err && err.message) ? err.message : "Operation failed"),
});
});
}
if (tagNameOn !== null && tagNameOn !== undefined) {
// If the tag ordering meta data is required, it is added by
// the RoomSubList when it sorts its rooms
cli.setRoomTag(roomId, tagNameOn, {}).finally(function() {
// Close the context menu
if (self.props.onFinished) {
self.props.onFinished();
};
}).fail(function(err) {
var ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
Modal.createDialog(ErrorDialog, {
title: "Failed to add tag " + tagNameOn + " to room",
description: ((err && err.message) ? err.message : "Operation failed"),
});
});
}
});
}
},
_onClickFavourite: function() {
// Tag room as 'Favourite'
if (!this.state.isFavourite && this.state.isLowPriority) {
this.setState({
isFavourite: true,
isLowPriority: false,
});
this._toggleTag("m.favourite", "m.lowpriority");
} else if (this.state.isFavourite) {
this.setState({isFavourite: false});
this._toggleTag(null, "m.favourite");
} else if (!this.state.isFavourite) {
this.setState({isFavourite: true});
this._toggleTag("m.favourite");
}
},
_onClickLowPriority: function() {
// Tag room as 'Low Priority'
if (!this.state.isLowPriority && this.state.isFavourite) {
this.setState({
isFavourite: false,
isLowPriority: true,
});
this._toggleTag("m.lowpriority", "m.favourite");
} else if (this.state.isLowPriority) {
this.setState({isLowPriority: false});
this._toggleTag(null, "m.lowpriority");
} else if (!this.state.isLowPriority) {
this.setState({isLowPriority: true});
this._toggleTag("m.lowpriority");
}
},
_onClickDM: function() {
const newIsDirectMessage = !this.state.isDirectMessage;
this.setState({
isDirectMessage: newIsDirectMessage,
});
if (MatrixClientPeg.get().isGuest()) return;
Rooms.guessAndSetDMRoom(
this.props.room, newIsDirectMessage
).delay(500).finally(() => {
// Close the context menu
if (this.props.onFinished) {
this.props.onFinished();
};
}, (err) => {
var ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
Modal.createDialog(ErrorDialog, {
title: "Failed to set Direct Message status of room",
description: ((err && err.message) ? err.message : "Operation failed"),
});
});
},
_onClickLeave: function() {
// Leave room
dis.dispatch({
action: 'leave_room',
room_id: this.props.room.roomId,
});
// Close the context menu
if (this.props.onFinished) {
this.props.onFinished();
};
},
_onClickReject: function() {
dis.dispatch({
action: 'reject_invite',
room_id: this.props.room.roomId,
});
// Close the context menu
if (this.props.onFinished) {
this.props.onFinished();
};
},
_onClickForget: function() {
// FIXME: duplicated with RoomSettings (and dead code in RoomView)
MatrixClientPeg.get().forget(this.props.room.roomId).done(function() {
dis.dispatch({ action: 'view_next_room' });
}, function(err) {
var errCode = err.errcode || "unknown error code";
var ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
Modal.createDialog(ErrorDialog, {
title: `Failed to forget room (${errCode})`,
description: ((err && err.message) ? err.message : "Operation failed"),
});
});
// Close the context menu
if (this.props.onFinished) {
this.props.onFinished();
};
},
_saveNotifState: function(newState) {
const oldState = this.state.roomNotifState;
const roomId = this.props.room.roomId;
var cli = MatrixClientPeg.get();
if (cli.isGuest()) return;
this.setState({
roomNotifState: newState,
});
RoomNotifs.setRoomNotifsState(this.props.room.roomId, newState).done(() => {
// delay slightly so that the user can see their state change
// before closing the menu
return q.delay(500).then(() => {
if (this._unmounted) return;
// Close the context menu
if (this.props.onFinished) {
this.props.onFinished();
};
});
}, (error) => {
// TODO: some form of error notification to the user
// to inform them that their state change failed.
// For now we at least set the state back
if (this._unmounted) return;
this.setState({
roomNotifState: oldState,
});
});
},
_onClickAlertMe: function() {
this._saveNotifState(RoomNotifs.ALL_MESSAGES_LOUD);
},
_onClickAllNotifs: function() {
this._saveNotifState(RoomNotifs.ALL_MESSAGES);
},
_onClickMentions: function() {
this._saveNotifState(RoomNotifs.MENTIONS_ONLY);
},
_onClickMute: function() {
this._saveNotifState(RoomNotifs.MUTE);
},
_renderNotifMenu: function() {
var alertMeClasses = classNames({
'mx_RoomTileContextMenu_notif_field': true,
'mx_RoomTileContextMenu_notif_fieldSet': this.state.roomNotifState == RoomNotifs.ALL_MESSAGES_LOUD,
});
var allNotifsClasses = classNames({
'mx_RoomTileContextMenu_notif_field': true,
'mx_RoomTileContextMenu_notif_fieldSet': this.state.roomNotifState == RoomNotifs.ALL_MESSAGES,
});
var mentionsClasses = classNames({
'mx_RoomTileContextMenu_notif_field': true,
'mx_RoomTileContextMenu_notif_fieldSet': this.state.roomNotifState == RoomNotifs.MENTIONS_ONLY,
});
var muteNotifsClasses = classNames({
'mx_RoomTileContextMenu_notif_field': true,
'mx_RoomTileContextMenu_notif_fieldSet': this.state.roomNotifState == RoomNotifs.MUTE,
});
return (
<div>
<div className="mx_RoomTileContextMenu_notif_picker" >
<img src="img/notif-slider.svg" width="20" height="107" />
</div>
<div className={ alertMeClasses } onClick={this._onClickAlertMe} >
<img className="mx_RoomTileContextMenu_notif_activeIcon" src="img/notif-active.svg" width="12" height="12" />
<img className="mx_RoomTileContextMenu_notif_icon mx_filterFlipColor" src="img/icon-context-mute-off-copy.svg" width="16" height="12" />
All messages (loud)
</div>
<div className={ allNotifsClasses } onClick={this._onClickAllNotifs} >
<img className="mx_RoomTileContextMenu_notif_activeIcon" src="img/notif-active.svg" width="12" height="12" />
<img className="mx_RoomTileContextMenu_notif_icon mx_filterFlipColor" src="img/icon-context-mute-off.svg" width="16" height="12" />
All messages
</div>
<div className={ mentionsClasses } onClick={this._onClickMentions} >
<img className="mx_RoomTileContextMenu_notif_activeIcon" src="img/notif-active.svg" width="12" height="12" />
<img className="mx_RoomTileContextMenu_notif_icon mx_filterFlipColor" src="img/icon-context-mute-mentions.svg" width="16" height="12" />
Mentions only
</div>
<div className={ muteNotifsClasses } onClick={this._onClickMute} >
<img className="mx_RoomTileContextMenu_notif_activeIcon" src="img/notif-active.svg" width="12" height="12" />
<img className="mx_RoomTileContextMenu_notif_icon mx_filterFlipColor" src="img/icon-context-mute.svg" width="16" height="12" />
Mute
</div>
</div>
);
},
_renderLeaveMenu: function(membership) {
if (!membership) {
return null;
}
let leaveClickHandler = null;
let leaveText = null;
switch (membership) {
case "join":
leaveClickHandler = this._onClickLeave;
leaveText = "Leave";
break;
case "leave":
case "ban":
leaveClickHandler = this._onClickForget;
leaveText = "Forget";
break;
case "invite":
leaveClickHandler = this._onClickReject;
leaveText = "Reject";
break;
}
return (
<div>
<div className="mx_RoomTileContextMenu_leave" onClick={ leaveClickHandler } >
<img className="mx_RoomTileContextMenu_tag_icon" src="img/icon_context_delete.svg" width="15" height="15" />
{ leaveText }
</div>
</div>
);
},
_renderRoomTagMenu: function() {
const favouriteClasses = classNames({
'mx_RoomTileContextMenu_tag_field': true,
'mx_RoomTileContextMenu_tag_fieldSet': this.state.isFavourite,
'mx_RoomTileContextMenu_tag_fieldDisabled': false,
});
const lowPriorityClasses = classNames({
'mx_RoomTileContextMenu_tag_field': true,
'mx_RoomTileContextMenu_tag_fieldSet': this.state.isLowPriority,
'mx_RoomTileContextMenu_tag_fieldDisabled': false,
});
const dmClasses = classNames({
'mx_RoomTileContextMenu_tag_field': true,
'mx_RoomTileContextMenu_tag_fieldSet': this.state.isDirectMessage,
'mx_RoomTileContextMenu_tag_fieldDisabled': false,
});
return (
<div>
<div className={ favouriteClasses } onClick={this._onClickFavourite} >
<img className="mx_RoomTileContextMenu_tag_icon" src="img/icon_context_fave.svg" width="15" height="15" />
<img className="mx_RoomTileContextMenu_tag_icon_set" src="img/icon_context_fave_on.svg" width="15" height="15" />
Favourite
</div>
<div className={ lowPriorityClasses } onClick={this._onClickLowPriority} >
<img className="mx_RoomTileContextMenu_tag_icon" src="img/icon_context_low.svg" width="15" height="15" />
<img className="mx_RoomTileContextMenu_tag_icon_set" src="img/icon_context_low_on.svg" width="15" height="15" />
Low Priority
</div>
<div className={ dmClasses } onClick={this._onClickDM} >
<img className="mx_RoomTileContextMenu_tag_icon" src="img/icon_context_person.svg" width="15" height="15" />
<img className="mx_RoomTileContextMenu_tag_icon_set" src="img/icon_context_person_on.svg" width="15" height="15" />
Direct Chat
</div>
</div>
);
},
render: function() {
const myMember = this.props.room.getMember(
MatrixClientPeg.get().credentials.userId
);
// Can't set notif level or tags on non-join rooms
if (myMember.membership !== 'join') {
return this._renderLeaveMenu(myMember.membership);
}
return (
<div>
{ this._renderNotifMenu() }
<hr className="mx_RoomTileContextMenu_separator" />
{ this._renderLeaveMenu(myMember.membership) }
<hr className="mx_RoomTileContextMenu_separator" />
{ this._renderRoomTagMenu() }
</div>
);
}
});

View File

@@ -1,166 +0,0 @@
/*
Copyright 2017 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.
*/
import React from 'react';
import sdk from 'matrix-react-sdk';
import SdkConfig from 'matrix-react-sdk/lib/SdkConfig';
export default class BugReportDialog extends React.Component {
constructor(props, context) {
super(props, context);
this.state = {
sendLogs: true,
busy: false,
err: null,
text: "",
progress: null,
};
this._unmounted = false;
this._onSubmit = this._onSubmit.bind(this);
this._onCancel = this._onCancel.bind(this);
this._onTextChange = this._onTextChange.bind(this);
this._onSendLogsChange = this._onSendLogsChange.bind(this);
this._sendProgressCallback = this._sendProgressCallback.bind(this);
}
componentWillUnmount() {
this._unmounted = true;
}
_onCancel(ev) {
this.props.onFinished(false);
}
_onSubmit(ev) {
const sendLogs = this.state.sendLogs;
const userText = this.state.text;
if (!sendLogs && userText.trim().length === 0) {
this.setState({
err: "Please describe the bug and/or send logs.",
});
return;
}
this.setState({ busy: true, progress: null, err: null });
this._sendProgressCallback("Loading bug report module");
require(['../../../vector/submit-rageshake'], (s) => {
s(SdkConfig.get().bug_report_endpoint_url, {
userText: userText,
sendLogs: sendLogs,
progressCallback: this._sendProgressCallback,
}).then(() => {
if (!this._unmounted) {
this.setState({ busy: false, progress: null });
this.props.onFinished(false);
}
}, (err) => {
if (!this._unmounted) {
this.setState({
busy: false, progress: null,
err: `Failed to send report: ${err.message}`,
});
}
});
});
}
_onTextChange(ev) {
this.setState({ text: ev.target.value });
}
_onSendLogsChange(ev) {
this.setState({ sendLogs: ev.target.checked });
}
_sendProgressCallback(progress) {
if (this._unmounted) {
return;
}
this.setState({progress: progress});
}
render() {
const Loader = sdk.getComponent("elements.Spinner");
let error = null;
if (this.state.err) {
error = <div className="error">
{this.state.err}
</div>;
}
let cancelButton = null;
if (!this.state.busy) {
cancelButton = <button onClick={this._onCancel}>
Cancel
</button>;
}
let progress = null;
if (this.state.busy) {
progress = (
<div className="progress">
<Loader />
{this.state.progress} ...
</div>
);
}
return (
<div className="mx_BugReportDialog">
<div className="mx_Dialog_title">
Report a bug
</div>
<div className="mx_Dialog_content">
<p>Please describe the bug. What did you do?
What did you expect to happen?
What actually happened?</p>
<textarea
className="mx_BugReportDialog_input"
rows={5}
onChange={this._onTextChange}
value={this.state.text}
placeholder="Describe your problem here."
/>
<p>In order to diagnose problems, logs from this client will be sent with
this bug report.
If you would prefer to only send the text above, please untick:</p>
<input type="checkbox" checked={this.state.sendLogs}
onChange={this._onSendLogsChange} id="mx_BugReportDialog_logs"/>
<label htmlFor="mx_BugReportDialog_logs">Send logs</label>
{progress}
{error}
</div>
<div className="mx_Dialog_buttons">
<button
className="mx_Dialog_primary danger"
onClick={this._onSubmit}
autoFocus={true}
disabled={this.state.busy}
>
Send
</button>
{cancelButton}
</div>
</div>
);
}
}
BugReportDialog.propTypes = {
onFinished: React.PropTypes.func.isRequired,
};

View File

@@ -31,10 +31,9 @@ export default class ChangelogDialog extends React.Component {
const version = this.props.newVersion.split('-');
const version2 = this.props.version.split('-');
if(version == null || version2 == null) return;
// parse versions of form: [vectorversion]-react-[react-sdk-version]-js-[js-sdk-version]
for(let i=0; i<REPOS.length; i++) {
const oldVersion = version2[2*i];
const newVersion = version[2*i];
const oldVersion = version2[2*i+1];
const newVersion = version[2*i+1];
request(`https://api.github.com/repos/${REPOS[i]}/compare/${oldVersion}...${newVersion}`, (a, b, body) => {
if(body == null) return;
this.setState({[REPOS[i]]: JSON.parse(body).commits});
@@ -45,7 +44,7 @@ export default class ChangelogDialog extends React.Component {
_elementsForCommit(commit) {
return (
<li key={commit.sha} className="mx_ChangelogDialog_li">
<a href={commit.html_url} target="_blank" rel="noopener">
<a href={commit.html_url} target="_blank" ref="noopener">
{commit.commit.message}
</a>
</li>

View File

@@ -16,9 +16,6 @@ limitations under the License.
import React from 'react';
import MatrixClientPeg from 'matrix-react-sdk/lib/MatrixClientPeg';
import {instanceForInstanceId} from '../../../utils/DirectoryUtils';
const DEFAULT_ICON_URL = "img/network-matrix.svg";
export default class NetworkDropdown extends React.Component {
constructor(props) {
@@ -38,11 +35,20 @@ export default class NetworkDropdown extends React.Component {
this.inputTextBox = null;
const server = MatrixClientPeg.getHomeServerName();
let defaultNetwork = null;
if (
this.props.config.serverConfig &&
this.props.config.serverConfig[server] &&
this.props.config.serverConfig[server].networks &&
this.props.config.serverConfig[server].networks.indexOf('_matrix') > -1
) {
defaultNetwork = '_matrix';
}
this.state = {
expanded: false,
selectedServer: server,
selectedInstance: null,
includeAllNetworks: false,
selectedNetwork: defaultNetwork,
};
}
@@ -52,7 +58,7 @@ export default class NetworkDropdown extends React.Component {
document.addEventListener('click', this.onDocumentClick, false);
// fire this now so the defaults can be set up
this.props.onOptionChange(this.state.selectedServer, this.state.selectedInstance, this.state.includeAllNetworks);
this.props.onOptionChange(this.state.selectedServer, this.state.selectedNetwork);
}
componentWillUnmount() {
@@ -92,14 +98,13 @@ export default class NetworkDropdown extends React.Component {
ev.preventDefault();
}
onMenuOptionClick(server, instance, includeAll) {
onMenuOptionClick(server, network, ev) {
this.setState({
expanded: false,
selectedServer: server,
selectedInstanceId: instance ? instance.instance_id : null,
includeAll: includeAll,
selectedNetwork: network,
});
this.props.onOptionChange(server, instance ? instance.instance_id : null, includeAll);
this.props.onOptionChange(server, network);
}
onInputKeyUp(e) {
@@ -139,22 +144,11 @@ export default class NetworkDropdown extends React.Component {
servers.unshift(MatrixClientPeg.getHomeServerName());
}
// For our own HS, we can use the instance_ids given in the third party protocols
// response to get the server to filter the room list by network for us.
// We can't get thirdparty protocols for remote server yet though, so for those
// we can only show the default room list.
for (const server of servers) {
options.push(this._makeMenuOption(server, null, true));
if (server == MatrixClientPeg.getHomeServerName()) {
options.push(this._makeMenuOption(server, null, false));
if (this.props.protocols) {
for (const proto of Object.keys(this.props.protocols)) {
if (!this.props.protocols[proto].instances) continue;
for (const instance of this.props.protocols[proto].instances) {
if (!instance.instance_id) continue;
options.push(this._makeMenuOption(server, instance, false));
}
}
options.push(this._makeMenuOption(server, null));
if (this.props.config.serverConfig && this.props.config.serverConfig[server] && this.props.config.serverConfig[server].networks) {
for (const network of this.props.config.serverConfig[server].networks) {
options.push(this._makeMenuOption(server, network));
}
}
}
@@ -162,39 +156,50 @@ export default class NetworkDropdown extends React.Component {
return options;
}
_makeMenuOption(server, instance, includeAll, handleClicks) {
if (handleClicks === undefined) handleClicks = true;
_makeMenuOption(server, network, wire_onclick) {
if (wire_onclick === undefined) wire_onclick = true;
let icon;
let name;
let span_class;
let key;
if (!instance && includeAll) {
key = server;
if (network === null) {
name = server;
span_class = 'mx_NetworkDropdown_menu_all';
} else if (!instance) {
key = server + '_all';
} else if (network == '_matrix') {
name = 'Matrix';
icon = <img src="img/network-matrix.svg" />;
icon = <img src="img/network-matrix.svg" width="16" height="16" />;
span_class = 'mx_NetworkDropdown_menu_network';
} else {
key = server + '_inst_' + instance.instance_id;
const imgUrl = instance.icon ?
MatrixClientPeg.get().mxcUrlToHttp(instance.icon, 25, 25, 'crop', true) :
DEFAULT_ICON_URL;
icon = <img src={imgUrl} />;
name = instance.desc;
if (this.props.config.networks[network] === undefined) {
throw new Error(network + ' network missing from config');
}
if (this.props.config.networks[network].name) {
name = this.props.config.networks[network].name;
} else {
name = network;
}
if (this.props.config.networks[network].icon) {
// omit height here so if people define a non-square logo in the config, it
// will keep the aspect when it scales
icon = <img src={this.props.config.networks[network].icon} width="16" />;
} else {
icon = <img src={iconPath} width="16" height="16" />;
}
span_class = 'mx_NetworkDropdown_menu_network';
}
const click_handler = handleClicks ? this.onMenuOptionClick.bind(this, server, instance, includeAll) : null;
const click_handler = wire_onclick ? this.onMenuOptionClick.bind(this, server, network) : null;
let key = server;
if (network !== null) {
key += '_' + network;
}
return <div key={key} className="mx_NetworkDropdown_networkoption" onClick={click_handler}>
{icon}
<span className="mx_NetworkDropdown_menu_network">{name}</span>
</div>
<span className={span_class}>{name}</span>
</div>;
}
render() {
@@ -211,9 +216,8 @@ export default class NetworkDropdown extends React.Component {
placeholder="matrix.org" // 'matrix.org' as an example of an HS name
/>
} else {
const instance = instanceForInstanceId(this.props.protocols, this.state.selectedInstanceId);
current_value = this._makeMenuOption(
this.state.selectedServer, instance, this.state.includeAll, false
this.state.selectedServer, this.state.selectedNetwork, false
);
}
@@ -229,12 +233,12 @@ export default class NetworkDropdown extends React.Component {
NetworkDropdown.propTypes = {
onOptionChange: React.PropTypes.func.isRequired,
protocols: React.PropTypes.object,
// The room directory config. May have a 'servers' key that is a list of server names to include in the dropdown
config: React.PropTypes.object,
};
NetworkDropdown.defaultProps = {
protocols: {},
config: {},
config: {
networks: [],
}
};

View File

@@ -22,9 +22,6 @@ var MatrixClientPeg = require('matrix-react-sdk/lib/MatrixClientPeg');
var DateUtils = require('matrix-react-sdk/lib/DateUtils');
var filesize = require('filesize');
var AccessibleButton = require('matrix-react-sdk/lib/components/views/elements/AccessibleButton');
const Modal = require('matrix-react-sdk/lib/Modal');
const sdk = require('matrix-react-sdk');
module.exports = React.createClass({
displayName: 'ImageView',
@@ -64,23 +61,19 @@ module.exports = React.createClass({
},
onRedactClick: function() {
const ConfirmRedactDialog = sdk.getComponent("dialogs.ConfirmRedactDialog");
Modal.createDialog(ConfirmRedactDialog, {
onFinished: (proceed) => {
if (!proceed) return;
var self = this;
MatrixClientPeg.get().redactEvent(
this.props.mxEvent.getRoomId(), this.props.mxEvent.getId()
).catch(function(e) {
var ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
// display error message stating you couldn't delete this.
var code = e.errcode || e.statusCode;
Modal.createDialog(ErrorDialog, {
title: "Error",
description: "You cannot delete this image. (" + code + ")"
});
}).done();
}
var self = this;
MatrixClientPeg.get().redactEvent(
this.props.mxEvent.getRoomId(), this.props.mxEvent.getId()
).done(function() {
if (self.props.onFinished) self.props.onFinished();
}, function(e) {
var ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
// display error message stating you couldn't delete this.
var code = e.errcode || e.statusCode;
Modal.createDialog(ErrorDialog, {
title: "Error",
description: "You cannot delete this image. (" + code + ")"
});
});
},
@@ -169,14 +162,14 @@ module.exports = React.createClass({
<img src={this.props.src} style={style}/>
<div className="mx_ImageView_labelWrapper">
<div className="mx_ImageView_label">
<AccessibleButton className="mx_ImageView_cancel" onClick={ this.props.onFinished }><img src="img/cancel-white.svg" width="18" height="18" alt="Close"/></AccessibleButton>
<img className="mx_ImageView_cancel" src="img/cancel-white.svg" width="18" height="18" alt="Close" onClick={ this.props.onFinished }/>
<div className="mx_ImageView_shim">
</div>
<div className="mx_ImageView_name">
{ this.getName() }
</div>
{ eventMeta }
<a className="mx_ImageView_link" href={ this.props.src } download={ this.props.name } target="_blank" rel="noopener">
<a className="mx_ImageView_link" href={ this.props.src } target="_blank" rel="noopener">
<div className="mx_ImageView_download">
Download this file<br/>
<span className="mx_ImageView_size">{ size_res }</span>

View File

@@ -19,7 +19,6 @@ limitations under the License.
var React = require('react');
var Notifier = require("matrix-react-sdk/lib/Notifier");
var sdk = require('matrix-react-sdk')
var AccessibleButton = require('matrix-react-sdk/lib/components/views/elements/AccessibleButton');
module.exports = React.createClass({
displayName: 'MatrixToolbar',
@@ -39,7 +38,7 @@ module.exports = React.createClass({
<div className="mx_MatrixToolbar_content">
You are not receiving desktop notifications. <a className="mx_MatrixToolbar_link" onClick={ this.onClick }>Enable them now</a>
</div>
<AccessibleButton className="mx_MatrixToolbar_close" onClick={ this.hideToolbar } ><img src="img/cancel.svg" width="18" height="18" /></AccessibleButton>
<div className="mx_MatrixToolbar_close"><img src="img/cancel.svg" width="18" height="18" onClick={ this.hideToolbar } /></div>
</div>
);
}

View File

@@ -16,79 +16,50 @@ limitations under the License.
'use strict';
import React from 'react';
import sdk from 'matrix-react-sdk';
var React = require('react');
var sdk = require('matrix-react-sdk');
import Modal from 'matrix-react-sdk/lib/Modal';
import PlatformPeg from 'matrix-react-sdk/lib/PlatformPeg';
/**
* Check a version string is compatible with the Changelog
* dialog ([vectorversion]-react-[react-sdk-version]-js-[js-sdk-version])
* dialog
*/
function checkVersion(ver) {
const parts = ver.split('-');
return parts.length == 5 && parts[1] == 'react' && parts[3] == 'js';
return parts[0] == 'vector' && parts[2] == 'react' && parts[4] == 'js';
}
export default React.createClass({
propTypes: {
version: React.PropTypes.string.isRequired,
newVersion: React.PropTypes.string.isRequired,
releaseNotes: React.PropTypes.string,
},
displayReleaseNotes: function(releaseNotes) {
const QuestionDialog = sdk.getComponent('dialogs.QuestionDialog');
Modal.createDialog(QuestionDialog, {
title: "What's New",
description: <pre className="changelog_text">{releaseNotes}</pre>,
button: "Update",
onFinished: (update) => {
if(update && PlatformPeg.get()) {
PlatformPeg.get().installUpdate();
}
}
});
},
displayChangelog: function() {
export default function NewVersionBar(props) {
const onChangelogClicked = () => {
const ChangelogDialog = sdk.getComponent('dialogs.ChangelogDialog');
Modal.createDialog(ChangelogDialog, {
version: this.props.version,
newVersion: this.props.newVersion,
version: props.version,
newVersion: props.newVersion,
onFinished: (update) => {
if(update && PlatformPeg.get()) {
PlatformPeg.get().installUpdate();
if(update) {
window.location.reload();
}
}
});
},
};
onUpdateClicked: function() {
PlatformPeg.get().installUpdate();
},
render: function() {
let action_button;
// If we have release notes to display, we display them. Otherwise,
// we display the Changelog Dialog which takes two versions and
// automatically tells you what's changed (provided the versions
// are in the right format)
if (this.props.releaseNotes) {
action_button = <button className="mx_MatrixToolbar_action" onClick={this.displayReleaseNotes}>What's new?</button>;
} else if (checkVersion(this.props.version) && checkVersion(this.props.newVersion)) {
action_button = <button className="mx_MatrixToolbar_action" onClick={this.displayChangelog}>What's new?</button>;
} else if (PlatformPeg.get()) {
action_button = <button className="mx_MatrixToolbar_action" onClick={this.onUpdateClicked}>Update</button>;
}
return (
<div className="mx_MatrixToolbar">
<img className="mx_MatrixToolbar_warning" src="img/warning.svg" width="24" height="23" alt="/!\"/>
<div className="mx_MatrixToolbar_content">
A new version of Riot is available.
</div>
{action_button}
</div>
);
let changelog_button;
if (checkVersion(props.version) && checkVersion(props.newVersion)) {
changelog_button = <button className="mx_MatrixToolbar_action" onClick={onChangelogClicked}>Changelog</button>;
}
});
return (
<div className="mx_MatrixToolbar">
<img className="mx_MatrixToolbar_warning" src="img/warning.svg" width="24" height="23" alt="/!\"/>
<div className="mx_MatrixToolbar_content">
A new version of Riot is available. Refresh your browser.
</div>
{changelog_button}
</div>
);
}
NewVersionBar.propTypes = {
version: React.PropTypes.string.isRequired,
newVersion: React.PropTypes.string.isRequired,
};

View File

@@ -18,22 +18,16 @@ limitations under the License.
var React = require('react');
const i = [1, 2, 3, 4, 5][Math.floor(Math.random() * 5)];
const DEFAULT_LOGO_URI = "img/logos/riot-logo-" + i + ".svg";
module.exports = React.createClass({
displayName: 'VectorLoginHeader',
statics: {
replaces: 'LoginHeader',
},
propTypes: {
icon: React.PropTypes.string,
},
render: function() {
return (
<div className="mx_Login_logo">
<img src={this.props.icon || DEFAULT_LOGO_URI} alt="Riot"/>
<img src="img/logo.png" width="195" height="195" alt="Riot"/>
</div>
);
}

View File

@@ -25,7 +25,7 @@ module.exports = React.createClass({
render: function() {
var date = new Date(this.props.ts);
return (
<span className="mx_MessageTimestamp" title={ DateUtils.formatDate(date) }>
<span className="mx_MessageTimestamp">
{ DateUtils.formatTime(date) }
</span>
);

View File

@@ -16,16 +16,14 @@ limitations under the License.
'use strict';
import React from 'react';
import {DragSource} from 'react-dnd';
import {DropTarget} from 'react-dnd';
var React = require('react');
var DragSource = require('react-dnd').DragSource;
var DropTarget = require('react-dnd').DropTarget;
import dis from 'matrix-react-sdk/lib/dispatcher';
import MatrixClientPeg from 'matrix-react-sdk/lib/MatrixClientPeg';
import sdk from 'matrix-react-sdk';
import RoomTile from 'matrix-react-sdk/lib/components/views/rooms/RoomTile';
import * as Rooms from 'matrix-react-sdk/lib/Rooms';
import Modal from 'matrix-react-sdk/lib/Modal';
var dis = require("matrix-react-sdk/lib/dispatcher");
var MatrixClientPeg = require('matrix-react-sdk/lib/MatrixClientPeg');
var sdk = require('matrix-react-sdk');
var RoomTile = require('matrix-react-sdk/lib/components/views/rooms/RoomTile');
/**
* Defines a new Component, DNDRoomTile that wraps RoomTile, making it draggable.
@@ -74,49 +72,21 @@ var roomTileSource = {
item.targetList.forceUpdate(); // as we're not using state
}
const prevTag = item.originalList.props.tagName;
const newTag = item.targetList.props.tagName;
if (monitor.didDrop() && item.targetList.props.editable) {
// Evil hack to get DMs behaving
if ((prevTag === undefined && newTag === 'im.vector.fake.direct') ||
(prevTag === 'im.vector.fake.direct' && newTag === undefined)
) {
Rooms.guessAndSetDMRoom(
item.room, newTag === 'im.vector.fake.direct',
).done(() => {
item.originalList.removeRoomTile(item.room);
}, (err) => {
const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
console.error("Failed to set direct chat tag " + err);
Modal.createDialog(ErrorDialog, {
title: "Failed to set direct chat tag",
description: ((err && err.message) ? err.message : "Operation failed"),
});
});
return;
}
// More evilness: We will still be dealing with moving to favourites/low prio,
// but we avoid ever doing a request with 'im.vector.fake.direct`.
// if we moved lists, remove the old tag
if (prevTag && prevTag !== 'im.vector.fake.direct' &&
item.targetList !== item.originalList
) {
if (item.targetList !== item.originalList && item.originalList.props.tagName) {
// commented out attempts to set a spinner on our target component as component is actually
// the original source component being dragged, not our target. To fix we just need to
// move all of this to endDrop in the target instead. FIXME later.
//component.state.set({ spinner: component.state.spinner ? component.state.spinner++ : 1 });
MatrixClientPeg.get().deleteRoomTag(item.room.roomId, prevTag).finally(function() {
MatrixClientPeg.get().deleteRoomTag(item.room.roomId, item.originalList.props.tagName).finally(function() {
//component.state.set({ spinner: component.state.spinner-- });
}).fail(function(err) {
var ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
console.error("Failed to remove tag " + prevTag + " from room: " + err);
Modal.createDialog(ErrorDialog, {
title: "Failed to remove tag " + prevTag + " from room",
description: ((err && err.message) ? err.message : "Operation failed"),
title: "Failed to remove tag " + item.originalList.props.tagName + " from room",
description: err.toString()
});
});
}
@@ -127,18 +97,15 @@ var roomTileSource = {
}
// if we moved lists or the ordering changed, add the new tag
if (newTag && newTag !== 'im.vector.fake.direct' &&
(item.targetList !== item.originalList || newOrder)
) {
if (item.targetList.props.tagName && (item.targetList !== item.originalList || newOrder)) {
//component.state.set({ spinner: component.state.spinner ? component.state.spinner++ : 1 });
MatrixClientPeg.get().setRoomTag(item.room.roomId, newTag, newOrder).finally(function() {
MatrixClientPeg.get().setRoomTag(item.room.roomId, item.targetList.props.tagName, newOrder).finally(function() {
//component.state.set({ spinner: component.state.spinner-- });
}).fail(function(err) {
var ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
console.error("Failed to add tag " + newTag + " to room: " + err);
Modal.createDialog(ErrorDialog, {
title: "Failed to add tag " + newTag + " to room",
description: ((err && err.message) ? err.message : "Operation failed"),
title: "Failed to add tag " + item.targetList.props.tagName + " to room",
description: err.toString()
});
});
}

View File

@@ -22,12 +22,21 @@ module.exports = React.createClass({
displayName: 'RoomDropTarget',
render: function() {
return (
<div className="mx_RoomDropTarget">
<div className="mx_RoomDropTarget_label">
{ this.props.label }
if (this.props.placeholder) {
return (
<div className="mx_RoomDropTarget mx_RoomDropTarget_placeholder">
</div>
</div>
);
);
}
else {
return (
<div className="mx_RoomDropTarget">
<div className="mx_RoomDropTarget_avatar"></div>
<div className="mx_RoomDropTarget_label">
{ this.props.label }
</div>
</div>
);
}
}
});

View File

@@ -20,7 +20,6 @@ var React = require('react');
var MatrixClientPeg = require('matrix-react-sdk/lib/MatrixClientPeg');
var sdk = require('matrix-react-sdk');
var classNames = require('classnames');
var AccessibleButton = require('matrix-react-sdk/lib/components/views/elements/AccessibleButton');
module.exports = React.createClass({
displayName: 'SearchBar',
@@ -58,12 +57,12 @@ module.exports = React.createClass({
var allRoomsClasses = classNames({ mx_SearchBar_button : true, mx_SearchBar_unselected : this.state.scope !== 'All' });
return (
<div className="mx_SearchBar">
<div className="mx_SearchBar">
<input ref="search_term" className="mx_SearchBar_input" type="text" autoFocus={true} placeholder="Search..." onKeyDown={this.onSearchChange}/>
<AccessibleButton className={ searchButtonClasses } onClick={this.onSearch}><img src="img/search-button.svg" width="37" height="37" alt="Search"/></AccessibleButton>
<AccessibleButton className={ thisRoomClasses } onClick={this.onThisRoomClick}>This Room</AccessibleButton>
<AccessibleButton className={ allRoomsClasses } onClick={this.onAllRoomsClick}>All Rooms</AccessibleButton>
<AccessibleButton className="mx_SearchBar_cancel" onClick={this.props.onCancelClick}><img src="img/cancel.svg" width="18" height="18" /></AccessibleButton>
<div className={ searchButtonClasses } onClick={this.onSearch}><img src="img/search-button.svg" width="37" height="37" alt="Search"/></div>
<div className={ thisRoomClasses } onClick={this.onThisRoomClick}>This Room</div>
<div className={ allRoomsClasses } onClick={this.onAllRoomsClick}>All Rooms</div>
<img className="mx_SearchBar_cancel" src="img/cancel.svg" width="18" height="18" onClick={this.props.onCancelClick} />
</div>
);
}

View File

@@ -238,10 +238,9 @@ module.exports = React.createClass({
self._refreshFromServer();
}, function(error) {
var ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
console.error("Failed to change settings: " + error);
Modal.createDialog(ErrorDialog, {
title: "Failed to change settings",
description: ((error && error.message) ? error.message : "Operation failed"),
title: "Can't change settings",
description: error.toString(),
onFinished: self._refreshFromServer
});
});
@@ -308,10 +307,9 @@ module.exports = React.createClass({
self._refreshFromServer();
}, function(error) {
var ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
console.error("Can't update user notification settings: " + error);
Modal.createDialog(ErrorDialog, {
title: "Can't update user notification settings",
description: ((error && error.message) ? error.message : "Operation failed"),
description: error.toString(),
onFinished: self._refreshFromServer
});
});
@@ -350,10 +348,9 @@ module.exports = React.createClass({
var onError = function(error) {
var ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
console.error("Failed to update keywords: " + error);
Modal.createDialog(ErrorDialog, {
title: "Failed to update keywords",
description: ((error && error.message) ? error.message : "Operation failed"),
title: "Can't update keywords",
description: error.toString(),
onFinished: self._refreshFromServer
});
}
@@ -461,8 +458,8 @@ module.exports = React.createClass({
'.m.rule.master': 'master',
// The default push rules displayed by Vector UI
// XXX: .m.rule.contains_user_name is not managed (not a fancy rule for Vector?)
'.m.rule.contains_display_name': 'vector',
'.m.rule.contains_user_name': 'vector',
'.m.rule.room_one_to_one': 'vector',
'.m.rule.message': 'vector',
'.m.rule.invite_for_me': 'vector',
@@ -515,7 +512,6 @@ module.exports = React.createClass({
var vectorRuleIds = [
'.m.rule.contains_display_name',
'.m.rule.contains_user_name',
'_keywords',
'.m.rule.room_one_to_one',
'.m.rule.message',
@@ -719,17 +715,20 @@ module.exports = React.createClass({
);
}
const emailThreepids = this.props.threepids.filter((tp) => tp.medium === "email");
let emailNotificationsRow;
if (emailThreepids.length === 0) {
var emailNotificationsRow;
if (this.props.threepids.filter(function(tp) {
if (tp.medium == "email") {
return true;
}
}).length == 0) {
emailNotificationsRow = <div>
Add an email address above to configure email notifications
</div>;
} else {
// This only supports the first email address in your profile for now
emailNotificationsRow = this.emailNotificationsRow(
emailThreepids[0].address,
"Enable email notifications ("+emailThreepids[0].address+")"
this.props.threepids[0].address,
"Enable email notifications ("+this.props.threepids[0].address+")"
);
}

View File

@@ -1,6 +1,5 @@
/*
Copyright 2015, 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.

View File

@@ -63,20 +63,10 @@ class VectorPushRuleDefinition {
*/
module.exports = {
// Messages containing user's display name
// (skip contains_user_name which is too geeky)
".m.rule.contains_display_name": new VectorPushRuleDefinition({
kind: "override",
description: "Messages containing my display name",
vectorStateToActions: { // The actions for each vector state, or null to disable the rule.
on: StandardActions.ACTION_NOTIFY,
loud: StandardActions.ACTION_HIGHLIGHT_DEFAULT_SOUND,
off: StandardActions.ACTION_DISABLED
}
}),
// Messages containing user's username (localpart/MXID)
".m.rule.contains_user_name": new VectorPushRuleDefinition({
kind: "override",
description: "Messages containing my user name",
description: "Messages containing my name",
vectorStateToActions: { // The actions for each vector state, or null to disable the rule.
on: StandardActions.ACTION_NOTIFY,
loud: StandardActions.ACTION_HIGHLIGHT_DEFAULT_SOUND,

View File

@@ -1,81 +0,0 @@
// autogenerated by rethemendex.sh
@import "./_common.scss";
@import "./matrix-react-sdk/structures/_ContextualMenu.scss";
@import "./matrix-react-sdk/structures/_CreateRoom.scss";
@import "./matrix-react-sdk/structures/_FilePanel.scss";
@import "./matrix-react-sdk/structures/_MatrixChat.scss";
@import "./matrix-react-sdk/structures/_NotificationPanel.scss";
@import "./matrix-react-sdk/structures/_RoomStatusBar.scss";
@import "./matrix-react-sdk/structures/_RoomView.scss";
@import "./matrix-react-sdk/structures/_SearchBox.scss";
@import "./matrix-react-sdk/structures/_UploadBar.scss";
@import "./matrix-react-sdk/structures/_UserSettings.scss";
@import "./matrix-react-sdk/structures/login/_Login.scss";
@import "./matrix-react-sdk/views/avatars/_BaseAvatar.scss";
@import "./matrix-react-sdk/views/dialogs/_BugReportDialog.scss";
@import "./matrix-react-sdk/views/dialogs/_ChatCreateOrReuseChatDialog.scss";
@import "./matrix-react-sdk/views/dialogs/_ChatInviteDialog.scss";
@import "./matrix-react-sdk/views/dialogs/_ConfirmUserActionDialog.scss";
@import "./matrix-react-sdk/views/dialogs/_EncryptedEventDialog.scss";
@import "./matrix-react-sdk/views/dialogs/_SetDisplayNameDialog.scss";
@import "./matrix-react-sdk/views/dialogs/_UnknownDeviceDialog.scss";
@import "./matrix-react-sdk/views/elements/_AccessibleButton.scss";
@import "./matrix-react-sdk/views/elements/_AddressSelector.scss";
@import "./matrix-react-sdk/views/elements/_AddressTile.scss";
@import "./matrix-react-sdk/views/elements/_DirectorySearchBox.scss";
@import "./matrix-react-sdk/views/elements/_Dropdown.scss";
@import "./matrix-react-sdk/views/elements/_MemberEventListSummary.scss";
@import "./matrix-react-sdk/views/elements/_ProgressBar.scss";
@import "./matrix-react-sdk/views/elements/_RichText.scss";
@import "./matrix-react-sdk/views/login/_InteractiveAuthEntryComponents.scss";
@import "./matrix-react-sdk/views/login/_ServerConfig.scss";
@import "./matrix-react-sdk/views/messages/_MEmoteBody.scss";
@import "./matrix-react-sdk/views/messages/_MImageBody.scss";
@import "./matrix-react-sdk/views/messages/_MNoticeBody.scss";
@import "./matrix-react-sdk/views/messages/_MTextBody.scss";
@import "./matrix-react-sdk/views/messages/_TextualEvent.scss";
@import "./matrix-react-sdk/views/messages/_UnknownBody.scss";
@import "./matrix-react-sdk/views/rooms/_Autocomplete.scss";
@import "./matrix-react-sdk/views/rooms/_EntityTile.scss";
@import "./matrix-react-sdk/views/rooms/_EventTile.scss";
@import "./matrix-react-sdk/views/rooms/_LinkPreviewWidget.scss";
@import "./matrix-react-sdk/views/rooms/_MemberDeviceInfo.scss";
@import "./matrix-react-sdk/views/rooms/_MemberInfo.scss";
@import "./matrix-react-sdk/views/rooms/_MemberList.scss";
@import "./matrix-react-sdk/views/rooms/_MessageComposer.scss";
@import "./matrix-react-sdk/views/rooms/_PresenceLabel.scss";
@import "./matrix-react-sdk/views/rooms/_RoomHeader.scss";
@import "./matrix-react-sdk/views/rooms/_RoomList.scss";
@import "./matrix-react-sdk/views/rooms/_RoomPreviewBar.scss";
@import "./matrix-react-sdk/views/rooms/_RoomSettings.scss";
@import "./matrix-react-sdk/views/rooms/_RoomTile.scss";
@import "./matrix-react-sdk/views/rooms/_SearchableEntityList.scss";
@import "./matrix-react-sdk/views/rooms/_TabCompleteBar.scss";
@import "./matrix-react-sdk/views/rooms/_TopUnreadMessagesBar.scss";
@import "./matrix-react-sdk/views/settings/_DevicesPanel.scss";
@import "./matrix-react-sdk/views/settings/_IntegrationsManager.scss";
@import "./matrix-react-sdk/views/voip/_CallView.scss";
@import "./matrix-react-sdk/views/voip/_IncomingCallbox.scss";
@import "./matrix-react-sdk/views/voip/_VideoView.scss";
@import "./vector-web/_fonts.scss";
@import "./vector-web/structures/_CompatibilityPage.scss";
@import "./vector-web/structures/_HomePage.scss";
@import "./vector-web/structures/_LeftPanel.scss";
@import "./vector-web/structures/_RightPanel.scss";
@import "./vector-web/structures/_RoomDirectory.scss";
@import "./vector-web/structures/_RoomSubList.scss";
@import "./vector-web/structures/_ViewSource.scss";
@import "./vector-web/views/context_menus/_MessageContextMenu.scss";
@import "./vector-web/views/context_menus/_RoomTileContextMenu.scss";
@import "./vector-web/views/dialogs/_ChangelogDialog.scss";
@import "./vector-web/views/directory/_NetworkDropdown.scss";
@import "./vector-web/views/elements/_ImageView.scss";
@import "./vector-web/views/elements/_Spinner.scss";
@import "./vector-web/views/globals/_GuestWarningBar.scss";
@import "./vector-web/views/globals/_MatrixToolbar.scss";
@import "./vector-web/views/messages/_MessageTimestamp.scss";
@import "./vector-web/views/messages/_SenderProfile.scss";
@import "./vector-web/views/rooms/_RoomDropTarget.scss";
@import "./vector-web/views/rooms/_RoomTooltip.scss";
@import "./vector-web/views/rooms/_SearchBar.scss";
@import "./vector-web/views/settings/_Notifications.scss";

View File

@@ -1,6 +1,5 @@
/*
Copyright 2015, 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.
@@ -30,8 +29,7 @@ body {
Arial here. */
font-family: 'Open Sans', Arial, Helvetica, Sans-Serif;
font-size: 15px;
background-color: $primary-bg-color;
color: $primary-fg-color;
color: #454545;
border: 0px;
margin: 0px;
/* This should render the fonts the same accross browsers */
@@ -43,7 +41,7 @@ div.error {
}
h2 {
color: $primary-fg-color;
color: #454545;
font-weight: 400;
font-size: 18px;
margin-top: 16px;
@@ -53,20 +51,15 @@ h2 {
a:hover,
a:link,
a:visited {
color: $accent-color;
}
input[type=text], input[type=password], textarea {
background-color: transparent;
color: $primary-fg-color;
color: #76cfa6;
}
input[type=text].error, input[type=password].error {
border: 1px solid $warning-color;
border: 1px solid red;
}
input[type=text]:focus, input[type=password]:focus, textarea:focus {
border: 1px solid $accent-color;
input[type=text]:focus, textarea:focus {
border: 1px solid #76CFA6;
outline: none;
box-shadow: none;
}
@@ -84,7 +77,10 @@ textarea {
/* applied to side-panels and messagepanel when in RoomSettings */
.mx_fadable {
opacity: 1;
transition: opacity 0.2s ease-in-out;
-webkit-transition: opacity 0.2s ease-in-out;
-moz-transition: opacity 0.2s ease-in-out;
-ms-transition: opacity 0.2s ease-in-out;
-o-transition: opacity 0.2s ease-in-out;
}
/* XXX: critical hack to GeminiScrollbar to allow them to work in FF 42 and Chrome 48.
@@ -126,8 +122,14 @@ textarea {
width: 100%;
height: 100%;
display: -webkit-box;
display: -moz-box;
display: -ms-flexbox;
display: -webkit-flex;
display: flex;
-webkit-align-items: center;
align-items: center;
-webkit-justify-content: center;
justify-content: center;
}
@@ -135,7 +137,7 @@ textarea {
.mx_Dialog_wrapper.mx_Dialog_spinner .mx_Dialog {
width: auto;
border-radius: 8px;
padding: 0px;
padding-left: 0px;
box-shadow: none;
}
@@ -146,14 +148,13 @@ textarea {
}
.mx_Dialog {
background-color: $primary-bg-color;
color: $light-fg-color;
background-color: #fff;
color: #747474;
z-index: 4010;
font-weight: 300;
font-size: 15px;
position: relative;
padding-left: 58px;
padding-bottom: 36px;
width: 60%;
max-width: 704px;
box-shadow: 0 1px 0 0 rgba(0, 0, 0, 0.2);
@@ -167,13 +168,13 @@ textarea {
left: 0;
width: 100%;
height: 100%;
background-color: $dialog-background-bg-color;
background-color: #e9e9e9;
opacity: 0.8;
}
.mx_Dialog_lightbox .mx_Dialog_background {
opacity: 0.85;
background-color: $lightbox-background-bg-color;
background-color: #000;
}
.mx_Dialog_lightbox .mx_Dialog {
@@ -186,29 +187,22 @@ textarea {
pointer-events: none;
}
.mx_Dialog_cancelButton {
position: absolute;
right: 11px;
top: 13px;
cursor: pointer;
}
.mx_Dialog_cancelButton object {
pointer-events: none;
}
.mx_Dialog_content {
margin: 24px 58px 68px 0;
font-size: 14px;
color: $primary-fg-color;
color: #4a4a4a;
word-wrap: break-word;
}
.mx_Dialog_buttons {
padding-bottom: 36px;
}
.mx_Dialog button, .mx_Dialog input[type="submit"] {
border: 0px;
height: 36px;
border-radius: 40px;
border: solid 1px $accent-color;
border: solid 1px #76cfa6;
font-weight: 600;
font-size: 14px;
font-family: 'Open Sans', Arial, Helvetica, Sans-Serif;
@@ -218,30 +212,26 @@ textarea {
padding-right: 1.5em;
outline: none;
cursor: pointer;
color: $accent-color;
background-color: $primary-bg-color;
color: #76cfa6;
background-color: #fff;
/* align images in buttons (eg spinners) */
vertical-align: middle;
}
.mx_Dialog button:focus, .mx_Dialog input[type="submit"]:focus {
filter: brightness($focus-brightness);
}
.mx_Dialog button.mx_Dialog_primary, .mx_Dialog input[type="submit"].mx_Dialog_primary {
color: $accent-fg-color;
background-color: $accent-color;
color: #fff;
background-color: #76cfa6;
}
.mx_Dialog button.danger, .mx_Dialog input[type="submit"].danger {
background-color: $warning-color;
border: solid 1px $warning-color;
background-color: #ff0064;
border: solid 1px #ff0064;
}
.mx_Dialog button:disabled, .mx_Dialog input[type="submit"]:disabled {
background-color: $light-fg-color;
border: solid 1px $light-fg-color;
background-color: #777777;
border: solid 1px #777777;
opacity: 0.7;
}
@@ -251,11 +241,11 @@ textarea {
font-weight: bold;
font-size: 22px;
line-height: 1.4;
color: $primary-fg-color;
color: #454545;
}
.mx_Dialog_title.danger {
color: $warning-color;
color: #ff0064;
}
.mx_TextInputDialog_label {
@@ -266,10 +256,10 @@ textarea {
.mx_TextInputDialog_input {
font-size: 15px;
border-radius: 3px;
border: 1px solid $input-border-color;
border: 1px solid #f0f0f0;
padding: 9px;
color: $primary-fg-color;
background-color: $primary-bg-color;
color: #454545;
background-color: #fff;
}
.mx_emojione {
@@ -278,19 +268,19 @@ textarea {
}
::-moz-selection {
background-color: $accent-color;
color: $selection-fg-color;
background-color: #76CFA6;
color: white;
}
::selection {
background-color: $accent-color;
color: $selection-fg-color;
background-color: #76CFA6;
color: white;
}
/** green button with rounded corners */
.mx_textButton {
color: $accent-fg-color;
background-color: $accent-color;
color: #fff;
background-color: #76cfa6;
border-radius: 17px;
text-align: center;
padding-left: 1em;
@@ -298,11 +288,3 @@ textarea {
cursor: pointer;
display: inline;
}
.mx_button_row {
margin-top: 69px;
}
.changelog_text {
font-family: 'Open Sans', Arial, Helvetica, Sans-Serif;
}

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