Compare commits

..

1 Commits

Author SHA1 Message Date
Kegan Dougal
80277583db Fix timestamp hovers #59 2015-10-21 15:05:17 +01:00
350 changed files with 6698 additions and 12152 deletions

15
.gitignore vendored
View File

@@ -1,12 +1,3 @@
/cert.pem
/karma-reports
/key.pem
/lib
/node_modules
/packages/
/vector/bundle.*
/vector/components.css
/vector/emojione/
/vector/config.json
/vector/olm.js
.DS_Store
node_modules
vector/bundle.*
lib

View File

@@ -1,14 +0,0 @@
{
"minify": true,
"classPrefix": "modernizr_",
"options": [
"setClasses"
],
"feature-detects": [
"test/css/displaytable",
"test/css/flexbox",
"test/es5/specification",
"test/css/objectfit",
"test/storage/localstorage"
]
}

View File

@@ -1,6 +0,0 @@
language: node_js
node_js:
- 6 # node v6, to match jenkins
install:
- npm install
- (cd node_modules/matrix-react-sdk && npm run build)

View File

@@ -7,6 +7,3 @@ include:
* https://github.com/neko259
Improved scrollbar CSS
* Florent VIOLLEAU (https://github.com/floviolleau) <floviolleau at gmail dot com>
Improve README.md for a better understanding of installation instructions

View File

@@ -1,329 +0,0 @@
Changes in [0.7.5-r3](https://github.com/vector-im/vector-web/releases/tag/v0.7.5-r3) (2016-09-02)
==================================================================================================
[Full Changelog](https://github.com/vector-im/vector-web/compare/v0.7.5-r2...v0.7.5-r3)
* Bump to matrix-react-sdk 0.6.5-r3 in order to fix bug #2020 (tightloop when flooded with join events)
Changes in [0.7.5-r2](https://github.com/vector-im/vector-web/releases/tag/v0.7.5-r2) (2016-09-01)
==================================================================================================
[Full Changelog](https://github.com/vector-im/vector-web/compare/v0.7.5-r1...v0.7.5-r2)
* Bump to matrix-react-sdk 0.6.5-r1 in order to fix guest access
Changes in [0.7.5-r1](https://github.com/vector-im/vector-web/releases/tag/v0.7.5-r1) (2016-08-28)
==================================================================================================
[Full Changelog](https://github.com/vector-im/vector-web/compare/v0.7.5...v0.7.5-r1)
* Correctly pin deps :(
Changes in [0.7.5](https://github.com/vector-im/vector-web/releases/tag/v0.7.5) (2016-08-28)
============================================================================================
[Full Changelog](https://github.com/vector-im/vector-web/compare/v0.7.4-r1...v0.7.5)
* re-add leave button in RoomSettings
* add /user URLs
* recognise matrix.to links and other vector links
* fix linkify dependency
* fix avatar clicking in MemberInfo
* fix RoomTagContextMenu so it works on historical rooms
* warn people to put their Matrix HS on a separate domain to Vector
* fix zalgos again
* Add .travis.yml
[\#2007](https://github.com/vector-im/vector-web/pull/2007)
* add fancy changelog dialog
[\#1972](https://github.com/vector-im/vector-web/pull/1972)
* Update autocomplete design
[\#1978](https://github.com/vector-im/vector-web/pull/1978)
* Update encryption info in README
[\#2001](https://github.com/vector-im/vector-web/pull/2001)
* Added event/info message avatars back in
[\#2000](https://github.com/vector-im/vector-web/pull/2000)
* Wmwragg/chat message presentation
[\#1987](https://github.com/vector-im/vector-web/pull/1987)
* Make the notification slider work
[\#1982](https://github.com/vector-im/vector-web/pull/1982)
* Use cpx to copy olm.js, and add watcher
[\#1966](https://github.com/vector-im/vector-web/pull/1966)
* Make up a device display name
[\#1959](https://github.com/vector-im/vector-web/pull/1959)
Changes in [0.7.4-r1](https://github.com/vector-im/vector-web/releases/tag/v0.7.4-r1) (2016-08-12)
==================================================================================================
[Full Changelog](https://github.com/vector-im/vector-web/compare/v0.7.4...v0.7.4-r1)
* Update to matrix-react-sdk 0.6.4-r1 to fix inviting multiple people
Changes in [0.7.4](https://github.com/vector-im/vector-web/releases/tag/v0.7.4) (2016-08-11)
============================================================================================
[Full Changelog](https://github.com/vector-im/vector-web/compare/v0.7.3...v0.7.4)
* Don't show border on composer when not in RTE mode
[\#1954](https://github.com/vector-im/vector-web/pull/1954)
* Wmwragg/room tag menu
[\#1941](https://github.com/vector-im/vector-web/pull/1941)
* Don't redirect to mobile app if verifying 3pid
[\#1951](https://github.com/vector-im/vector-web/pull/1951)
* Make sure that we clear localstorage before *all* tests
[\#1950](https://github.com/vector-im/vector-web/pull/1950)
* Basic CSS for multi-invite dialog
[\#1942](https://github.com/vector-im/vector-web/pull/1942)
* More tests for the loading process:
[\#1947](https://github.com/vector-im/vector-web/pull/1947)
* Support for refactored login token handling
[\#1946](https://github.com/vector-im/vector-web/pull/1946)
* Various fixes and improvements to emojification.
[\#1935](https://github.com/vector-im/vector-web/pull/1935)
* More app-loading tests
[\#1938](https://github.com/vector-im/vector-web/pull/1938)
* Some tests of the application load process
[\#1936](https://github.com/vector-im/vector-web/pull/1936)
* Add 'enable labs' setting to sample config
[\#1930](https://github.com/vector-im/vector-web/pull/1930)
* Matthew/scalar
[\#1928](https://github.com/vector-im/vector-web/pull/1928)
* Fix unit tests
[\#1929](https://github.com/vector-im/vector-web/pull/1929)
* Wmwragg/mute mention state fix
[\#1926](https://github.com/vector-im/vector-web/pull/1926)
* CSS for deactivate account dialog
[\#1919](https://github.com/vector-im/vector-web/pull/1919)
* Wmwragg/mention state menu
[\#1900](https://github.com/vector-im/vector-web/pull/1900)
* Fix UnknownBody styling for #1901
[\#1913](https://github.com/vector-im/vector-web/pull/1913)
* Exclude olm from the webpack
[\#1914](https://github.com/vector-im/vector-web/pull/1914)
* Wmwragg/button updates
[\#1912](https://github.com/vector-im/vector-web/pull/1912)
* Wmwragg/button updates
[\#1828](https://github.com/vector-im/vector-web/pull/1828)
* CSS for device management UI
[\#1909](https://github.com/vector-im/vector-web/pull/1909)
* Fix a warning from RoomSubList
[\#1908](https://github.com/vector-im/vector-web/pull/1908)
* Fix notifications warning layout
[\#1907](https://github.com/vector-im/vector-web/pull/1907)
* Remove relayoutOnUpdate prop on gemini-scrollbar
[\#1883](https://github.com/vector-im/vector-web/pull/1883)
* Bump dependency versions
[\#1842](https://github.com/vector-im/vector-web/pull/1842)
* Wmwragg/mention state indicator round 2
[\#1835](https://github.com/vector-im/vector-web/pull/1835)
* Wmwragg/spinner fix
[\#1822](https://github.com/vector-im/vector-web/pull/1822)
* Wmwragg/mention state indicator
[\#1823](https://github.com/vector-im/vector-web/pull/1823)
* Revert "Presentation for inline link"
[\#1809](https://github.com/vector-im/vector-web/pull/1809)
* Wmwragg/modal restyle
[\#1806](https://github.com/vector-im/vector-web/pull/1806)
* Presentation for inline link
[\#1799](https://github.com/vector-im/vector-web/pull/1799)
* CSS for offline user colours
[\#1798](https://github.com/vector-im/vector-web/pull/1798)
* Wmwragg/typography updates
[\#1776](https://github.com/vector-im/vector-web/pull/1776)
* webpack: always use the olm from vector-web
[\#1766](https://github.com/vector-im/vector-web/pull/1766)
* feat: large emoji support
[\#1718](https://github.com/vector-im/vector-web/pull/1718)
* Autocomplete
[\#1717](https://github.com/vector-im/vector-web/pull/1717)
* #1664 Set a maximum height for codeblocks
[\#1670](https://github.com/vector-im/vector-web/pull/1670)
* CSS for device blocking
[\#1688](https://github.com/vector-im/vector-web/pull/1688)
* Fix joining rooms by typing the alias
[\#1685](https://github.com/vector-im/vector-web/pull/1685)
* Add ability to delete an alias from room directory
[\#1680](https://github.com/vector-im/vector-web/pull/1680)
* package.json: add olm as optionalDependency
[\#1678](https://github.com/vector-im/vector-web/pull/1678)
* Another go at enabling olm on vector.im/develop
[\#1675](https://github.com/vector-im/vector-web/pull/1675)
* CSS for unverify button
[\#1661](https://github.com/vector-im/vector-web/pull/1661)
* CSS fix for rooms with crypto enabled
[\#1660](https://github.com/vector-im/vector-web/pull/1660)
* Karma: fix warning by ignoring olm
[\#1652](https://github.com/vector-im/vector-web/pull/1652)
* Update for react-sdk dbkr/fix_peeking branch
[\#1639](https://github.com/vector-im/vector-web/pull/1639)
* Update README.md
[\#1641](https://github.com/vector-im/vector-web/pull/1641)
* Fix karma tests
[\#1643](https://github.com/vector-im/vector-web/pull/1643)
* Rich Text Editor
[\#1553](https://github.com/vector-im/vector-web/pull/1553)
* Fix RoomDirectory to join by alias whenever possible.
[\#1615](https://github.com/vector-im/vector-web/pull/1615)
* Make the config optional
[\#1612](https://github.com/vector-im/vector-web/pull/1612)
* CSS support for device verification
[\#1610](https://github.com/vector-im/vector-web/pull/1610)
* Don't use SdkConfig
[\#1609](https://github.com/vector-im/vector-web/pull/1609)
* serve config.json statically instead of bundling it
[\#1516](https://github.com/vector-im/vector-web/pull/1516)
Changes in [0.7.3](https://github.com/vector-im/vector-web/releases/tag/v0.7.3) (2016-06-03)
============================================================================================
[Full Changelog](https://github.com/vector-im/vector-web/compare/v0.7.2...v0.7.3)
* Update to react-sdk 0.6.3
Changes in [0.7.2](https://github.com/vector-im/vector-web/releases/tag/v0.7.2) (2016-06-02)
============================================================================================
[Full Changelog](https://github.com/vector-im/vector-web/compare/v0.7.1...v0.7.2)
* Correctly bump the dep on new matrix-js-sdk and matrix-react-sdk
Changes in [0.7.1](https://github.com/vector-im/vector-web/releases/tag/v0.7.1) (2016-06-02)
============================================================================================
[Full Changelog](https://github.com/vector-im/vector-web/compare/v0.7.0...v0.7.1)
* Fix accidentally committed local changes to the default config.json (doh!)
Changes in [0.7.0](https://github.com/vector-im/vector-web/releases/tag/v0.7.0) (2016-06-02)
============================================================================================
[Full Changelog](https://github.com/vector-im/vector-web/compare/v0.6.1...v0.7.0)
* Update to matrix-react-sdk 0.6.0 - see
[changelog](https://github.com/matrix-org/matrix-react-sdk/blob/v0.6.0/CHANGELOG.md)
* Style selection color.
[\#1557](https://github.com/vector-im/vector-web/pull/1557)
* Fix NPE when loading the Settings page which infini-spinnered
[\#1518](https://github.com/vector-im/vector-web/pull/1518)
* Add option to enable email notifications
[\#1469](https://github.com/vector-im/vector-web/pull/1469)
Changes in [0.6.1](https://github.com/vector-im/vector-web/releases/tag/v0.6.1) (2016-04-22)
============================================================================================
[Full Changelog](https://github.com/vector-im/vector-web/compare/v0.6.0...v0.6.1)
* Update to matrix-react-sdk 0.5.2 - see
[changelog](https://github.com/matrix-org/matrix-react-sdk/blob/v0.5.2/CHANGELOG.md)
* Don't relayout scrollpanels every time something changes
[\#1438](https://github.com/vector-im/vector-web/pull/1438)
* Include react-addons-perf for non-production builds
[\#1431](https://github.com/vector-im/vector-web/pull/1431)
Changes in [0.6.0](https://github.com/vector-im/vector-web/releases/tag/v0.6.0) (2016-04-19)
============================================================================================
[Full Changelog](https://github.com/vector-im/vector-web/compare/v0.5.0...v0.6.0)
* Matthew/design tweaks
[\#1402](https://github.com/vector-im/vector-web/pull/1402)
* Improve handling of notification rules we can't parse
[\#1399](https://github.com/vector-im/vector-web/pull/1399)
* Do less mangling of jenkins builds
[\#1391](https://github.com/vector-im/vector-web/pull/1391)
* Start Notifications component refactor
[\#1386](https://github.com/vector-im/vector-web/pull/1386)
* make the UI fadable to help with decluttering
[\#1376](https://github.com/vector-im/vector-web/pull/1376)
* Get and display a user's pushers in settings
[\#1374](https://github.com/vector-im/vector-web/pull/1374)
* URL previewing support
[\#1343](https://github.com/vector-im/vector-web/pull/1343)
* 😄 Emoji autocomplete and unicode emoji to image conversion using emojione.
[\#1332](https://github.com/vector-im/vector-web/pull/1332)
* Show full-size avatar on MemberInfo avatar click
[\#1340](https://github.com/vector-im/vector-web/pull/1340)
* Numerous other changes via [matrix-react-sdk 0.5.1](https://github.com/matrix-org/matrix-react-sdk/blob/v0.5.1/CHANGELOG.md)
Changes in [0.5.0](https://github.com/vector-im/vector-web/releases/tag/v0.5.0) (2016-03-30)
============================================================================================
[Full Changelog](https://github.com/vector-im/vector-web/compare/v0.4.1...v0.5.0)
* Prettier, animated placeholder :D
[\#1292](https://github.com/vector-im/vector-web/pull/1292)
(Disabled for now due to high CPU usage)
* RoomDirectory: use SimpleRoomHeader instead of RoomHeader
[\#1307](https://github.com/vector-im/vector-web/pull/1307)
* Tell webpack not to parse the highlight.js languages
[\#1277](https://github.com/vector-im/vector-web/pull/1277)
* CSS for https://github.com/matrix-org/matrix-react-sdk/pull/247
[\#1249](https://github.com/vector-im/vector-web/pull/1249)
* URI-decode the hash-fragment
[\#1254](https://github.com/vector-im/vector-web/pull/1254)
Changes in [0.4.1](https://github.com/vector-im/vector-web/releases/tag/v0.4.1) (2016-03-23)
============================================================================================
[Full Changelog](https://github.com/vector-im/vector-web/compare/v0.4.0...v0.4.1)
* Update to matrix-react-sdk 0.3.1; see
https://github.com/matrix-org/matrix-react-sdk/blob/v0.3.1/CHANGELOG.md
(Disables debug logging)
Changes in [0.4.0](https://github.com/vector-im/vector-web/releases/tag/v0.4.0) (2016-03-23)
============================================================================================
[Full Changelog](https://github.com/vector-im/vector-web/compare/v0.3.0...v0.4.0)
* Update to matrix-react-sdk 0.3.0; see
https://github.com/matrix-org/matrix-react-sdk/blob/master/CHANGELOG.md
Other changes
* permalink button
[\#1232](https://github.com/vector-im/vector-web/pull/1232)
* make senderprofiles clickable
[\#1191](https://github.com/vector-im/vector-web/pull/1191)
* fix notif spam when logging in from a guest session by correctly logging out
first.
[\#1180](https://github.com/vector-im/vector-web/pull/1180)
* use new start_login_from_guest dispatch for cancellable logins from guest
accounts
[\#1165](https://github.com/vector-im/vector-web/pull/1165)
* Use then() chaining rather than manual callbacks
[\#1171](https://github.com/vector-im/vector-web/pull/1171)
* Remove trailing whitespace
[\#1163](https://github.com/vector-im/vector-web/pull/1163)
* Update the actions of default rules instead of overriding.
[\#1037](https://github.com/vector-im/vector-web/pull/1037)
* Update README to include `npm install` in react-sdk
[\#1137](https://github.com/vector-im/vector-web/pull/1137)
Changes in vector v0.3.0 (2016-03-11)
======================================
* Lots of new bug fixes and updates
Changes in vector v0.2.0 (2016-02-24)
======================================
* Refactor of matrix-react-sdk and vector to remove separation between views and
controllers
* Temporarily break the layering abstraction between vector and matrix-react-sdk
for expedience in developing vector.
* Vast numbers of new features, including read receipts, read-up-to markers,
updated look and feel, search, new room and user settings, and email invites.
Changes in vector v0.1.2 (2015-10-28)
======================================
* Support Room Avatars
* Fullscreen video calls
* Mute mic in VoIP calls
* Fix bug with multiple desktop notifications
* Context menu on messages
* Better hover-over on member list
* Support CAS auth
* Many other bug fixes
Changes in vector v0.1.1 (2015-08-10)
======================================
* Support logging in with an email address
* Use the Vector identity server
* Fix a bug where the client was not stopped properly on logout
* Fix bugs where field values would be forgotten if login or registration failed
* Improve URL bar navigation
* Add explanatory help text on advanced server options
* Fix a bug which caused execptions on malformed VoIP invitations
* Remove superfluous scrollbars on Firefox
* Numerous CSS fixes
* Improved accessibility
* Support command-click / middle click to open image in a new tab
* Improved room directory
* Fix display of text with many combining unicode points
Changes in vector v0.1.0 (2015-08-10)
======================================
Initial release

20
CHANGES.rst Normal file
View File

@@ -0,0 +1,20 @@
Changes in vector v0.1.1 (2015-08-10)
======================================
* Support logging in with an email address
* Use the Vector identity server
* Fix a bug where the client was not stopped properly on logout
* Fix bugs where field values would be forgotten if login or registration failed
* Improve URL bar navigation
* Add explanatory help text on advanced server options
* Fix a bug which caused execptions on malformed VoIP invitations
* Remove superfluous scrollbars on Firefox
* Numerous CSS fixes
* Improved accessibility
* Support command-click / middle click to open image in a new tab
* Improved room directory
* Fix display of text with many combining unicode points
Changes in vector v0.1.0 (2015-08-10)
======================================
Initial release

246
README.md
View File

@@ -3,241 +3,43 @@ Vector/Web
Vector is a Matrix web client built using the Matrix React SDK (https://github.com/matrix-org/matrix-react-sdk).
Getting Started
Getting started
===============
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 Vector, the quickest bet is to use a pre-built released version
of Vector:
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 vector!
Important Security Note
=======================
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/vector-web/issues/1977 for more details.
Building From Source
====================
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/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, 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.
2. Clone the repo: `git clone https://github.com/vector-im/vector-web.git`
3. Switch to the SDK directory: `cd vector-web`
4. Install the prerequisites: `npm install`
5. Start the development builder and a testing server: `npm start`
6. Wait a few seconds for the initial build to finish.
7. Open http://127.0.0.1:8080/ in your browser to see your newly built Vector.
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 `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
for verifying third party identifiers like email addresses). If this is blank,
registering with an email address, adding an email address to your account,
or inviting users via email address will not work. Matrix identity servers are
very simple web services which map third party identifiers (currently only email
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.
Running as a Desktop app
========================
In future we'll do an official distribution of Vector as an desktop app. Meanwhile,
there are a few options:
@asdf:matrix.org points out that you can use nativefier and it just works(tm):
```
sudo npm install nativefier -g
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
With `npm start`, any changes you make to the source files will cause a rebuild so
your changes will show up when you refresh.
For production use, run `npm run build` to build all the necessary files
into the `vector` directory and run your own server.
Development
===========
You can work on any of the source files within Vector with the setup above,
and your changes will cause an instant rebuild. If you also need to make
changes to the react sdk, you can:
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 Vector too.
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.
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 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 `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 `vector-web` in github.
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 Vector itself.
Setting up a dev environment
============================
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.
First clone and build `matrix-js-sdk`:
1. `git clone git@github.com:matrix-org/matrix-js-sdk.git`
1. `pushd matrix-js-sdk`
1. `git checkout develop`
1. `npm install`
1. `npm install source-map-loader` # because webpack is made of fail (https://github.com/webpack/webpack/issues/1472)
1. `popd`
Then similarly with `matrix-react-sdk`:
1. `git clone git@github.com:matrix-org/matrix-react-sdk.git`
1. `pushd matrix-react-sdk`
1. `git checkout develop`
1. `npm install`
1. `rm -r node_modules/matrix-js-sdk; ln -s ../../matrix-js-sdk node_modules/`
1. `popd`
Finally, build and start vector itself:
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/`
1. `rm -r node_modules/matrix-react-sdk; ln -s ../../matrix-react-sdk node_modules/`
1. `npm start`
1. Wait a few seconds for the initial build to finish; you should see something like:
```
Hash: b0af76309dd56d7275c8
Version: webpack 1.12.14
Time: 14533ms
Asset Size Chunks Chunk Names
bundle.js 4.2 MB 0 [emitted] main
bundle.css 91.5 kB 0 [emitted] main
bundle.js.map 5.29 MB 0 [emitted] main
bundle.css.map 116 kB 0 [emitted] main
+ 1013 hidden modules
```
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 Vector.
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.
1. Link the react sdk package into the example:
`npm link path/to/your/react/sdk`
2. Start the development rebuilder in your react SDK directory:
`npm start`
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 vector.
You may need to run `npm i source-map-loader` in matrix-js-sdk if you get errors
about "Cannot resolve module 'source-map-loader'" due to shortcomings in webpack.
Filing issues
=============
Deployment
==========
All issues for Vector-web and Matrix-react-sdk should be filed at
https://github.com/matrix-org/matrix-react-sdk/issues
Just run `npm build` and then mount the `vector` directory on your webserver to
actually serve up the app, which is entirely static content.
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: 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)
* release blocker
* ui/ux (think of this as cosmetic)
* 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.

4
config.json Normal file
View File

@@ -0,0 +1,4 @@
{
"default_hs_url": "https://matrix.org",
"default_is_url": "https://vector.im"
}

View File

@@ -1,170 +0,0 @@
#!/usr/bin/env python
from __future__ import print_function
import json, requests, tarfile, argparse, os, errno
from urlparse import urljoin
from flask import Flask, jsonify, request, abort
app = Flask(__name__)
arg_jenkins_url, arg_extract_path, arg_should_clean, arg_symlink = (
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:
os.symlink(source, linkname)
except OSError, e:
if e.errno == errno.EEXIST:
# atomic modification
os.symlink(source, linkname + ".tmp")
os.rename(linkname + ".tmp", linkname)
else:
raise e
@app.route("/", methods=["POST"])
def on_receive_jenkins_poke():
# {
# "name": "VectorWebDevelop",
# "build": {
# "number": 8
# }
# }
incoming_json = request.get_json()
if not incoming_json:
abort(400, "No JSON provided!")
return
print("Incoming JSON: %s" % (incoming_json,))
job_name = incoming_json.get("name")
if not isinstance(job_name, basestring):
abort(400, "Bad job name: %s" % (job_name,))
return
build_num = incoming_json.get("build", {}).get("number", 0)
if not build_num or build_num <= 0 or not isinstance(build_num, int):
abort(400, "Missing or bad build number")
return
artifact_url = urljoin(
arg_jenkins_url, "job/%s/%s/api/json" % (job_name, build_num)
)
artifact_response = requests.get(artifact_url).json()
# {
# "actions": [],
# "artifacts": [
# {
# "displayPath": "vector-043f6991a4ed-react-20f77d1224ef-js-0a7efe3e8bd5.tar.gz",
# "fileName": "vector-043f6991a4ed-react-20f77d1224ef-js-0a7efe3e8bd5.tar.gz",
# "relativePath": "vector-043f6991a4ed-react-20f77d1224ef-js-0a7efe3e8bd5.tar.gz"
# }
# ],
# "building": false,
# "description": null,
# "displayName": "#11",
# "duration": 137976,
# "estimatedDuration": 132008,
# "executor": null,
# "fullDisplayName": "VectorWebDevelop #11",
# "id": "11",
# "keepLog": false,
# "number": 11,
# "queueId": 12254,
# "result": "SUCCESS",
# "timestamp": 1454432640079,
# "url": "http://matrix.org/jenkins/job/VectorWebDevelop/11/",
# "builtOn": "",
# "changeSet": {},
# "culprits": []
# }
if artifact_response.get("result") != "SUCCESS":
abort(404, "Not deploying. Build was not marked as SUCCESS.")
return
if len(artifact_response.get("artifacts", [])) != 1:
abort(404, "Not deploying. Build has an unexpected number of artifacts.")
return
tar_gz_path = artifact_response["artifacts"][0]["relativePath"]
if not tar_gz_path.endswith(".tar.gz"):
abort(404, "Not deploying. Artifact is not a .tar.gz file")
return
tar_gz_url = urljoin(
arg_jenkins_url, "job/%s/%s/artifact/%s" % (job_name, build_num, tar_gz_path)
)
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)
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)
return jsonify({})
if __name__ == "__main__":
parser = argparse.ArgumentParser("Runs a Vector redeployment server.")
parser.add_argument(
"-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."
)
)
parser.add_argument(
"-p", "--port", dest="port", default=4000, type=int, help=(
"The port to listen on for requests from Jenkins."
)
)
parser.add_argument(
"-e", "--extract", dest="extract", default="./extracted", help=(
"The location to extract .tar.gz files to."
)
)
parser.add_argument(
"-c", "--clean", dest="clean", action="store_true", default=False, help=(
"Remove .tar.gz files after they have been downloaded and extracted."
)
)
parser.add_argument(
"-s", "--symlink", dest="symlink", default="./latest", help=(
"Write a symlink to this location pointing to the extracted tarball. \
New builds will keep overwriting this symlink. The symlink will point \
to the /vector directory INSIDE the tarball."
)
)
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
print(
"Listening on port %s. Extracting to %s%s. Symlinking to %s. Jenkins URL: %s" %
(args.port, arg_extract_path,
" (clean after)" if arg_should_clean else "", arg_symlink, arg_jenkins_url)
)
app.run(host="0.0.0.0", port=args.port, debug=True)

View File

@@ -1,36 +0,0 @@
#!/bin/bash
set -e
export NVM_DIR="/home/jenkins/.nvm"
[ -s "$NVM_DIR/nvm.sh" ] && . "$NVM_DIR/nvm.sh"
nvm use 6
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
# 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
# build our artifacts; dumps them in ./vector
npm run build:dev
# 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
# we're grabbing here.
REACT_SHA=$(grep 'gitHead' node_modules/matrix-react-sdk/package.json | cut -d \" -f 4 | head -c 12)
JSSDK_SHA=$(grep 'gitHead' node_modules/matrix-js-sdk/package.json | cut -d \" -f 4 | head -c 12)
VECTOR_SHA=$(git rev-parse --short=12 HEAD) # use the ACTUAL SHA rather than assume develop
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

@@ -1,142 +0,0 @@
// karma.conf.js - the config file for karma, which runs our tests.
var path = require('path');
var webpack = require('webpack');
/*
* 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
* the tests for changes, and webpack will rebuild using a cache. This is much quicker
* than a clean rebuild.
*/
// the name of the test file. By default, a special file which runs all tests.
var testFile = process.env.KARMA_TEST_FILE || 'test/all-tests.js';
process.env.PHANTOMJS_BIN = 'node_modules/.bin/phantomjs';
process.env.Q_DEBUG = 1;
module.exports = function (config) {
config.set({
// frameworks to use
// available frameworks: https://npmjs.org/browse/keyword/karma-adapter
frameworks: ['mocha'],
// list of files / patterns to load in the browser
files: [
'node_modules/babel-polyfill/browser.js',
testFile,
{pattern: 'vector/img/*', watched: false, included: false, served: true, nocache: false},
],
// redirect img links to the karma server
proxies: {
"/img/": "/base/vector/img/",
},
// preprocess matching files before serving them to the browser
// available preprocessors:
// https://npmjs.org/browse/keyword/karma-preprocessor
preprocessors: {
'test/**/*.js': ['webpack', 'sourcemap']
},
// test results reporter to use
// possible values: 'dots', 'progress'
// available reporters: https://npmjs.org/browse/keyword/karma-reporter
reporters: ['progress', 'junit'],
// web server port
port: 9876,
// enable / disable colors in the output (reporters and logs)
colors: true,
// level of logging
// possible values: config.LOG_DISABLE || config.LOG_ERROR ||
// config.LOG_WARN || config.LOG_INFO || config.LOG_DEBUG
logLevel: config.LOG_INFO,
// enable / disable watching file and executing tests whenever any file
// changes
autoWatch: true,
// start these browsers
// available browser launchers:
// https://npmjs.org/browse/keyword/karma-launcher
browsers: [
'Chrome',
//'PhantomJS',
],
// Continuous Integration mode
// if true, Karma captures browsers, runs the tests and exits
// singleRun: false,
// Concurrency level
// how many browser should be started simultaneous
concurrency: Infinity,
junitReporter: {
outputDir: 'karma-reports',
},
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/,
// 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',
},
});
};

View File

@@ -1,6 +1,6 @@
{
"name": "vector-web",
"version": "0.7.5-r3",
"version": "0.0.1",
"description": "Vector webapp",
"author": "matrix.org",
"repository": {
@@ -9,87 +9,40 @@
},
"license": "Apache-2.0",
"style": "bundle.css",
"matrix-react-parent": "matrix-react-sdk",
"scripts": {
"reskindex": "reskindex -h src/header",
"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",
"reskindex": "reskindex vector -h src/skins/vector/header",
"build:css": "catw \"src/skins/vector/css/**/*.css\" -o vector/bundle.css -c uglifycss --no-watch",
"build:compile": "babel --source-maps -d lib src",
"build:bundle": "NODE_ENV=production webpack -p lib/vector/index.js vector/bundle.js",
"build:bundle:dev": "NODE_ENV=production 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",
"build": "npm run build:css && npm run build:compile && npm run build:bundle",
"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/",
"start:skins:css": "catw \"src/skins/vector/css/**/*.css\" -o vector/bundle.css",
"//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"
"start": "parallelshell \"npm run start:js\" \"npm run start:skins:css\" \"http-server -c 1 vector\"",
"clean": "rimraf lib vector/bundle.css vector/bundle.js vector/bundle.js.map",
"prepublish": "npm run build:css && npm run build:compile"
},
"dependencies": {
"babel-polyfill": "^6.5.0",
"browser-request": "^0.3.3",
"classnames": "^2.1.2",
"draft-js": "^0.7.0",
"extract-text-webpack-plugin": "^0.9.1",
"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.0.0-beta.4",
"matrix-js-sdk": "0.5.6",
"matrix-react-sdk": "0.6.5-r3",
"modernizr": "^3.1.0",
"linkifyjs": "^2.0.0-beta.4",
"matrix-js-sdk": "^0.2.2",
"matrix-react-sdk": "^0.0.1",
"q": "^1.4.1",
"react": "^15.2.1",
"react-dnd": "^2.1.4",
"react-dnd-html5-backend": "^2.1.2",
"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"
"react": "^0.13.3",
"react-loader": "^1.4.0"
},
"devDependencies": {
"babel": "^5.8.23",
"babel-core": "^5.8.25",
"babel-loader": "^5.3.2",
"catw": "^1.0.1",
"cpx": "^1.3.2",
"css-raw-loader": "^0.1.1",
"emojione": "^2.2.3",
"expect": "^1.16.0",
"fs-extra": "^0.30.0",
"http-server": "^0.8.4",
"json-loader": "^0.5.3",
"karma": "^0.13.22",
"karma-chrome-launcher": "^0.2.3",
"karma-cli": "^0.1.2",
"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",
"mocha": "^2.4.5",
"parallelshell": "^1.2.0",
"phantomjs-prebuilt": "^2.1.7",
"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"
},
"optionalDependencies": {
"olm": "https://matrix.org/packages/npm/olm/olm-1.0.0.tgz"
"uglifycss": "0.0.15"
}
}

View File

@@ -1,12 +0,0 @@
#!/bin/sh
#
# Script to perform a release of vector-web.
#
# Requires github-changelog-generator; to install, do
# pip install git+https://github.com/matrix-org/github-changelog-generator.git
set -e
cd `dirname $0`
exec ./node_modules/matrix-js-sdk/release.sh -z "$@"

View File

@@ -1,124 +0,0 @@
#!/usr/bin/env perl
use warnings;
use strict;
use Net::GitHub;
use DateTime;
use DateTime::Format::ISO8601;
my $gh = Net::GitHub->new(
login => 'ara4n', pass => 'secret'
);
$gh->set_default_user_repo('vector-im', 'vector-web');
my @issues = $gh->issue->repos_issues({ state => 'all', milestone => 3 });
while ($gh->issue->has_next_page) {
push @issues, $gh->issue->next_page;
}
# we want:
# day by day:
# split by { open, closed }
# split by { bug, feature, neither }
# each split by { p1, p2, p3, p4, p5, unprioritised } <- priority
# each split by { minor, major, critical, cosmetic, network, no-severity } <- severity
# then split (with overlap between the groups) as { total, tag1, tag2, ... }?
# ...and then all over again split by milestone.
my $days = {};
my $schema = {};
my $now = DateTime->now();
foreach my $issue (@issues) {
next if ($issue->{pull_request});
# use Data::Dumper;
# print STDERR Dumper($issue);
my @label_list = map { $_->{name} } @{$issue->{labels}};
my $labels = {};
$labels->{$_} = 1 foreach (@label_list);
$labels->{bug}++ if ($labels->{cosmetic} && !$labels->{bug} && !$labels->{feature});
my $extract_labels = sub {
my $label = undef;
foreach (@_) {
$label ||= $_ if (delete $labels->{$_});
}
return $label;
};
my $state = $issue->{state};
my $type = &$extract_labels(qw(bug feature)) || "neither";
my $priority = &$extract_labels(qw(p1 p2 p3 p4 p5)) || "unprioritised";
my $severity = &$extract_labels(qw(minor major critical cosmetic network)) || "no-severity";
my $start = DateTime::Format::ISO8601->parse_datetime($issue->{created_at});
do {
my $ymd = $start->ymd();
$days->{ $ymd }->{ 'created' }->{ $type }->{ $priority }->{ $severity }->{ total }++;
$schema->{ 'created' }->{ $type }->{ $priority }->{ $severity }->{ total }++;
foreach (keys %$labels) {
$days->{ $ymd }->{ 'created' }->{ $type }->{ $priority }->{ $severity }->{ $_ }++;
$schema->{ 'created' }->{ $type }->{ $priority }->{ $severity }->{ $_ }++;
}
$start = $start->add(days => 1);
} while (DateTime->compare($start, $now) < 0);
if ($state eq 'closed') {
my $end = DateTime::Format::ISO8601->parse_datetime($issue->{closed_at});
do {
my $ymd = $end->ymd();
$days->{ $ymd }->{ 'resolved' }->{ $type }->{ $priority }->{ $severity }->{ total }++;
$schema->{ 'resolved' }->{ $type }->{ $priority }->{ $severity }->{ total }++;
foreach (keys %$labels) {
$days->{ $ymd }->{ 'resolved' }->{ $type }->{ $priority }->{ $severity }->{ $_ }++;
$schema->{ 'resolved' }->{ $type }->{ $priority }->{ $severity }->{ $_ }++;
}
$end = $end->add(days => 1);
} while (DateTime->compare($end, $now) < 0);
}
}
print "day,";
foreach my $state (sort keys %{$schema}) {
foreach my $type (grep { /^(bug|feature)$/ } sort keys %{$schema->{$state}}) {
foreach my $priority (grep { /^(p1|p2)$/ } sort keys %{$schema->{$state}->{$type}}) {
foreach my $severity (sort keys %{$schema->{$state}->{$type}->{$priority}}) {
# foreach my $tag (sort keys %{$schema->{$state}->{$type}->{$priority}->{$severity}}) {
# print "\"$type\n$priority\n$severity\n$tag\",";
# }
print "\"$state\n$type\n$priority\n$severity\",";
}
}
}
}
print "\n";
foreach my $day (sort keys %$days) {
print "$day,";
foreach my $state (sort keys %{$schema}) {
foreach my $type (grep { /^(bug|feature)$/ } sort keys %{$schema->{$state}}) {
foreach my $priority (grep { /^(p1|p2)$/ } sort keys %{$schema->{$state}->{$type}}) {
foreach my $severity (sort keys %{$schema->{$state}->{$type}->{$priority}}) {
# foreach my $tag (sort keys %{$schema->{$state}->{$type}->{$priority}->{$severity}}) {
# print $days->{$day}->{$state}->{$type}->{$priority}->{$severity}->{$tag} || 0;
# print ",";
# }
print $days->{$day}->{$state}->{$type}->{$priority}->{$severity}->{total} || 0;
print ",";
}
}
}
}
print "\n";
}

View File

@@ -1,104 +0,0 @@
#!/usr/bin/env perl
use warnings;
use strict;
use Net::GitHub;
use DateTime;
use DateTime::Format::ISO8601;
my $gh = Net::GitHub->new(
login => 'ara4n', pass => 'secret'
);
$gh->set_default_user_repo('vector-im', 'vector-web');
my @issues = $gh->issue->repos_issues({ state => 'all', milestone => 3 });
while ($gh->issue->has_next_page) {
push @issues, $gh->issue->next_page;
}
# we want:
# day by day:
# split by { open, closed }
# split by { bug, feature, neither }
# each split by { p1, p2, p3, p4, p5, unprioritised } <- priority
# each split by { minor, major, critical, cosmetic, network, no-severity } <- severity
# then split (with overlap between the groups) as { total, tag1, tag2, ... }?
# ...and then all over again split by milestone.
my $days = {};
my $schema = {};
my $now = DateTime->now();
foreach my $issue (@issues) {
next if ($issue->{pull_request});
use Data::Dumper;
print STDERR Dumper($issue);
my @label_list = map { $_->{name} } @{$issue->{labels}};
my $labels = {};
$labels->{$_} = 1 foreach (@label_list);
$labels->{bug}++ if ($labels->{cosmetic} && !$labels->{bug} && !$labels->{feature});
my $extract_labels = sub {
my $label = undef;
foreach (@_) {
$label ||= $_ if (delete $labels->{$_});
}
return $label;
};
my $type = &$extract_labels(qw(bug feature)) || "neither";
my $priority = &$extract_labels(qw(p1 p2 p3 p4 p5)) || "unprioritised";
my $severity = &$extract_labels(qw(minor major critical cosmetic network)) || "no-severity";
my $start = DateTime::Format::ISO8601->parse_datetime($issue->{created_at});
my $end = $issue->{closed_at} ? DateTime::Format::ISO8601->parse_datetime($issue->{closed_at}) : $now;
do {
my $ymd = $start->ymd();
$days->{ $ymd }->{ $type }->{ $priority }->{ $severity }->{ total }++;
$schema->{ $type }->{ $priority }->{ $severity }->{ total }++;
foreach (keys %$labels) {
$days->{ $ymd }->{ $type }->{ $priority }->{ $severity }->{ $_ }++;
$schema->{ $type }->{ $priority }->{ $severity }->{ $_ }++;
}
$start = $start->add(days => 1);
} while (DateTime->compare($start, $end) < 0);
}
print "day,";
foreach my $type (sort keys %{$schema}) {
foreach my $priority (sort keys %{$schema->{$type}}) {
foreach my $severity (sort keys %{$schema->{$type}->{$priority}}) {
# foreach my $tag (sort keys %{$schema->{$type}->{$priority}->{$severity}}) {
# print "\"$type\n$priority\n$severity\n$tag\",";
# }
print "\"$type\n$priority\n$severity\",";
}
}
}
print "\n";
foreach my $day (sort keys %$days) {
print "$day,";
foreach my $type (sort keys %{$schema}) {
foreach my $priority (sort keys %{$schema->{$type}}) {
foreach my $severity (sort keys %{$schema->{$type}->{$priority}}) {
# foreach my $tag (sort keys %{$schema->{$type}->{$priority}->{$severity}}) {
# print $days->{$day}->{$type}->{$priority}->{$severity}->{$tag} || 0;
# print ",";
# }
print $days->{$day}->{$type}->{$priority}->{$severity}->{total} || 0;
print ",";
}
}
}
print "\n";
}

View File

@@ -1,15 +0,0 @@
#!/bin/sh
set -e
version=`git describe --dirty --tags || echo unknown`
npm run build
mkdir -p packages
cp -r vector vector-$version
echo $version > vector-$version/version
tar chvzf packages/vector-$version.tar.gz vector-$version
rm -r vector-$version
echo
echo "Packaged packages/vector-$version.tar.gz"

53
src/Avatar.js Normal file
View File

@@ -0,0 +1,53 @@
/*
Copyright 2015 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 MatrixClientPeg = require('matrix-react-sdk/lib/MatrixClientPeg');
module.exports = {
avatarUrlForMember: function(member, width, height, resizeMethod) {
var url = member.getAvatarUrl(
MatrixClientPeg.get().getHomeserverUrl(),
width,
height,
resizeMethod
);
if (!url) {
// member can be null here currently since on invites, the JS SDK
// does not have enough info to build a RoomMember object for
// the inviter.
url = this.defaultAvatarUrlForString(member ? member.userId : '');
}
return url;
},
defaultAvatarUrlForString: function(s) {
var total = 0;
for (var i = 0; i < s.length; ++i) {
total += s.charCodeAt(i);
}
switch (total % 3) {
case 0:
return "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACgAAAAoCAIAAAADnC86AAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAADRJREFUeNrszQENADAIACB9QjNbxSKP4eagAFnTseHFErFYLBaLxWKxWCwWi8Vi8cX4CzAABSwCRWJw31gAAAAASUVORK5CYII=";
case 1:
return "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACgAAAAoCAIAAAADnC86AAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAADRJREFUeNrszQENADAIACB9chOaxgCP4eagAFk9seHFErFYLBaLxWKxWCwWi8Vi8cX4CzAAtKMCks/JG8MAAAAASUVORK5CYII=";
case 2:
return "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACgAAAAoCAIAAAADnC86AAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAADRJREFUeNrszQENADAIACB9YzNayQCP4eagADldseHFErFYLBaLxWKxWCwWi8Vi8cX4CzAAyiACeHwPiu4AAAAASUVORK5CYII=";
}
}
}

71
src/ContextualMenu.js Normal file
View File

@@ -0,0 +1,71 @@
/*
Copyright 2015 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 React = require('react');
// Shamelessly ripped off Modal.js. There's probably a better way
// of doing reusable widgets like dialog boxes & menus where we go and
// pass in a custom control as the actual body.
module.exports = {
ContextualMenuContainerId: "mx_ContextualMenu_Container",
getOrCreateContainer: function() {
var container = document.getElementById(this.ContextualMenuContainerId);
if (!container) {
container = document.createElement("div");
container.id = this.ContextualMenuContainerId;
document.body.appendChild(container);
}
return container;
},
createMenu: function (Element, props) {
var self = this;
var closeMenu = function() {
React.unmountComponentAtNode(self.getOrCreateContainer());
if (props && props.onFinished) props.onFinished.apply(null, arguments);
};
var position = {
top: props.top - 20,
right: props.right + 8,
};
// FIXME: If a menu uses getDefaultProps it clobbers the onFinished
// property set here so you can't close the menu from a button click!
var menu = (
<div className="mx_ContextualMenu_wrapper">
<div className="mx_ContextualMenu" style={position}>
<img className="mx_ContextualMenu_chevron" src="img/chevron-right.png" width="9" height="16" />
<Element {...props} onFinished={closeMenu}/>
</div>
<div className="mx_ContextualMenu_background" onClick={closeMenu}></div>
</div>
);
React.render(menu, this.getOrCreateContainer());
return {close: closeMenu};
},
};

View File

@@ -1,57 +0,0 @@
/*
Copyright 2015, 2016 OpenMarket Ltd
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
/*
* THIS FILE IS AUTO-GENERATED
* You can edit it you like, but your changes will be overwritten,
* so you'd just be trying to swim upstream like a salmon.
* You are not a salmon.
*
* To update it, run:
* ./reskindex.js -h header
*/
module.exports.components = require('matrix-react-sdk/lib/component-index').components;
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.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.BottomLeftMenuTile'] = require('./components/views/rooms/BottomLeftMenuTile');
module.exports.components['views.rooms.RoomDNDView'] = require('./components/views/rooms/RoomDNDView');
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

@@ -1,66 +0,0 @@
/*
Copyright 2015, 2016 OpenMarket Ltd
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
'use strict';
var React = require('react');
module.exports = React.createClass({
displayName: 'CompatibilityPage',
propTypes: {
onAccept: React.PropTypes.func
},
getDefaultProps: function() {
return {
onAccept: function() {} // NOP
};
},
onAccept: function() {
this.props.onAccept();
},
render: function() {
return (
<div className="mx_CompatibilityPage">
<div className="mx_CompatibilityPage_box">
<p>Sorry, your browser is <b>not</b> able to run Vector.</p>
<p>
Vector uses many advanced browser features, some of which are not
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.
</p>
<p>
With your current browser, the look and feel of the application may
be completely incorrect, and some or all features may not function.
If you want to try it anyway you can continue, but you are on your own
in terms of any issues you may encounter!
</p>
<button onClick={this.onAccept}>
I understand the risks and wish to continue
</button>
</div>
</div>
);
}
});

View File

@@ -1,133 +0,0 @@
/*
Copyright 2015, 2016 OpenMarket Ltd
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
'use strict';
var React = require('react');
var DragDropContext = require('react-dnd').DragDropContext;
var HTML5Backend = require('react-dnd-html5-backend');
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',
getInitialState: function() {
return {
showCallElement: null,
searchFilter: '',
};
},
componentDidMount: function() {
this.dispatcherRef = dis.register(this.onAction);
},
componentWillReceiveProps: function(newProps) {
this._recheckCallElement(newProps.selectedRoom);
},
componentWillUnmount: function() {
dis.unregister(this.dispatcherRef);
},
onAction: function(payload) {
switch (payload.action) {
// listen for call state changes to prod the render method, which
// may hide the global CallView if the call it is tracking is dead
case 'call_state':
this._recheckCallElement(this.props.selectedRoom);
break;
}
},
_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
// audio/video not crap out
var activeCall = CallHandler.getAnyActiveCall();
var callForRoom = CallHandler.getCallForRoom(selectedRoomId);
var showCall = (activeCall && activeCall.call_state === 'connected' && !callForRoom);
this.setState({
showCallElement: showCall
});
},
onHideClick: function() {
dis.dispatch({
action: 'hide_left_panel',
});
},
onCallViewClick: function() {
var call = CallHandler.getAnyActiveCall();
if (call) {
dis.dispatch({
action: 'view_room',
room_id: call.groupRoomId || call.roomId,
});
}
},
onSearch: function(term) {
this.setState({ searchFilter: term });
},
render: function() {
var RoomList = sdk.getComponent('rooms.RoomList');
var BottomLeftMenu = sdk.getComponent('structures.BottomLeftMenu');
var SearchBox = sdk.getComponent('structures.SearchBox');
var collapseButton;
var classes = "mx_LeftPanel mx_fadable";
if (this.props.collapsed) {
classes += " collapsed";
}
else {
// Hide the collapse button until we work out how to display it in the new skin
// collapseButton = <img className="mx_LeftPanel_hideButton" onClick={ this.onHideClick } src="img/hide.png" width="12" height="20" alt="<"/>
}
var callPreview;
if (this.state.showCallElement && !this.props.collapsed) {
var CallView = sdk.getComponent('voip.CallView');
callPreview = (
<CallView
className="mx_LeftPanel_callView" onClick={this.onCallViewClick}
ConferenceHandler={VectorConferenceHandler} />
);
}
return (
<aside className={classes} style={{ opacity: this.props.opacity }}>
<SearchBox collapsed={ this.props.collapsed } onSearch={ this.onSearch } />
{ collapseButton }
{ callPreview }
<RoomList
selectedRoom={this.props.selectedRoom}
collapsed={this.props.collapsed}
searchFilter={this.state.searchFilter}
ConferenceHandler={VectorConferenceHandler} />
<BottomLeftMenu collapsed={this.props.collapsed}/>
</aside>
);
}
});
module.exports = DragDropContext(HTML5Backend)(LeftPanel);

View File

@@ -1,191 +0,0 @@
/*
Copyright 2015, 2016 OpenMarket Ltd
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
'use strict';
var React = require('react');
var sdk = require('matrix-react-sdk');
var Matrix = require("matrix-js-sdk");
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');
module.exports = React.createClass({
displayName: 'RightPanel',
Phase : {
MemberList: 'MemberList',
FileList: 'FileList',
MemberInfo: 'MemberInfo',
},
componentWillMount: function() {
this.dispatcherRef = dis.register(this.onAction);
var cli = MatrixClientPeg.get();
cli.on("RoomState.members", this.onRoomStateMember);
},
componentWillUnmount: function() {
dis.unregister(this.dispatcherRef);
if (MatrixClientPeg.get()) {
MatrixClientPeg.get().removeListener("RoomState.members", this.onRoomStateMember);
}
},
getInitialState: function() {
if (this.props.userId) {
var member = new Matrix.RoomMember(null, this.props.userId);
return {
phase: this.Phase.MemberInfo,
member: member,
}
}
else {
return {
phase: this.Phase.MemberList
}
}
},
onMemberListButtonClick: function() {
if (this.props.collapsed) {
this.setState({ phase: this.Phase.MemberList });
dis.dispatch({
action: 'show_right_panel',
});
}
else {
dis.dispatch({
action: 'hide_right_panel',
});
}
},
onRoomStateMember: function(ev, state, member) {
// redraw the badge on the membership list
if (this.state.phase == this.Phase.MemberList && member.roomId === this.props.roomId) {
this._delayedUpdate();
}
else if (this.state.phase === this.Phase.MemberInfo && member.roomId === this.props.roomId &&
member.userId === this.state.member.userId) {
// refresh the member info (e.g. new power level)
this._delayedUpdate();
}
},
_delayedUpdate: new rate_limited_func(function() {
this.forceUpdate();
}, 500),
onAction: function(payload) {
if (payload.action === "view_user") {
dis.dispatch({
action: 'show_right_panel',
});
if (payload.member) {
this.setState({
phase: this.Phase.MemberInfo,
member: payload.member,
});
}
else {
this.setState({
phase: this.Phase.MemberList
});
}
}
else if (payload.action === "view_room") {
if (this.state.phase === this.Phase.MemberInfo) {
this.setState({
phase: this.Phase.MemberList
});
}
}
},
render: function() {
var MemberList = sdk.getComponent('rooms.MemberList');
var TintableSvg = sdk.getComponent("elements.TintableSvg");
var buttonGroup;
var panel;
var filesHighlight;
var membersHighlight;
if (!this.props.collapsed) {
if (this.state.phase == this.Phase.MemberList || this.state.phase === this.Phase.MemberInfo) {
membersHighlight = <div className="mx_RightPanel_headerButton_highlight"></div>;
}
else if (this.state.phase == this.Phase.FileList) {
filesHighlight = <div className="mx_RightPanel_headerButton_highlight"></div>;
}
}
var membersBadge;
if ((this.state.phase == this.Phase.MemberList || this.state.phase === this.Phase.MemberInfo) && this.props.roomId) {
var cli = MatrixClientPeg.get();
var room = cli.getRoom(this.props.roomId);
if (room) {
membersBadge = <div className="mx_RightPanel_headerButton_badge">{ room.getJoinedMembers().length }</div>;
}
}
if (this.props.roomId) {
buttonGroup =
<div className="mx_RightPanel_headerButtonGroup">
<div className="mx_RightPanel_headerButton" title="Members" onClick={ this.onMemberListButtonClick }>
{ membersBadge }
<TintableSvg src="img/icons-people.svg" width="25" height="25"/>
{ membersHighlight }
</div>
<div className="mx_RightPanel_headerButton mx_RightPanel_filebutton" title="Files">
<TintableSvg src="img/files.svg" width="17" height="22"/>
{ filesHighlight }
</div>
</div>;
}
if (!this.props.collapsed) {
if(this.props.roomId && this.state.phase == this.Phase.MemberList) {
panel = <MemberList roomId={this.props.roomId} key={this.props.roomId} />
}
else if(this.state.phase == this.Phase.MemberInfo) {
var MemberInfo = sdk.getComponent('rooms.MemberInfo');
panel = <MemberInfo member={this.state.member} key={this.props.roomId || this.props.userId} />
}
}
if (!panel) {
panel = <div className="mx_RightPanel_blank"></div>;
}
var classes = "mx_RightPanel mx_fadable";
if (this.props.collapsed) {
classes += " collapsed";
}
return (
<aside className={classes} style={{ opacity: this.props.opacity }}>
<div className="mx_RightPanel_header">
{ buttonGroup }
</div>
{ panel }
<div className="mx_RightPanel_footer">
</div>
</aside>
);
}
});

View File

@@ -1,302 +0,0 @@
/*
Copyright 2015, 2016 OpenMarket Ltd
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
'use strict';
var React = require('react');
var MatrixClientPeg = require('matrix-react-sdk/lib/MatrixClientPeg');
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');
var linkifyMatrix = require('matrix-react-sdk/lib/linkify-matrix');
var sanitizeHtml = require('sanitize-html');
linkifyMatrix(linkify);
module.exports = React.createClass({
displayName: 'RoomDirectory',
getInitialState: function() {
return {
publicRooms: [],
roomAlias: '',
loading: true,
}
},
componentWillMount: function() {
// dis.dispatch({
// action: 'ui_opacity',
// sideOpacity: 0.3,
// middleOpacity: 0.3,
// });
},
componentDidMount: function() {
this.getPublicRooms();
},
componentWillUnmount: function() {
// dis.dispatch({
// action: 'ui_opacity',
// sideOpacity: 1.0,
// middleOpacity: 1.0,
// });
},
getPublicRooms: function() {
var self = this;
MatrixClientPeg.get().publicRooms(function (err, data) {
if (err) {
self.setState({ loading: false });
console.error("Failed to get publicRooms: %s", JSON.stringify(err));
var ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
Modal.createDialog(ErrorDialog, {
title: "Failed to get public room list",
description: err.message
});
}
else {
self.setState({
publicRooms: data.chunk,
loading: false,
});
}
});
},
/**
* A limited interface for removing rooms from the directory.
* Will set the room to not be publicly visible and delete the
* default alias. In the long term, it would be better to allow
* HS admins to do this through the RoomSettings interface, but
* this needs SPEC-417.
*/
removeFromDirectory: function(room) {
var alias = get_display_alias_for_room(room);
var name = room.name || alias || "Unnamed room";
var QuestionDialog = sdk.getComponent("dialogs.QuestionDialog");
var ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
var desc;
if (alias) {
desc = `Delete the room alias '${alias}' and remove '${name}' from the directory?`;
} else {
desc = `Remove '${name}' from the directory?`;
}
Modal.createDialog(QuestionDialog, {
title: "Remove from Directory",
description: desc,
onFinished: (should_delete) => {
if (!should_delete) return;
var Loader = sdk.getComponent("elements.Spinner");
var modal = Modal.createDialog(Loader);
var step = `remove '${name}' from the directory.`;
MatrixClientPeg.get().setRoomDirectoryVisibility(room.room_id, 'private').then(() => {
if (!alias) return;
step = 'delete the alias.';
return MatrixClientPeg.get().deleteAlias(alias);
}).done(() => {
modal.close();
this.getPublicRooms();
}, function(err) {
modal.close();
this.getPublicRooms();
Modal.createDialog(ErrorDialog, {
title: "Failed to "+step,
description: err.toString()
});
});
}
});
},
onRoomClicked: function(room, ev) {
if (ev.shiftKey) {
ev.preventDefault();
this.removeFromDirectory(room);
} else {
this.showRoom(room);
}
},
showRoomAlias: function(alias) {
this.showRoom(null, alias);
},
showRoom: function(room, room_alias) {
var payload = {action: 'view_room'};
if (room) {
// Don't let the user view a room they won't be able to either
// peek or join: fail earlier so they don't have to click back
// to the directory.
if (MatrixClientPeg.get().isGuest()) {
if (!room.world_readable && !room.guest_can_join) {
var NeedToRegisterDialog = sdk.getComponent("dialogs.NeedToRegisterDialog");
Modal.createDialog(NeedToRegisterDialog, {
title: "Failed to join the room",
description: "This room is inaccessible to guests. You may be able to join if you register."
});
return;
}
}
if (!room_alias) {
room_alias = get_display_alias_for_room(room);
}
payload.oob_data = {
avatarUrl: room.avatar_url,
// XXX: This logic is duplicated from the JS SDK which
// would normally decide what the name is.
name: room.name || room_alias || "Unnamed room",
};
}
// It's not really possible to join Matrix rooms by ID because the HS has no way to know
// which servers to start querying. However, there's no other way to join rooms in
// this list without aliases at present, so if roomAlias isn't set here we have no
// choice but to supply the ID.
if (room_alias) {
payload.room_alias = room_alias;
} else {
payload.room_id = room.room_id;
}
dis.dispatch(payload);
},
getRows: function(filter) {
var BaseAvatar = sdk.getComponent('avatars.BaseAvatar');
if (!this.state.publicRooms) return [];
var rooms = this.state.publicRooms.filter(function(a) {
// FIXME: if incrementally typing, keep narrowing down the search set
// incrementally rather than starting over each time.
return (((a.name && a.name.toLowerCase().search(filter.toLowerCase()) >= 0) ||
(a.aliases && a.aliases[0].toLowerCase().search(filter.toLowerCase()) >= 0)) &&
a.num_joined_members > 0);
}).sort(function(a,b) {
return a.num_joined_members - b.num_joined_members;
});
var rows = [];
var self = this;
var guestRead, guestJoin, perms;
for (var i = 0; i < rooms.length; i++) {
var name = rooms[i].name || get_display_alias_for_room(rooms[i]) || "Unnamed room";
guestRead = null;
guestJoin = null;
if (rooms[i].world_readable) {
guestRead = (
<div className="mx_RoomDirectory_perm">World readable</div>
);
}
if (rooms[i].guest_can_join) {
guestJoin = (
<div className="mx_RoomDirectory_perm">Guests can join</div>
);
}
perms = null;
if (guestRead || guestJoin) {
perms = <div className="mx_RoomDirectory_perms">{guestRead} {guestJoin}</div>;
}
var topic = rooms[i].topic || '';
topic = linkifyString(sanitizeHtml(topic));
rows.unshift(
<tr key={ rooms[i].room_id }
onClick={self.onRoomClicked.bind(self, rooms[i])}
// cancel onMouseDown otherwise shift-clicking highlights text
onMouseDown={(ev) => {ev.preventDefault();}}
>
<td className="mx_RoomDirectory_roomAvatar">
<BaseAvatar width={24} height={24} resizeMethod='crop'
name={ name } idName={ name }
url={ ContentRepo.getHttpUriForMxc(
MatrixClientPeg.get().getHomeserverUrl(),
rooms[i].avatar_url, 24, 24, "crop") } />
</td>
<td className="mx_RoomDirectory_roomDescription">
<div className="mx_RoomDirectory_name">{ name }</div>&nbsp;
{ perms }
<div className="mx_RoomDirectory_topic"
onClick={ function(e) { e.stopPropagation() } }
dangerouslySetInnerHTML={{ __html: topic }}/>
<div className="mx_RoomDirectory_alias">{ get_display_alias_for_room(rooms[i]) }</div>
</td>
<td className="mx_RoomDirectory_roomMemberCount">
{ rooms[i].num_joined_members }
</td>
</tr>
);
}
return rows;
},
onKeyUp: function(ev) {
this.forceUpdate();
this.setState({ roomAlias : this.refs.roomAlias.value })
if (ev.key == "Enter") {
this.showRoomAlias(this.refs.roomAlias.value);
}
},
render: function() {
if (this.state.loading) {
var Loader = sdk.getComponent("elements.Spinner");
return (
<div className="mx_RoomDirectory">
<Loader />
</div>
);
}
var SimpleRoomHeader = sdk.getComponent('rooms.SimpleRoomHeader');
return (
<div className="mx_RoomDirectory">
<SimpleRoomHeader title="Directory" />
<div className="mx_RoomDirectory_list">
<input ref="roomAlias" placeholder="Join a room (e.g. #foo:domain.com)" className="mx_RoomDirectory_input" size="64" onKeyUp={ this.onKeyUp }/>
<GeminiScrollbar className="mx_RoomDirectory_tableWrapper">
<table ref="directory_table" className="mx_RoomDirectory_table">
<tbody>
{ this.getRows(this.state.roomAlias) }
</tbody>
</table>
</GeminiScrollbar>
</div>
</div>
);
}
});
// Similar to matrix-react-sdk's MatrixTools.getDisplayAliasForRoom
// but works with the objects we get from the public room list
function get_display_alias_for_room(room) {
return room.canonical_alias || (room.aliases ? room.aliases[0] : "");
}

View File

@@ -1,429 +0,0 @@
/*
Copyright 2015, 2016 OpenMarket Ltd
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
'use strict';
var React = require('react');
var DropTarget = require('react-dnd').DropTarget;
var sdk = require('matrix-react-sdk')
var dis = require('matrix-react-sdk/lib/dispatcher');
var Unread = require('matrix-react-sdk/lib/Unread');
var MatrixClientPeg = require('matrix-react-sdk/lib/MatrixClientPeg');
// turn this on for drop & drag console debugging galore
var debug = false;
var roomListTarget = {
canDrop: function() {
return true;
},
drop: function(props, monitor, component) {
if (debug) console.log("dropped on sublist")
},
hover: function(props, monitor, component) {
var item = monitor.getItem();
if (component.state.sortedList.length == 0 && props.editable) {
if (debug) console.log("hovering on sublist " + props.label + ", isOver=" + monitor.isOver());
if (item.targetList !== component) {
item.targetList.removeRoomTile(item.room);
item.targetList = component;
}
component.moveRoomTile(item.room, 0);
}
},
};
var RoomSubList = React.createClass({
displayName: 'RoomSubList',
debug: debug,
propTypes: {
list: React.PropTypes.arrayOf(React.PropTypes.object).isRequired,
label: React.PropTypes.string.isRequired,
tagName: React.PropTypes.string,
editable: React.PropTypes.bool,
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,
incomingCall: React.PropTypes.object,
onShowMoreRooms: React.PropTypes.func,
searchFilter: React.PropTypes.string,
},
getInitialState: function() {
return {
hidden: this.props.startAsHidden || false,
truncateAt: 20,
sortedList: [],
};
},
getDefaultProps: function() {
return {
onHeaderClick: function() {}, // NOP
onShowMoreRooms: function() {} // NOP
};
},
componentWillMount: function() {
this.sortList(this.applySearchFilter(this.props.list, this.props.searchFilter), this.props.order);
},
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);
},
applySearchFilter: function(list, filter) {
if (filter === "") return list;
return list.filter((room) => {
return room.name && room.name.toLowerCase().indexOf(filter.toLowerCase()) >= 0
});
},
onClick: function(ev) {
var isHidden = !this.state.hidden;
this.setState({ hidden : isHidden });
if (isHidden) {
// as good a way as any to reset the truncate state
this.setState({ truncateAt : 20 });
this.props.onShowMoreRooms();
}
this.props.onHeaderClick(isHidden);
},
tsOfNewestEvent: function(room) {
for (var i = room.timeline.length - 1; i >= 0; --i) {
var ev = room.timeline[i];
if (Unread.eventTriggersUnreadCount(ev) ||
(ev.sender && ev.sender.userId === MatrixClientPeg.get().credentials.userId))
{
return ev.getTs();
}
}
// 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) {
return room.timeline[0].getTs();
} else {
return Number.MAX_SAFE_INTEGER;
}
},
// TODO: factor the comparators back out into a generic comparator
// so that view_prev_room and view_next_room can do the right thing
recentsComparator: function(roomA, roomB) {
return this.tsOfNewestEvent(roomB) - this.tsOfNewestEvent(roomA);
},
lexicographicalComparator: function(roomA, roomB) {
return roomA.name > roomB.name ? 1 : -1;
},
// Generates the manual comparator using the given list
manualComparator: function(roomA, roomB) {
if (!roomA.tags[this.props.tagName] || !roomB.tags[this.props.tagName]) return 0;
// Make sure the room tag has an order element, if not set it to be the bottom
var a = roomA.tags[this.props.tagName].order;
var b = roomB.tags[this.props.tagName].order;
// Order undefined room tag orders to the bottom
if (a === undefined && b !== undefined) {
return 1;
} else if (a !== undefined && b === undefined) {
return -1;
}
return a == b ? this.lexicographicalComparator(roomA, roomB) : ( a > b ? 1 : -1);
},
sortList: function(list, order) {
if (list === undefined) list = this.state.sortedList;
if (order === undefined) order = this.props.order;
var comparator;
list = list || [];
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) });
},
moveRoomTile: function(room, atIndex) {
if (debug) console.log("moveRoomTile: id " + room.roomId + ", atIndex " + atIndex);
//console.log("moveRoomTile before: " + JSON.stringify(this.state.rooms));
var found = this.findRoomTile(room);
var rooms = this.state.sortedList;
if (found.room) {
if (debug) console.log("removing at index " + found.index + " and adding at index " + atIndex);
rooms.splice(found.index, 1);
rooms.splice(atIndex, 0, found.room);
}
else {
if (debug) console.log("Adding at index " + atIndex);
rooms.splice(atIndex, 0, room);
}
this.setState({ sortedList: rooms });
// console.log("moveRoomTile after: " + JSON.stringify(this.state.rooms));
},
// XXX: this isn't invoked via a property method but indirectly via
// the roomList property method. Unsure how evil this is.
removeRoomTile: function(room) {
if (debug) console.log("remove room " + room.roomId);
var found = this.findRoomTile(room);
var rooms = this.state.sortedList;
if (found.room) {
rooms.splice(found.index, 1);
}
else {
console.warn("Can't remove room " + room.roomId + " - can't find it");
}
this.setState({ sortedList: rooms });
},
findRoomTile: function(room) {
var index = this.state.sortedList.indexOf(room);
if (index >= 0) {
// console.log("found: room: " + room.roomId + " with index " + index);
}
else {
if (debug) console.log("didn't find room");
room = null;
}
return ({
room: room,
index: index,
});
},
calcManualOrderTagData: function(room) {
var index = this.state.sortedList.indexOf(room);
// we sort rooms by the lexicographic ordering of the 'order' metadata on their tags.
// for convenience, we calculate this for now a floating point number between 0.0 and 1.0.
var orderA = 0.0; // by default we're next to the beginning of the list
if (index > 0) {
var prevTag = this.state.sortedList[index - 1].tags[this.props.tagName];
if (!prevTag) {
console.error("Previous room in sublist is not tagged to be in this list. This should never happen.")
}
else if (prevTag.order === undefined) {
console.error("Previous room in sublist has no ordering metadata. This should never happen.");
}
else {
orderA = prevTag.order;
}
}
var orderB = 1.0; // by default we're next to the end of the list too
if (index < this.state.sortedList.length - 1) {
var nextTag = this.state.sortedList[index + 1].tags[this.props.tagName];
if (!nextTag) {
console.error("Next room in sublist is not tagged to be in this list. This should never happen.")
}
else if (nextTag.order === undefined) {
console.error("Next room in sublist has no ordering metadata. This should never happen.");
}
else {
orderB = nextTag.order;
}
}
var order = (orderA + orderB) / 2.0;
if (order === orderA || order === orderB) {
console.error("Cannot describe new list position. This should be incredibly unlikely.");
// TODO: renumber the list
}
return order;
},
makeRoomTiles: function() {
var self = this;
var RoomTile = sdk.getComponent("rooms.RoomTile");
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 (
<RoomTile
room={ room }
roomSubList={ self }
key={ room.roomId }
collapsed={ self.props.collapsed || false}
selected={ selected }
unread={ Unread.doesRoomHaveUnreadMessages(room) }
highlight={ room.getUnreadNotificationCount('highlight') > 0 || self.props.label === 'Invites' }
isInvite={ self.props.label === 'Invites' }
incomingCall={ self.props.incomingCall && (self.props.incomingCall.roomId === room.roomId) ? self.props.incomingCall : null } />
);
});
},
_getHeaderJsx: function() {
var TintableSvg = sdk.getComponent("elements.TintableSvg");
return (
<h2 onClick={ this.onClick } className="mx_RoomSubList_label">
{ this.props.collapsed ? '' : this.props.label }
<img className="mx_RoomSubList_chevron"
src={ this.state.hidden ? "img/list-close.svg" : "img/list-open.svg" }
width="10" height="10" />
</h2>
);
},
_createOverflowTile: function(overflowCount, totalCount) {
var BaseAvatar = sdk.getComponent('avatars.BaseAvatar');
// XXX: this is duplicated from RoomTile - factor it out
return (
<div className="mx_RoomTile mx_RoomTile_ellipsis" onClick={this._showFullMemberList}>
<div className="mx_RoomTile_avatar">
<BaseAvatar url="img/ellipsis.svg" name="..." width={24} height={24} />
</div>
<div className="mx_RoomTile_name">and { overflowCount } others...</div>
</div>
);
},
_showFullMemberList: function() {
this.setState({
truncateAt: -1
});
this.props.onShowMoreRooms();
},
// Fix any undefined order elements of a room in a manual ordered list
// room.tag[tagname].order
_fixUndefinedOrder: function(list) {
if (this.props.order === "manual") {
var order = 0.0;
var self = this;
// Find the highest (lowest position) order of a room in a manual ordered list
list.forEach(function(room) {
if (room.tags.hasOwnProperty(self.props.tagName)) {
if (order < room.tags[self.props.tagName].order) {
order = room.tags[self.props.tagName].order;
}
}
});
// Fix any undefined order elements of a room in a manual ordered list
// Do this one at a time, as each time a rooms tag data is updated the RoomList
// 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 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) {
MatrixClientPeg.get().setRoomTag(list[i].roomId, self.props.tagName, {order: (order + 1.0) / 2.0}).finally(function() {
// Do any final stuff here
}).fail(function(err) {
var ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
Modal.createDialog(ErrorDialog, {
title: "Failed to add tag " + self.props.tagName + " to room",
description: err.toString()
});
});
break;
};
};
}
},
render: function() {
var connectDropTarget = this.props.connectDropTarget;
var RoomDropTarget = sdk.getComponent('rooms.RoomDropTarget');
var TruncatedList = sdk.getComponent('elements.TruncatedList');
var label = this.props.collapsed ? null : this.props.label;
//console.log("render: " + JSON.stringify(this.state.sortedList));
var target;
if (this.state.sortedList.length == 0 && this.props.editable) {
target = <RoomDropTarget label={ 'Drop here to ' + this.props.verb }/>;
}
if (this.state.sortedList.length > 0 || this.props.editable) {
var subList;
var classes = "mx_RoomSubList";
if (!this.state.hidden) {
subList = <TruncatedList className={ classes } truncateAt={this.state.truncateAt}
createOverflowElement={this._createOverflowTile} >
{ target }
{ this.makeRoomTiles() }
</TruncatedList>;
}
else {
subList = <TruncatedList className={ classes }>
</TruncatedList>;
}
return connectDropTarget(
<div>
{ this._getHeaderJsx() }
{ subList }
</div>
);
}
else {
var Loader = sdk.getComponent("elements.Spinner");
return (
<div className="mx_RoomSubList">
{ this.props.alwaysShowHeader ? this._getHeaderJsx() : undefined }
{ (this.props.showSpinner && !this.state.hidden) ? <Loader /> : undefined }
</div>
);
}
}
});
// Export the wrapped version, inlining the 'collect' functions
// to more closely resemble the ES7
module.exports =
DropTarget('RoomTile', roomListTarget, function(connect) {
return {
connectDropTarget: connect.dropTarget(),
}
})(RoomSubList);

View File

@@ -1,119 +0,0 @@
/*
Copyright 2015, 2016 OpenMarket Ltd
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
'use strict';
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');
module.exports = React.createClass({
displayName: 'SearchBox',
propTypes: {
collapsed: React.PropTypes.bool,
onSearch: React.PropTypes.func,
},
getInitialState: function() {
return {
searchTerm: "",
};
},
onChange: function() {
if (!this.refs.search) return;
this.setState({ searchTerm: this.refs.search.value });
this.onSearch();
},
onSearch: new rate_limited_func(
function() {
this.props.onSearch(this.refs.search.value);
},
100
),
onToggleCollapse: function(show) {
if (show) {
dis.dispatch({
action: 'show_left_panel',
});
}
else {
dis.dispatch({
action: 'hide_left_panel',
});
}
},
render: function() {
var TintableSvg = sdk.getComponent('elements.TintableSvg');
var toggleCollapse;
if (this.props.collapsed) {
toggleCollapse =
<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 =
<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 ?
<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"
/>
</div>
:
<TintableSvg
key="button"
className="mx_SearchBox_searchButton"
src="img/icons-search-copy.svg" width="13" height="13"
/>,
<input
key="searchfield"
type="text"
ref="search"
className="mx_SearchBox_search"
value={ this.state.searchTerm }
onChange={ this.onChange }
placeholder="Filter room names"
/>
];
}
var self = this;
return (
<div className="mx_SearchBox">
{ searchControls }
{ toggleCollapse }
</div>
);
}
});

View File

@@ -1,54 +0,0 @@
/*
Copyright 2015, 2016 OpenMarket Ltd
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
'use strict';
var React = require('react');
module.exports = React.createClass({
displayName: 'ViewSource',
propTypes: {
onFinished: React.PropTypes.func.isRequired
},
componentDidMount: function() {
document.addEventListener("keydown", this.onKeyDown);
},
componentWillUnmount: function() {
document.removeEventListener("keydown", this.onKeyDown);
},
onKeyDown: function(ev) {
if (ev.keyCode == 27) { // escape
ev.stopPropagation();
ev.preventDefault();
this.props.onFinished();
}
},
render: function() {
return (
<div className="mx_ViewSource">
<pre>
{JSON.stringify(this.props.mxEvent.event, null, 2)}
</pre>
</div>
);
}
});

View File

@@ -1,155 +0,0 @@
/*
Copyright 2015, 2016 OpenMarket Ltd
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
'use strict';
var React = require('react');
var MatrixClientPeg = require('matrix-react-sdk/lib/MatrixClientPeg');
var dis = require('matrix-react-sdk/lib/dispatcher');
var sdk = require('matrix-react-sdk')
var Modal = require('matrix-react-sdk/lib/Modal');
var Resend = require("matrix-react-sdk/lib/Resend");
module.exports = React.createClass({
displayName: 'MessageContextMenu',
propTypes: {
/* the MatrixEvent associated with the context menu */
mxEvent: React.PropTypes.object.isRequired,
/* an optional EventTileOps implementation that can be used to unhide preview widgets */
eventTileOps: React.PropTypes.object,
/* callback called when the menu is dismissed */
onFinished: React.PropTypes.func,
},
onResendClick: function() {
Resend.resend(this.props.mxEvent);
if (this.props.onFinished) this.props.onFinished();
},
onViewSourceClick: function() {
var ViewSource = sdk.getComponent('structures.ViewSource');
Modal.createDialog(ViewSource, {
mxEvent: this.props.mxEvent
}, 'mx_Dialog_viewsource');
if (this.props.onFinished) this.props.onFinished();
},
onRedactClick: function() {
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();
},
onCancelSendClick: function() {
Resend.removeFromQueue(this.props.mxEvent);
if (this.props.onFinished) this.props.onFinished();
},
onPermalinkClick: function() {
if (this.props.onFinished) this.props.onFinished();
},
onUnhidePreviewClick: function() {
if (this.props.eventTileOps) {
this.props.eventTileOps.unhideWidget();
}
if (this.props.onFinished) this.props.onFinished();
},
render: function() {
var eventStatus = this.props.mxEvent.status;
var resendButton;
var viewSourceButton;
var redactButton;
var cancelButton;
var permalinkButton;
var unhidePreviewButton;
if (eventStatus === 'not_sent') {
resendButton = (
<div className="mx_MessageContextMenu_field" onClick={this.onResendClick}>
Resend
</div>
);
}
if (!eventStatus) { // sent
redactButton = (
<div className="mx_MessageContextMenu_field" onClick={this.onRedactClick}>
Redact
</div>
);
}
if (eventStatus === "queued" || eventStatus === "not_sent") {
cancelButton = (
<div className="mx_MessageContextMenu_field" onClick={this.onCancelSendClick}>
Cancel Sending
</div>
);
}
viewSourceButton = (
<div className="mx_MessageContextMenu_field" onClick={this.onViewSourceClick}>
View Source
</div>
);
if (this.props.eventTileOps) {
if (this.props.eventTileOps.isWidgetHidden()) {
unhidePreviewButton = (
<div className="mx_MessageContextMenu_field" onClick={this.onUnhidePreviewClick}>
Unhide Preview
</div>
)
}
}
// XXX: if we use room ID, we should also include a server where the event can be found (other than in the domain of the event ID)
permalinkButton = (
<div className="mx_MessageContextMenu_field">
<a href={ "https://matrix.to/#/" + this.props.mxEvent.getRoomId() +"/"+ this.props.mxEvent.getId() }
target="_blank" onClick={ this.onPermalinkClick }>Permalink</a>
</div>
);
return (
<div>
{resendButton}
{redactButton}
{cancelButton}
{viewSourceButton}
{unhidePreviewButton}
{permalinkButton}
</div>
);
}
});

View File

@@ -1,144 +0,0 @@
/*
Copyright 2015, 2016 OpenMarket Ltd
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
'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

@@ -1,201 +0,0 @@
/*
Copyright 2015, 2016 OpenMarket Ltd
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
'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');
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() {
return {
isFavourite: this.props.room.tags.hasOwnProperty("m.favourite"),
isLowPriority: this.props.room.tags.hasOwnProperty("m.lowpriority"),
};
},
_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");
}
},
_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() {
var myUserId = MatrixClientPeg.get().credentials.userId;
var myMember = this.props.room.getMember(myUserId);
var favouriteClasses = classNames({
'mx_RoomTagContextMenu_field': true,
'mx_RoomTagContextMenu_fieldSet': this.state.isFavourite,
'mx_RoomTagContextMenu_fieldDisabled': false,
});
var lowPriorityClasses = classNames({
'mx_RoomTagContextMenu_field': true,
'mx_RoomTagContextMenu_fieldSet': this.state.isLowPriority,
'mx_RoomTagContextMenu_fieldDisabled': false,
});
var leaveClasses = classNames({
'mx_RoomTagContextMenu_field': true,
'mx_RoomTagContextMenu_fieldSet': false,
'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>
<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,82 +0,0 @@
/*
Copyright 2016 Aviral Dasgupta
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
import React from 'react';
import sdk from 'matrix-react-sdk';
import request from 'browser-request';
const REPOS = ['vector-im/vector-web', 'matrix-org/matrix-react-sdk', 'matrix-org/matrix-js-sdk'];
export default class ChangelogDialog extends React.Component {
constructor(props) {
super(props);
this.state = {};
}
componentDidMount() {
const version = this.props.newVersion.split('-');
const version2 = this.props.version.split('-');
if(version == null || version2 == null) return;
for(let i=0; i<REPOS.length; 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});
});
}
}
render() {
const Spinner = sdk.getComponent('views.elements.Spinner');
const QuestionDialog = sdk.getComponent('dialogs.QuestionDialog');
const logs = REPOS.map(repo => {
if (this.state[repo] == null) return <Spinner key={repo} />;
return (
<div key={repo}>
<h2>{repo}</h2>
{this.state[repo].map(commit =>
<div key={commit.commit.url}><a href={commit.commit.url}>{commit.commit.message}</a></div>
)}
</div>
)
});
const content = (
<div className="mx_ChangelogDialog_content">
{this.props.version == null || this.props.newVersion == null ? <h2>Unavailable</h2> : logs}
</div>
);
return (
<QuestionDialog
title="Changelog"
description={content}
button="Update"
onFinished={this.props.onFinished}
/>
)
}
}
ChangelogDialog.propTypes = {
version: React.PropTypes.string.isRequired,
newVersion: React.PropTypes.string.isRequired,
onFinished: React.PropTypes.func.isRequired,
};

View File

@@ -1,189 +0,0 @@
/*
Copyright 2015, 2016 OpenMarket Ltd
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
'use strict';
var React = require('react');
var MatrixClientPeg = require('matrix-react-sdk/lib/MatrixClientPeg');
var DateUtils = require('matrix-react-sdk/lib/DateUtils');
var filesize = require('filesize');
module.exports = React.createClass({
displayName: 'ImageView',
propTypes: {
src: React.PropTypes.string.isRequired, // the source of the image being displayed
name: React.PropTypes.string, // the main title ('name') for the image
link: React.PropTypes.string, // the link (if any) applied to the name of the image
width: React.PropTypes.number, // width of the image src in pixels
height: React.PropTypes.number, // height of the image src in pixels
fileSize: React.PropTypes.number, // size of the image src in bytes
onFinished: React.PropTypes.func.isRequired, // callback when the lightbox is dismissed
// the event (if any) that the Image is displaying. Used for event-specific stuff like
// redactions, senders, timestamps etc. Other descriptors are taken from the explicit
// properties above, which let us use lightboxes to display images which aren't associated
// with events.
mxEvent: React.PropTypes.object,
},
// XXX: keyboard shortcuts for managing dialogs should be done by the modal
// dialog base class somehow, surely...
componentDidMount: function() {
document.addEventListener("keydown", this.onKeyDown);
},
componentWillUnmount: function() {
document.removeEventListener("keydown", this.onKeyDown);
},
onKeyDown: function(ev) {
if (ev.keyCode == 27) { // escape
ev.stopPropagation();
ev.preventDefault();
this.props.onFinished();
}
},
onRedactClick: function() {
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 + ")"
});
});
},
getName: function () {
var name = this.props.name;
if (name && this.props.link) {
name = <a href={ this.props.link } target="_blank" rel="noopener">{ name }</a>;
}
return name;
},
render: function() {
/*
// In theory max-width: 80%, max-height: 80% on the CSS should work
// but in practice, it doesn't, so do it manually:
var width = this.props.width || 500;
var height = this.props.height || 500;
var maxWidth = document.documentElement.clientWidth * 0.8;
var maxHeight = document.documentElement.clientHeight * 0.8;
var widthFrac = width / maxWidth;
var heightFrac = height / maxHeight;
var displayWidth;
var displayHeight;
if (widthFrac > heightFrac) {
displayWidth = Math.min(width, maxWidth);
displayHeight = (displayWidth / width) * height;
} else {
displayHeight = Math.min(height, maxHeight);
displayWidth = (displayHeight / height) * width;
}
var style = {
width: displayWidth,
height: displayHeight
};
*/
var style, res;
if (this.props.width && this.props.height) {
style = {
width: this.props.width,
height: this.props.height,
};
res = style.width + "x" + style.height + "px";
}
var size;
if (this.props.fileSize) {
size = filesize(this.props.fileSize);
}
var size_res;
if (size && res) {
size_res = size + ", " + res;
}
else {
size_res = size || res;
}
var showEventMeta = !!this.props.mxEvent;
var eventMeta;
if(showEventMeta) {
eventMeta = (<div className="mx_ImageView_metadata">
Uploaded on { DateUtils.formatDate(new Date(this.props.mxEvent.getTs())) } by { this.props.mxEvent.getSender() }
</div>);
}
var eventRedact;
if(showEventMeta) {
eventRedact = (<div className="mx_ImageView_button" onClick={this.onRedactClick}>
Redact
</div>);
}
return (
<div className="mx_ImageView">
<div className="mx_ImageView_lhs">
</div>
<div className="mx_ImageView_content">
<img src={this.props.src} style={style}/>
<div className="mx_ImageView_labelWrapper">
<div className="mx_ImageView_label">
<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 } target="_blank" rel="noopener">
<div className="mx_ImageView_download">
Download this file<br/>
<span className="mx_ImageView_size">{ size_res }</span>
</div>
</a>
{ eventRedact }
<div className="mx_ImageView_shim">
</div>
</div>
</div>
</div>
<div className="mx_ImageView_rhs">
</div>
</div>
);
}
});

View File

@@ -1,43 +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.
*/
var React = require('react');
var dis = require('matrix-react-sdk/lib/dispatcher')
module.exports = React.createClass({
displayName: 'GuestWarningBar',
onRegisterClicked: function() {
dis.dispatch({'action': 'start_upgrade_registration'});
},
onLoginClicked: function() {
dis.dispatch({'action': 'logout'});
dis.dispatch({'action': 'start_login'});
},
render: function() {
return (
<div className="mx_GuestWarningBar">
<img className="mx_GuestWarningBar_warning" src="img/warning.svg" width="24" height="23" alt="/!\"/>
<div>
You are using Vector as a guest. <a onClick={this.onRegisterClicked}>Register</a> or <a onClick={this.onLoginClicked}>log in</a> to access more rooms and features.
</div>
</div>
);
}
});

View File

@@ -1,52 +0,0 @@
/*
Copyright 2015, 2016 OpenMarket Ltd
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
'use strict';
var React = require('react');
var sdk = require('matrix-react-sdk');
import Modal from 'matrix-react-sdk/lib/Modal';
export default function NewVersionBar(props) {
const onChangelogClicked = () => {
const ChangelogDialog = sdk.getComponent('dialogs.ChangelogDialog');
Modal.createDialog(ChangelogDialog, {
version: props.version,
newVersion: props.newVersion,
onFinished: (update) => {
if(update) {
window.location.reload();
}
}
});
};
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 Vector is available. Refresh your browser.
</div>
<button className="mx_MatrixToolbar_action" onClick={onChangelogClicked}>Changelog</button>
</div>
);
}
NewVersionBar.propTypes = {
version: React.PropTypes.string.isRequired,
newVersion: React.PropTypes.string.isRequired,
};

View File

@@ -1,205 +0,0 @@
/*
Copyright 2015, 2016 OpenMarket Ltd
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
'use strict';
var React = require('react');
var DragSource = require('react-dnd').DragSource;
var DropTarget = require('react-dnd').DropTarget;
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');
/**
* Specifies the drag source contract.
* Only `beginDrag` function is required.
*/
var roomTileSource = {
canDrag: function(props, monitor) {
return props.roomSubList.props.editable;
},
beginDrag: function (props) {
// Return the data describing the dragged item
var item = {
room: props.room,
originalList: props.roomSubList,
originalIndex: props.roomSubList.findRoomTile(props.room).index,
targetList: props.roomSubList, // at first target is same as original
// lastTargetRoom: null,
// lastYOffset: null,
// lastYDelta: null,
};
if (props.roomSubList.debug) console.log("roomTile beginDrag for " + item.room.roomId);
// doing this 'correctly' with state causes react-dnd to break seemingly due to the state transitions
props.room._dragging = true;
return item;
},
endDrag: function (props, monitor, component) {
var item = monitor.getItem();
if (props.roomSubList.debug) console.log("roomTile endDrag for " + item.room.roomId + " with didDrop=" + monitor.didDrop());
props.room._dragging = false;
if (monitor.didDrop()) {
if (props.roomSubList.debug) console.log("force updating component " + item.targetList.props.label);
item.targetList.forceUpdate(); // as we're not using state
}
if (monitor.didDrop() && item.targetList.props.editable) {
// if we moved lists, remove the old tag
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, item.originalList.props.tagName).finally(function() {
//component.state.set({ spinner: component.state.spinner-- });
}).fail(function(err) {
var ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
Modal.createDialog(ErrorDialog, {
title: "Failed to remove tag " + item.originalList.props.tagName + " from room",
description: err.toString()
});
});
}
var newOrder= {};
if (item.targetList.props.order === 'manual') {
newOrder['order'] = item.targetList.calcManualOrderTagData(item.room);
}
// if we moved lists or the ordering changed, add the new tag
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, item.targetList.props.tagName, newOrder).finally(function() {
//component.state.set({ spinner: component.state.spinner-- });
}).fail(function(err) {
var ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
Modal.createDialog(ErrorDialog, {
title: "Failed to add tag " + item.targetList.props.tagName + " to room",
description: err.toString()
});
});
}
}
else {
// cancel the drop and reset our original position
if (props.roomSubList.debug) console.log("cancelling drop & drag");
props.roomSubList.moveRoomTile(item.room, item.originalIndex);
if (item.targetList && item.targetList !== item.originalList) {
item.targetList.removeRoomTile(item.room);
}
}
}
};
var roomTileTarget = {
canDrop: function() {
return false;
},
hover: function(props, monitor) {
var item = monitor.getItem();
//var off = monitor.getClientOffset();
// console.log("hovering on room " + props.room.roomId + ", isOver=" + monitor.isOver());
//console.log("item.targetList=" + item.targetList + ", roomSubList=" + props.roomSubList);
var switchedTarget = false;
if (item.targetList !== props.roomSubList) {
// we've switched target, so remove the tile from the previous target.
// n.b. the previous target might actually be the source list.
if (props.roomSubList.debug) console.log("switched target sublist");
switchedTarget = true;
item.targetList.removeRoomTile(item.room);
item.targetList = props.roomSubList;
}
if (!item.targetList.props.editable) return;
if (item.targetList.props.order === 'manual') {
if (item.room.roomId !== props.room.roomId && props.room !== item.lastTargetRoom) {
// find the offset of the target tile in the list.
var roomTile = props.roomSubList.findRoomTile(props.room);
// shuffle the list to add our tile to that position.
props.roomSubList.moveRoomTile(item.room, roomTile.index);
}
// stop us from flickering between our droptarget and the previous room.
// whenever the cursor changes direction we have to reset the flicker-damping.
/*
var yDelta = off.y - item.lastYOffset;
if ((yDelta > 0 && item.lastYDelta < 0) ||
(yDelta < 0 && item.lastYDelta > 0))
{
// the cursor changed direction - forget our previous room
item.lastTargetRoom = null;
}
else {
// track the last room we were hovering over so we can stop
// bouncing back and forth if the droptarget is narrower than
// the other list items. The other way to do this would be
// to reduce the size of the hittarget on the list items, but
// can't see an easy way to do that.
item.lastTargetRoom = props.room;
}
if (yDelta) item.lastYDelta = yDelta;
item.lastYOffset = off.y;
*/
}
else if (switchedTarget) {
if (!props.roomSubList.findRoomTile(item.room).room) {
// add to the list in the right place
props.roomSubList.moveRoomTile(item.room, 0);
}
// we have to sort the list whatever to recalculate it
props.roomSubList.sortList();
}
},
};
// Export the wrapped version, inlining the 'collect' functions
// to more closely resemble the ES7
module.exports =
DropTarget('RoomTile', roomTileTarget, function(connect, monitor) {
return {
// Call this function inside render()
// to let React DnD handle the drag events:
connectDropTarget: connect.dropTarget(),
isOver: monitor.isOver(),
}
})(
DragSource('RoomTile', roomTileSource, function(connect, monitor) {
return {
// Call this function inside render()
// to let React DnD handle the drag events:
connectDragSource: connect.dragSource(),
// You can ask the monitor about the current drag state:
isDragging: monitor.isDragging()
};
})(RoomTile));
module.exports.replaces = 'RoomTile';

View File

@@ -1,69 +0,0 @@
/*
Copyright 2015, 2016 OpenMarket Ltd
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
'use strict';
var React = require('react');
var MatrixClientPeg = require('matrix-react-sdk/lib/MatrixClientPeg');
var sdk = require('matrix-react-sdk');
var classNames = require('classnames');
module.exports = React.createClass({
displayName: 'SearchBar',
getInitialState: function() {
return ({
scope: 'Room'
});
},
onThisRoomClick: function() {
this.setState({ scope: 'Room' });
},
onAllRoomsClick: function() {
this.setState({ scope: 'All' });
},
onSearchChange: function(e) {
if (e.keyCode === 13) { // on enter...
this.onSearch();
}
if (e.keyCode === 27) { // escape...
this.props.onCancelClick();
}
},
onSearch: function() {
this.props.onSearch(this.refs.search_term.value, this.state.scope);
},
render: function() {
var searchButtonClasses = classNames({ mx_SearchBar_searchButton : true, mx_SearchBar_searching: this.props.searchInProgress });
var thisRoomClasses = classNames({ mx_SearchBar_button : true, mx_SearchBar_unselected : this.state.scope !== 'Room' });
var allRoomsClasses = classNames({ mx_SearchBar_button : true, mx_SearchBar_unselected : this.state.scope !== 'All' });
return (
<div className="mx_SearchBar">
<input ref="search_term" className="mx_SearchBar_input" type="text" autoFocus={true} placeholder="Search..." onKeyDown={this.onSearchChange}/>
<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

@@ -1,56 +0,0 @@
/*
Copyright 2015, 2016 OpenMarket Ltd
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
'use strict';
var React = require('react');
var sdk = require('matrix-react-sdk');
var MatrixClientPeg = require('matrix-react-sdk/lib/MatrixClientPeg');
module.exports = React.createClass({
displayName: 'IntegrationsManager',
propTypes: {
src: React.PropTypes.string.isRequired, // the source of the integration manager being embedded
onFinished: React.PropTypes.func.isRequired, // callback when the lightbox is dismissed
},
// XXX: keyboard shortcuts for managing dialogs should be done by the modal
// dialog base class somehow, surely...
componentDidMount: function() {
document.addEventListener("keydown", this.onKeyDown);
},
componentWillUnmount: function() {
document.removeEventListener("keydown", this.onKeyDown);
},
onKeyDown: function(ev) {
if (ev.keyCode == 27) { // escape
ev.stopPropagation();
ev.preventDefault();
this.props.onFinished();
}
},
render: function() {
return (
<div className="mx_IntegrationsManager">
<iframe src={ this.props.src }></iframe>
</div>
);
}
});

View File

@@ -1,866 +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.
*/
'use strict';
var React = require('react');
var q = require("q");
var sdk = require('matrix-react-sdk');
var MatrixClientPeg = require('matrix-react-sdk/lib/MatrixClientPeg');
var UserSettingsStore = require('matrix-react-sdk/lib/UserSettingsStore');
var Modal = require('matrix-react-sdk/lib/Modal');
var notifications = require('../../../notifications');
// TODO: this "view" component still has far too much application logic in it,
// which should be factored out to other files.
// TODO: this component also does a lot of direct poking into this.state, which
// is VERY NAUGHTY.
var NotificationUtils = notifications.NotificationUtils;
var VectorPushRulesDefinitions = notifications.VectorPushRulesDefinitions;
var PushRuleVectorState = notifications.PushRuleVectorState;
var ContentRules = notifications.ContentRules;
/**
* Rules that Vector used to set in order to override the actions of default rules.
* These are used to port peoples existing overrides to match the current API.
* These can be removed and forgotten once everyone has moved to the new client.
*/
var LEGACY_RULES = {
"im.vector.rule.contains_display_name": ".m.rule.contains_display_name",
"im.vector.rule.room_one_to_one": ".m.rule.room_one_to_one",
"im.vector.rule.room_message": ".m.rule.message",
"im.vector.rule.invite_for_me": ".m.rule.invite_for_me",
"im.vector.rule.call": ".m.rule.call",
"im.vector.rule.notices": ".m.rule.suppress_notices"
};
function portLegacyActions(actions) {
var decoded = NotificationUtils.decodeActions(actions);
if (decoded !== null) {
return NotificationUtils.encodeActions(decoded);
} else {
// We don't recognise one of the actions here, so we don't try to
// canonicalise them.
return actions;
}
}
module.exports = React.createClass({
displayName: 'Notififications',
phases: {
LOADING: "LOADING", // The component is loading or sending data to the hs
DISPLAY: "DISPLAY", // The component is ready and display data
ERROR: "ERROR" // There was an error
},
propTypes: {
// The array of threepids from the JS SDK (required for email notifications)
threepids: React.PropTypes.array.isRequired,
// The brand string set when creating an email pusher
brand: React.PropTypes.string,
},
getDefaultProps: function() {
return {
threepids: []
};
},
getInitialState: function() {
return {
phase: this.phases.LOADING,
masterPushRule: undefined, // The master rule ('.m.rule.master')
vectorPushRules: [], // HS default push rules displayed in Vector UI
vectorContentRules: { // Keyword push rules displayed in Vector UI
vectorState: PushRuleVectorState.ON,
rules: []
},
externalPushRules: [], // Push rules (except content rule) that have been defined outside Vector UI
externalContentRules: [] // Keyword push rules that have been defined outside Vector UI
};
},
componentWillMount: function() {
this._refreshFromServer();
},
onEnableNotificationsChange: function(event) {
var self = this;
this.setState({
phase: this.phases.LOADING
});
MatrixClientPeg.get().setPushRuleEnabled('global', self.state.masterPushRule.kind, self.state.masterPushRule.rule_id, !event.target.checked).done(function() {
self._refreshFromServer();
});
},
onEnableDesktopNotificationsChange: function(event) {
UserSettingsStore.setEnableNotifications(event.target.checked);
},
onEnableEmailNotificationsChange: function(address, event) {
var emailPusherPromise;
if (event.target.checked) {
var data = {}
data['brand'] = this.props.brand || 'Vector';
emailPusherPromise = UserSettingsStore.addEmailPusher(address, data);
} else {
var emailPusher = UserSettingsStore.getEmailPusher(this.state.pushers, address);
emailPusher.kind = null;
emailPusherPromise = MatrixClientPeg.get().setPusher(emailPusher);
}
emailPusherPromise.done(() => {
this._refreshFromServer();
}, (error) => {
var ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
Modal.createDialog(ErrorDialog, {
title: "Error saving email notification preferences",
description: "Vector was unable to save your email notification preferences.",
});
});
},
onNotifStateButtonClicked: function(event) {
// FIXME: use .bind() rather than className metadata here surely
var vectorRuleId = event.target.className.split("-")[0];
var newPushRuleVectorState = event.target.className.split("-")[1];
if ("_keywords" === vectorRuleId) {
this._setKeywordsPushRuleVectorState(newPushRuleVectorState)
}
else {
var rule = this.getRule(vectorRuleId);
if (rule) {
this._setPushRuleVectorState(rule, newPushRuleVectorState);
}
}
},
onKeywordsClicked: function(event) {
var self = this;
// Compute the keywords list to display
var keywords = [];
for (var i in this.state.vectorContentRules.rules) {
var rule = this.state.vectorContentRules.rules[i];
keywords.push(rule.pattern);
}
if (keywords.length) {
// As keeping the order of per-word push rules hs side is a bit tricky to code,
// display the keywords in alphabetical order to the user
keywords.sort();
keywords = keywords.join(", ");
}
else {
keywords = "";
}
var TextInputDialog = sdk.getComponent("dialogs.TextInputDialog");
Modal.createDialog(TextInputDialog, {
title: "Keywords",
description: "Enter keywords separated by a comma:",
value: keywords,
onFinished: function onFinished(should_leave, newValue) {
if (should_leave && newValue !== keywords) {
var newKeywords = newValue.split(',');
for (var i in newKeywords) {
newKeywords[i] = newKeywords[i].trim();
}
// Remove duplicates and empty
newKeywords = newKeywords.reduce(function(array, keyword){
if (keyword !== "" && array.indexOf(keyword) < 0) {
array.push(keyword);
}
return array;
},[]);
self._setKeywords(newKeywords);
}
}
});
},
getRule: function(vectorRuleId) {
for (var i in this.state.vectorPushRules) {
var rule = this.state.vectorPushRules[i];
if (rule.vectorRuleId === vectorRuleId) {
return rule;
}
}
},
_setPushRuleVectorState: function(rule, newPushRuleVectorState) {
if (rule && rule.vectorState !== newPushRuleVectorState) {
this.setState({
phase: this.phases.LOADING
});
var self = this;
var cli = MatrixClientPeg.get();
var deferreds = [];
var ruleDefinition = VectorPushRulesDefinitions[rule.vectorRuleId];
if (rule.rule) {
var actions = ruleDefinition.vectorStateToActions[newPushRuleVectorState];
if (!actions) {
// The new state corresponds to disabling the rule.
deferreds.push(cli.setPushRuleEnabled('global', rule.rule.kind, rule.rule.rule_id, false));
}
else {
// The new state corresponds to enabling the rule and setting specific actions
deferreds.push(this._updatePushRuleActions(rule.rule, actions, true));
}
}
q.all(deferreds).done(function() {
self._refreshFromServer();
}, function(error) {
var ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
Modal.createDialog(ErrorDialog, {
title: "Can't change settings",
description: error.toString(),
onFinished: self._refreshFromServer
});
});
}
},
_setKeywordsPushRuleVectorState: function(newPushRuleVectorState) {
// Is there really a change?
if (this.state.vectorContentRules.vectorState === newPushRuleVectorState
|| this.state.vectorContentRules.rules.length === 0) {
return;
}
var self = this;
var cli = MatrixClientPeg.get();
this.setState({
phase: this.phases.LOADING
});
// Update all rules in self.state.vectorContentRules
var deferreds = [];
for (var i in this.state.vectorContentRules.rules) {
var rule = this.state.vectorContentRules.rules[i];
var enabled, actions;
switch (newPushRuleVectorState) {
case PushRuleVectorState.ON:
if (rule.actions.length !== 1) {
actions = PushRuleVectorState.actionsFor(PushRuleVectorState.ON);
}
if (this.state.vectorContentRules.vectorState === PushRuleVectorState.OFF) {
enabled = true;
}
break;
case PushRuleVectorState.LOUD:
if (rule.actions.length !== 3) {
actions = PushRuleVectorState.actionsFor(PushRuleVectorState.LOUD);
}
if (this.state.vectorContentRules.vectorState === PushRuleVectorState.OFF) {
enabled = true;
}
break;
case PushRuleVectorState.OFF:
enabled = false;
break;
}
if (actions) {
// Note that the workaround in _updatePushRuleActions will automatically
// enable the rule
deferreds.push(this._updatePushRuleActions(rule, actions, enabled));
}
else if (enabled != undefined) {
deferreds.push(cli.setPushRuleEnabled('global', rule.kind, rule.rule_id, enabled));
}
}
q.all(deferreds).done(function(resps) {
self._refreshFromServer();
}, function(error) {
var ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
Modal.createDialog(ErrorDialog, {
title: "Can't update user notification settings",
description: error.toString(),
onFinished: self._refreshFromServer
});
});
},
_setKeywords: function(newKeywords) {
this.setState({
phase: this.phases.LOADING
});
var self = this;
var cli = MatrixClientPeg.get();
var removeDeferreds = [];
// Remove per-word push rules of keywords that are no more in the list
var vectorContentRulesPatterns = [];
for (var i in self.state.vectorContentRules.rules) {
var rule = self.state.vectorContentRules.rules[i];
vectorContentRulesPatterns.push(rule.pattern);
if (newKeywords.indexOf(rule.pattern) < 0) {
removeDeferreds.push(cli.deletePushRule('global', rule.kind, rule.rule_id));
}
}
// If the keyword is part of `externalContentRules`, remove the rule
// before recreating it in the right Vector path
for (var i in self.state.externalContentRules) {
var rule = self.state.externalContentRules[i];
if (newKeywords.indexOf(rule.pattern) >= 0) {
removeDeferreds.push(cli.deletePushRule('global', rule.kind, rule.rule_id));
}
}
var onError = function(error) {
var ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
Modal.createDialog(ErrorDialog, {
title: "Can't update keywords",
description: error.toString(),
onFinished: self._refreshFromServer
});
}
// Then, add the new ones
q.all(removeDeferreds).done(function(resps) {
var deferreds = [];
var pushRuleVectorStateKind = self.state.vectorContentRules.vectorState;
if (pushRuleVectorStateKind === PushRuleVectorState.OFF) {
// When the current global keywords rule is OFF, we need to look at
// the flavor of rules in 'vectorContentRules' to apply the same actions
// when creating the new rule.
// Thus, this new rule will join the 'vectorContentRules' set.
if (self.state.vectorContentRules.rules.length) {
pushRuleVectorStateKind = PushRuleVectorState.contentRuleVectorStateKind(self.state.vectorContentRules.rules[0]);
}
else {
// ON is default
pushRuleVectorStateKind = PushRuleVectorState.ON;
}
}
for (var i in newKeywords) {
var keyword = newKeywords[i];
if (vectorContentRulesPatterns.indexOf(keyword) < 0) {
if (self.state.vectorContentRules.vectorState !== PushRuleVectorState.OFF) {
deferreds.push(cli.addPushRule
('global', 'content', keyword, {
actions: PushRuleVectorState.actionsFor(pushRuleVectorStateKind),
pattern: keyword
}));
}
else {
deferreds.push(self._addDisabledPushRule('global', 'content', keyword, {
actions: PushRuleVectorState.actionsFor(pushRuleVectorStateKind),
pattern: keyword
}));
}
}
}
q.all(deferreds).done(function(resps) {
self._refreshFromServer();
}, onError);
}, onError);
},
// Create a push rule but disabled
_addDisabledPushRule: function(scope, kind, ruleId, body) {
var cli = MatrixClientPeg.get();
return cli.addPushRule(scope, kind, ruleId, body).then(function() {
return cli.setPushRuleEnabled(scope, kind, ruleId, false);
});
},
// Check if any legacy im.vector rules need to be ported to the new API
// for overriding the actions of default rules.
_portRulesToNewAPI: function(rulesets) {
var self = this;
var needsUpdate = [];
var cli = MatrixClientPeg.get();
for (var kind in rulesets.global) {
var ruleset = rulesets.global[kind];
for (var i = 0; i < ruleset.length; ++i) {
var rule = ruleset[i];
if (rule.rule_id in LEGACY_RULES) {
console.log("Porting legacy rule", rule);
needsUpdate.push( function(kind, rule) {
return cli.setPushRuleActions(
'global', kind, LEGACY_RULES[rule.rule_id], portLegacyActions(rule.actions)
).then( function() {
return cli.deletePushRule('global', kind, rule.rule_id);
})
}(kind, rule));
}
}
}
if (needsUpdate.length > 0) {
// If some of the rules need to be ported then wait for the porting
// to happen and then fetch the rules again.
return q.allSettled(needsUpdate).then( function() {
return cli.getPushRules();
});
} else {
// Otherwise return the rules that we already have.
return rulesets;
}
},
_refreshFromServer: function() {
var self = this;
var pushRulesPromise = MatrixClientPeg.get().getPushRules().then(self._portRulesToNewAPI).then(function(rulesets) {
//console.log("resolving pushRulesPromise");
/// XXX seriously? wtf is this?
MatrixClientPeg.get().pushRules = rulesets;
// Get homeserver default rules and triage them by categories
var rule_categories = {
// The master rule (all notifications disabling)
'.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.room_one_to_one': 'vector',
'.m.rule.message': 'vector',
'.m.rule.invite_for_me': 'vector',
//'.m.rule.member_event': 'vector',
'.m.rule.call': 'vector',
'.m.rule.suppress_notices': 'vector'
// Others go to others
};
// HS default rules
var defaultRules = {master: [], vector: {}, others: []};
for (var kind in rulesets.global) {
for (var i = 0; i < Object.keys(rulesets.global[kind]).length; ++i) {
var r = rulesets.global[kind][i];
var cat = rule_categories[r.rule_id];
r.kind = kind;
if (r.rule_id[0] === '.') {
if (cat === 'vector') {
defaultRules.vector[r.rule_id] = r;
}
else if (cat === 'master') {
defaultRules.master.push(r);
}
else {
defaultRules['others'].push(r);
}
}
}
}
// Get the master rule if any defined by the hs
if (defaultRules.master.length > 0) {
self.state.masterPushRule = defaultRules.master[0];
}
// parse the keyword rules into our state
var contentRules = ContentRules.parseContentRules(rulesets);
self.state.vectorContentRules = {
vectorState: contentRules.vectorState,
rules: contentRules.rules,
};
self.state.externalContentRules = contentRules.externalRules;
// Build the rules displayed in the Vector UI matrix table
self.state.vectorPushRules = [];
self.state.externalPushRules = [];
var vectorRuleIds = [
'.m.rule.contains_display_name',
'_keywords',
'.m.rule.room_one_to_one',
'.m.rule.message',
'.m.rule.invite_for_me',
//'im.vector.rule.member_event',
'.m.rule.call',
'.m.rule.suppress_notices'
];
for (var i in vectorRuleIds) {
var vectorRuleId = vectorRuleIds[i];
if (vectorRuleId === '_keywords') {
// keywords needs a special handling
// For Vector UI, this is a single global push rule but translated in Matrix,
// it corresponds to all content push rules (stored in self.state.vectorContentRule)
self.state.vectorPushRules.push({
"vectorRuleId": "_keywords",
"description" : (<span>Messages containing <span className="mx_UserNotifSettings_keywords" onClick={ self.onKeywordsClicked }>keywords</span></span>),
"vectorState": self.state.vectorContentRules.vectorState
});
}
else {
var ruleDefinition = VectorPushRulesDefinitions[vectorRuleId];
var rule = defaultRules.vector[vectorRuleId];
var vectorState = ruleDefinition.ruleToVectorState(rule);
//console.log("Refreshing vectorPushRules for " + vectorRuleId +", "+ ruleDefinition.description +", " + rule +", " + vectorState);
self.state.vectorPushRules.push({
"vectorRuleId": vectorRuleId,
"description" : ruleDefinition.description,
"rule": rule,
"vectorState": vectorState,
});
// if there was a rule which we couldn't parse, add it to the external list
if (rule && !vectorState) {
rule.description = ruleDefinition.description;
self.state.externalPushRules.push(rule);
}
}
}
// Build the rules not managed by Vector UI
var otherRulesDescriptions = {
'.m.rule.message': "Notify for all other messages/rooms",
'.m.rule.fallback': "Notify me for anything else"
};
for (var i in defaultRules.others) {
var rule = defaultRules.others[i];
var ruleDescription = otherRulesDescriptions[rule.rule_id];
// Show enabled default rules that was modified by the user
if (ruleDescription && rule.enabled && !rule.default) {
rule.description = ruleDescription;
self.state.externalPushRules.push(rule);
}
}
});
var pushersPromise = MatrixClientPeg.get().getPushers().then(function(resp) {
//console.log("resolving pushersPromise");
self.setState({pushers: resp.pushers});
});
q.all([pushRulesPromise, pushersPromise]).then(function() {
self.setState({
phase: self.phases.DISPLAY
});
}, function(error) {
self.setState({
phase: self.phases.ERROR
});
}).finally(() => {
// actually explicitly update our state having been deep-manipulating it
self.setState({
masterPushRule: self.state.masterPushRule,
vectorContentRules: self.state.vectorContentRules,
vectorPushRules: self.state.vectorPushRules,
externalContentRules: self.state.externalContentRules,
externalPushRules: self.state.externalPushRules,
});
}).done();
},
_updatePushRuleActions: function(rule, actions, enabled) {
var cli = MatrixClientPeg.get();
return cli.setPushRuleActions(
'global', rule.kind, rule.rule_id, actions
).then( function() {
// Then, if requested, enabled or disabled the rule
if (undefined != enabled) {
return cli.setPushRuleEnabled(
'global', rule.kind, rule.rule_id, enabled
);
}
});
},
renderNotifRulesTableRow: function(title, className, pushRuleVectorState) {
return (
<tr key={ className }>
<th>
{title}
</th>
<th>
<input className= {className + "-" + PushRuleVectorState.OFF}
type="radio"
checked={ pushRuleVectorState === PushRuleVectorState.OFF }
onChange={ this.onNotifStateButtonClicked } />
</th>
<th>
<input className= {className + "-" + PushRuleVectorState.ON}
type="radio"
checked={ pushRuleVectorState === PushRuleVectorState.ON }
onChange={ this.onNotifStateButtonClicked } />
</th>
<th>
<input className= {className + "-" + PushRuleVectorState.LOUD}
type="radio"
checked={ pushRuleVectorState === PushRuleVectorState.LOUD }
onChange={ this.onNotifStateButtonClicked } />
</th>
</tr>
);
},
renderNotifRulesTableRows: function() {
var rows = [];
for (var i in this.state.vectorPushRules) {
var rule = this.state.vectorPushRules[i];
//console.log("rendering: " + rule.description + ", " + rule.vectorRuleId + ", " + rule.vectorState);
rows.push(this.renderNotifRulesTableRow(rule.description, rule.vectorRuleId, rule.vectorState));
}
return rows;
},
emailNotificationsRow: function(address, label) {
return (<div className="mx_UserNotifSettings_tableRow">
<div className="mx_UserNotifSettings_inputCell">
<input id="enableEmailNotifications_{address}"
ref="enableEmailNotifications_{address}"
type="checkbox"
checked={ UserSettingsStore.hasEmailPusher(this.state.pushers, address) }
onChange={ this.onEnableEmailNotificationsChange.bind(this, address) }
/>
</div>
<div className="mx_UserNotifSettings_labelCell">
<label htmlFor="enableEmailNotifications_{address}">
{label}
</label>
</div>
</div>);
},
render: function() {
var self = this;
var spinner;
if (this.state.phase === this.phases.LOADING) {
var Loader = sdk.getComponent("elements.Spinner");
spinner = <Loader />;
}
if (this.state.masterPushRule) {
var masterPushRuleDiv = (
<div className="mx_UserNotifSettings_tableRow">
<div className="mx_UserNotifSettings_inputCell">
<input id="enableNotifications"
ref="enableNotifications"
type="checkbox"
checked={ !this.state.masterPushRule.enabled }
onChange={ this.onEnableNotificationsChange } />
</div>
<div className="mx_UserNotifSettings_labelCell">
<label htmlFor="enableNotifications">
Enable notifications for this account
</label>
</div>
</div>
);
}
// When enabled, the master rule inhibits all existing rules
// So do not show all notification settings
if (this.state.masterPushRule && this.state.masterPushRule.enabled) {
return (
<div>
{masterPushRuleDiv}
<div className="mx_UserSettings_notifTable">
All notifications are currently disabled for all targets.
</div>
</div>
);
}
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(
this.props.threepids[0].address,
"Enable email notifications ("+this.props.threepids[0].address+")"
);
}
// Build external push rules
var externalRules = [];
for (var i in this.state.externalPushRules) {
var rule = this.state.externalPushRules[i];
externalRules.push(<li>{ rule.description }</li>);
}
// Show keywords not displayed by the vector UI as a single external push rule
var externalKeyWords = [];
for (var i in this.state.externalContentRules) {
var rule = this.state.externalContentRules[i];
externalKeyWords.push(rule.pattern);
}
if (externalKeyWords.length) {
externalKeyWords = externalKeyWords.join(", ");
externalRules.push(<li>Notifications on the following keywords follow rules which cant be displayed here: { externalKeyWords }</li>);
}
var devicesSection;
if (this.state.pushers === undefined) {
devicesSection = <div className="error">Unable to fetch notification target list</div>
} else if (this.state.pushers.length == 0) {
devicesSection = null;
} else {
// TODO: It would be great to be able to delete pushers from here too,
// and this wouldn't be hard to add.
var rows = [];
for (var i = 0; i < this.state.pushers.length; ++i) {
rows.push(<tr key={ i }>
<td>{this.state.pushers[i].app_display_name}</td>
<td>{this.state.pushers[i].device_display_name}</td>
</tr>);
}
devicesSection = (<table className="mx_UserSettings_devicesTable">
<tbody>
{rows}
</tbody>
</table>);
}
if (devicesSection) {
devicesSection = (<div>
<h3>Notification targets</h3>
{ devicesSection }
</div>);
}
var advancedSettings;
if (externalRules.length) {
advancedSettings = (
<div>
<h3>Advanced notifications settings</h3>
There are advanced notifications which are not shown here.<br/>
You might have configured them in another client than Vector. You cannot tune them in Vector but they still apply.
<ul>
{ externalRules }
</ul>
</div>
);
}
return (
<div>
{masterPushRuleDiv}
<div className="mx_UserSettings_notifTable">
{ spinner }
<div className="mx_UserNotifSettings_tableRow">
<div className="mx_UserNotifSettings_inputCell">
<input id="enableDesktopNotifications"
ref="enableDesktopNotifications"
type="checkbox"
checked={ UserSettingsStore.getEnableNotifications() }
onChange={ this.onEnableDesktopNotificationsChange } />
</div>
<div className="mx_UserNotifSettings_labelCell">
<label htmlFor="enableDesktopNotifications">
Enable desktop notifications
</label>
</div>
</div>
<div className="mx_UserNotifSettings_tableRow">
<div className="mx_UserNotifSettings_inputCell">
<input id="enableDesktopAudioNotifications"
ref="enableDesktopAudioNotifications"
type="checkbox"
checked={ UserSettingsStore.getEnableAudioNotifications() }
onChange={ (e) => {
UserSettingsStore.setEnableAudioNotifications(e.target.checked);
this.forceUpdate();
}} />
</div>
<div className="mx_UserNotifSettings_labelCell">
<label htmlFor="enableDesktopAudioNotifications">
Enable audible notifications in web client
</label>
</div>
</div>
{ emailNotificationsRow }
<div className="mx_UserNotifSettings_pushRulesTableWrapper">
<table className="mx_UserNotifSettings_pushRulesTable">
<thead>
<tr>
<th width="55%"></th>
<th width="15%">Off</th>
<th width="15%">On</th>
<th width="15%">Highlight<br/>&amp; sound</th>
</tr>
</thead>
<tbody>
{ this.renderNotifRulesTableRows() }
</tbody>
</table>
</div>
{ advancedSettings }
{ devicesSection }
</div>
</div>
);
}
});

View File

@@ -1,5 +1,5 @@
/*
Copyright 2015, 2016 OpenMarket Ltd
Copyright 2015 OpenMarket Ltd
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
@@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
.mx_TextualEvent {
opacity: 0.5;
overflow-y: hidden;
}
'use strict';
module.exports = {
};

View File

@@ -0,0 +1,96 @@
/*
Copyright 2015 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 dis = require("matrix-react-sdk/lib/dispatcher");
var CallHandler = require("matrix-react-sdk/lib/CallHandler");
var MatrixClientPeg = require("matrix-react-sdk/lib/MatrixClientPeg");
var VectorConferenceHandler = require('../../../modules/VectorConferenceHandler');
/*
* State vars:
* this.state.call = MatrixCall|null
*
* Props:
* this.props.room = Room (JS SDK)
*
* Internal state:
* this._trackedRoom = (either from props.room or programatically set)
*/
module.exports = {
componentDidMount: function() {
this.dispatcherRef = dis.register(this.onAction);
this._trackedRoom = null;
if (this.props.room) {
this._trackedRoom = this.props.room;
this.showCall(this._trackedRoom.roomId);
}
else {
var call = CallHandler.getAnyActiveCall();
if (call) {
console.log(
"Global CallView is now tracking active call in room %s",
call.roomId
);
this._trackedRoom = MatrixClientPeg.get().getRoom(call.roomId);
this.showCall(call.roomId);
}
}
},
componentWillUnmount: function() {
dis.unregister(this.dispatcherRef);
},
onAction: function(payload) {
// don't filter out payloads for room IDs other than props.room because
// we may be interested in the conf 1:1 room
if (payload.action !== 'call_state' || !payload.room_id) {
return;
}
this.showCall(payload.room_id);
},
showCall: function(roomId) {
var call = (
CallHandler.getCallForRoom(roomId) ||
VectorConferenceHandler.getConferenceCallForRoom(roomId)
);
if (call) {
call.setLocalVideoElement(this.getVideoView().getLocalVideoElement());
call.setRemoteVideoElement(this.getVideoView().getRemoteVideoElement());
// give a separate element for audio stream playback - both for voice calls
// and for the voice stream of screen captures
call.setRemoteAudioElement(this.getVideoView().getRemoteAudioElement());
}
if (call && call.type === "video" && call.state !== 'ended') {
// if this call is a conf call, don't display local video as the
// conference will have us in it
this.getVideoView().getLocalVideoElement().style.display = (
call.confUserId ? "none" : "initial"
);
this.getVideoView().getRemoteVideoElement().style.display = "initial";
}
else {
this.getVideoView().getLocalVideoElement().style.display = "none";
this.getVideoView().getRemoteVideoElement().style.display = "none";
}
}
};

View File

@@ -0,0 +1,182 @@
/*
Copyright 2015 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 React = require("react");
var MatrixClientPeg = require("matrix-react-sdk/lib/MatrixClientPeg");
var RoomListSorter = require("matrix-react-sdk/lib/RoomListSorter");
var dis = require("matrix-react-sdk/lib/dispatcher");
var sdk = require('matrix-react-sdk');
var VectorConferenceHandler = require("../../modules/VectorConferenceHandler");
var CallHandler = require("matrix-react-sdk/lib/CallHandler");
var HIDE_CONFERENCE_CHANS = true;
module.exports = {
componentWillMount: function() {
var cli = MatrixClientPeg.get();
cli.on("Room", this.onRoom);
cli.on("Room.timeline", this.onRoomTimeline);
cli.on("Room.name", this.onRoomName);
var rooms = this.getRoomList();
this.setState({
roomList: rooms,
activityMap: {}
});
},
componentDidMount: function() {
this.dispatcherRef = dis.register(this.onAction);
},
onAction: function(payload) {
switch (payload.action) {
// listen for call state changes to prod the render method, which
// may hide the global CallView if the call it is tracking is dead
case 'call_state':
this._recheckCallElement(this.props.selectedRoom);
break;
case 'view_tooltip':
this.tooltip = payload.tooltip;
this._repositionTooltip();
if (this.tooltip) this.tooltip.style.display = 'block';
break
}
},
componentWillUnmount: function() {
dis.unregister(this.dispatcherRef);
if (MatrixClientPeg.get()) {
MatrixClientPeg.get().removeListener("Room", this.onRoom);
MatrixClientPeg.get().removeListener("Room.timeline", this.onRoomTimeline);
MatrixClientPeg.get().removeListener("Room.name", this.onRoomName);
}
},
componentWillReceiveProps: function(newProps) {
this.state.activityMap[newProps.selectedRoom] = undefined;
this._recheckCallElement(newProps.selectedRoom);
this.setState({
activityMap: this.state.activityMap
});
},
onRoom: function(room) {
this.refreshRoomList();
},
onRoomTimeline: function(ev, room, toStartOfTimeline) {
if (toStartOfTimeline) return;
var newState = {
roomList: this.getRoomList()
};
if (
room.roomId != this.props.selectedRoom &&
ev.getSender() != MatrixClientPeg.get().credentials.userId)
{
var hl = 1;
var actions = MatrixClientPeg.get().getPushActionsForEvent(ev);
if (actions && actions.tweaks && actions.tweaks.highlight) {
hl = 2;
}
// obviously this won't deep copy but this shouldn't be necessary
var amap = this.state.activityMap;
amap[room.roomId] = Math.max(amap[room.roomId] || 0, hl);
newState.activityMap = amap;
}
this.setState(newState);
},
onRoomName: function(room) {
this.refreshRoomList();
},
refreshRoomList: function() {
var rooms = this.getRoomList();
this.setState({
roomList: rooms
});
},
getRoomList: function() {
return RoomListSorter.mostRecentActivityFirst(
MatrixClientPeg.get().getRooms().filter(function(room) {
var me = room.getMember(MatrixClientPeg.get().credentials.userId);
var shouldShowRoom = (
me && (me.membership == "join" || me.membership == "invite")
);
// hiding conf rooms only ever toggles shouldShowRoom to false
if (shouldShowRoom && HIDE_CONFERENCE_CHANS) {
// we want to hide the 1:1 conf<->user room and not the group chat
var joinedMembers = room.getJoinedMembers();
if (joinedMembers.length === 2) {
var otherMember = joinedMembers.filter(function(m) {
return m.userId !== me.userId
})[0];
if (VectorConferenceHandler.isConferenceUser(otherMember)) {
// console.log("Hiding conference 1:1 room %s", room.roomId);
shouldShowRoom = false;
}
}
}
return shouldShowRoom;
})
);
},
_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
// audio/video not crap out
var activeCall = CallHandler.getAnyActiveCall();
var callForRoom = CallHandler.getCallForRoom(selectedRoomId);
var showCall = (activeCall && !callForRoom);
this.setState({
show_call_element: showCall
});
},
_repositionTooltip: function(e) {
if (this.tooltip && this.tooltip.parentElement) {
var scroll = this.getDOMNode();
this.tooltip.style.top = (scroll.parentElement.offsetTop + this.tooltip.parentElement.offsetTop - scroll.scrollTop) + "px";
}
},
makeRoomTiles: function() {
var self = this;
var RoomTile = sdk.getComponent("molecules.RoomTile");
return this.state.roomList.map(function(room) {
var selected = room.roomId == self.props.selectedRoom;
return (
<RoomTile
room={room}
key={room.roomId}
collapsed={self.props.collapsed}
selected={selected}
unread={self.state.activityMap[room.roomId] === 1}
highlight={self.state.activityMap[room.roomId] === 2}
/>
);
});
}
};

View File

@@ -0,0 +1,506 @@
/*
Copyright 2015 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.
*/
var MatrixClientPeg = require("matrix-react-sdk/lib/MatrixClientPeg");
var React = require("react");
var q = require("q");
var ContentMessages = require("matrix-react-sdk/lib//ContentMessages");
var WhoIsTyping = require("matrix-react-sdk/lib/WhoIsTyping");
var Modal = require("matrix-react-sdk/lib/Modal");
var sdk = require('matrix-react-sdk/lib/index');
var CallHandler = require('matrix-react-sdk/lib/CallHandler');
var VectorConferenceHandler = require('../../modules/VectorConferenceHandler');
var dis = require("matrix-react-sdk/lib/dispatcher");
var PAGINATE_SIZE = 20;
var INITIAL_SIZE = 20;
module.exports = {
getInitialState: function() {
return {
room: this.props.roomId ? MatrixClientPeg.get().getRoom(this.props.roomId) : null,
messageCap: INITIAL_SIZE,
editingRoomSettings: false,
uploadingRoomSettings: false,
numUnreadMessages: 0,
draggingFile: false,
}
},
componentWillMount: function() {
this.dispatcherRef = dis.register(this.onAction);
MatrixClientPeg.get().on("Room.timeline", this.onRoomTimeline);
MatrixClientPeg.get().on("Room.name", this.onRoomName);
MatrixClientPeg.get().on("RoomMember.typing", this.onRoomMemberTyping);
MatrixClientPeg.get().on("RoomState.members", this.onRoomStateMember);
this.atBottom = true;
},
componentWillUnmount: function() {
if (this.refs.messageWrapper) {
var messageWrapper = this.refs.messageWrapper.getDOMNode();
messageWrapper.removeEventListener('drop', this.onDrop);
messageWrapper.removeEventListener('dragover', this.onDragOver);
messageWrapper.removeEventListener('dragleave', this.onDragLeaveOrEnd);
messageWrapper.removeEventListener('dragend', this.onDragLeaveOrEnd);
}
dis.unregister(this.dispatcherRef);
if (MatrixClientPeg.get()) {
MatrixClientPeg.get().removeListener("Room.timeline", this.onRoomTimeline);
MatrixClientPeg.get().removeListener("Room.name", this.onRoomName);
MatrixClientPeg.get().removeListener("RoomMember.typing", this.onRoomMemberTyping);
}
},
onAction: function(payload) {
switch (payload.action) {
case 'message_send_failed':
case 'message_sent':
case 'message_resend_started':
this.setState({
room: MatrixClientPeg.get().getRoom(this.props.roomId)
});
this.forceUpdate();
break;
case 'notifier_enabled':
this.forceUpdate();
break;
case 'call_state':
if (CallHandler.getCallForRoom(this.props.roomId)) {
// Call state has changed so we may be loading video elements
// which will obscure the message log.
// scroll to bottom
var messageWrapper = this.refs.messageWrapper;
if (messageWrapper) {
messageWrapper = messageWrapper.getDOMNode();
messageWrapper.scrollTop = messageWrapper.scrollHeight;
}
}
// possibly remove the conf call notification if we're now in
// the conf
this._updateConfCallNotification();
break;
}
},
// MatrixRoom still showing the messages from the old room?
// Set the key to the room_id. Sadly you can no longer get at
// the key from inside the component, or we'd check this in code.
/*componentWillReceiveProps: function(props) {
},*/
onRoomTimeline: function(ev, room, toStartOfTimeline) {
if (!this.isMounted()) return;
// ignore anything that comes in whilst pagingating: we get one
// event for each new matrix event so this would cause a huge
// number of UI updates. Just update the UI when the paginate
// call returns.
if (this.state.paginating) return;
// no point handling anything while we're waiting for the join to finish:
// we'll only be showing a spinner.
if (this.state.joining) return;
if (room.roomId != this.props.roomId) return;
if (this.refs.messageWrapper) {
var messageWrapper = this.refs.messageWrapper.getDOMNode();
this.atBottom = (
messageWrapper.scrollHeight - messageWrapper.scrollTop <=
(messageWrapper.clientHeight + 150)
);
}
var currentUnread = this.state.numUnreadMessages;
if (!toStartOfTimeline &&
(ev.getSender() !== MatrixClientPeg.get().credentials.userId)) {
// update unread count when scrolled up
if (this.atBottom) {
currentUnread = 0;
}
else {
currentUnread += 1;
}
}
this.setState({
room: MatrixClientPeg.get().getRoom(this.props.roomId),
numUnreadMessages: currentUnread
});
if (toStartOfTimeline && !this.state.paginating) {
this.fillSpace();
}
},
onRoomName: function(room) {
if (room.roomId == this.props.roomId) {
this.setState({
room: room
});
}
},
onRoomMemberTyping: function(ev, member) {
this.forceUpdate();
},
onRoomStateMember: function(ev, state, member) {
if (member.roomId !== this.props.roomId ||
member.userId !== VectorConferenceHandler.getConferenceUserIdForRoom(member.roomId)) {
return;
}
this._updateConfCallNotification();
},
_updateConfCallNotification: function() {
var room = MatrixClientPeg.get().getRoom(this.props.roomId);
if (!room) return;
var confMember = room.getMember(
VectorConferenceHandler.getConferenceUserIdForRoom(this.props.roomId)
);
if (!confMember) {
return;
}
var confCall = VectorConferenceHandler.getConferenceCallForRoom(confMember.roomId);
// A conf call notification should be displayed if there is an ongoing
// conf call but this cilent isn't a part of it.
this.setState({
displayConfCallNotification: (
(!confCall || confCall.call_state === "ended") &&
confMember.membership === "join"
)
});
},
componentDidMount: function() {
if (this.refs.messageWrapper) {
var messageWrapper = this.refs.messageWrapper.getDOMNode();
messageWrapper.addEventListener('drop', this.onDrop);
messageWrapper.addEventListener('dragover', this.onDragOver);
messageWrapper.addEventListener('dragleave', this.onDragLeaveOrEnd);
messageWrapper.addEventListener('dragend', this.onDragLeaveOrEnd);
messageWrapper.scrollTop = messageWrapper.scrollHeight;
this.fillSpace();
}
this._updateConfCallNotification();
},
componentDidUpdate: function() {
if (!this.refs.messageWrapper) return;
var messageWrapper = this.refs.messageWrapper.getDOMNode();
if (this.state.paginating && !this.waiting_for_paginate) {
var heightGained = messageWrapper.scrollHeight - this.oldScrollHeight;
messageWrapper.scrollTop += heightGained;
this.oldScrollHeight = undefined;
if (!this.fillSpace()) {
this.setState({paginating: false});
}
} else if (this.atBottom) {
messageWrapper.scrollTop = messageWrapper.scrollHeight;
if (this.state.numUnreadMessages !== 0) {
this.setState({numUnreadMessages: 0});
}
}
},
fillSpace: function() {
if (!this.refs.messageWrapper) return;
var messageWrapper = this.refs.messageWrapper.getDOMNode();
if (messageWrapper.scrollTop < messageWrapper.clientHeight && this.state.room.oldState.paginationToken) {
this.setState({paginating: true});
this.oldScrollHeight = messageWrapper.scrollHeight;
if (this.state.messageCap < this.state.room.timeline.length) {
this.waiting_for_paginate = false;
var cap = Math.min(this.state.messageCap + PAGINATE_SIZE, this.state.room.timeline.length);
this.setState({messageCap: cap, paginating: true});
} else {
this.waiting_for_paginate = true;
var cap = this.state.messageCap + PAGINATE_SIZE;
this.setState({messageCap: cap, paginating: true});
var self = this;
MatrixClientPeg.get().scrollback(this.state.room, PAGINATE_SIZE).finally(function() {
self.waiting_for_paginate = false;
if (self.isMounted()) {
self.setState({
room: MatrixClientPeg.get().getRoom(self.props.roomId)
});
}
// wait and set paginating to false when the component updates
});
}
return true;
}
return false;
},
onJoinButtonClicked: function(ev) {
var self = this;
MatrixClientPeg.get().joinRoom(this.props.roomId).then(function() {
self.setState({
joining: false,
room: MatrixClientPeg.get().getRoom(self.props.roomId)
});
}, function(error) {
self.setState({
joining: false,
joinError: error
});
});
this.setState({
joining: true
});
},
onMessageListScroll: function(ev) {
if (this.refs.messageWrapper) {
var messageWrapper = this.refs.messageWrapper.getDOMNode();
var wasAtBottom = this.atBottom;
this.atBottom = messageWrapper.scrollHeight - messageWrapper.scrollTop <= messageWrapper.clientHeight;
if (this.atBottom && !wasAtBottom) {
this.forceUpdate(); // remove unread msg count
}
}
if (!this.state.paginating) this.fillSpace();
},
onDragOver: function(ev) {
ev.stopPropagation();
ev.preventDefault();
ev.dataTransfer.dropEffect = 'none';
var items = ev.dataTransfer.items;
if (items.length == 1) {
if (items[0].kind == 'file') {
this.setState({ draggingFile : true });
ev.dataTransfer.dropEffect = 'copy';
}
}
},
onDrop: function(ev) {
ev.stopPropagation();
ev.preventDefault();
this.setState({ draggingFile : false });
var files = ev.dataTransfer.files;
if (files.length == 1) {
this.uploadFile(files[0]);
}
},
onDragLeaveOrEnd: function(ev) {
ev.stopPropagation();
ev.preventDefault();
this.setState({ draggingFile : false });
},
uploadFile: function(file) {
this.setState({
upload: {
fileName: file.name,
uploadedBytes: 0,
totalBytes: file.size
}
});
var self = this;
ContentMessages.sendContentToRoom(
file, this.props.roomId, MatrixClientPeg.get()
).progress(function(ev) {
//console.log("Upload: "+ev.loaded+" / "+ev.total);
self.setState({
upload: {
fileName: file.name,
uploadedBytes: ev.loaded,
totalBytes: ev.total
}
});
}).finally(function() {
self.setState({
upload: undefined
});
}).done(undefined, function() {
// display error message
});
},
getWhoIsTypingString: function() {
return WhoIsTyping.whoIsTypingString(this.state.room);
},
getEventTiles: function() {
var tileTypes = {
'm.room.message': sdk.getComponent('molecules.MessageTile'),
'm.room.member' : sdk.getComponent('molecules.EventAsTextTile'),
'm.call.invite' : sdk.getComponent('molecules.EventAsTextTile'),
'm.call.answer' : sdk.getComponent('molecules.EventAsTextTile'),
'm.call.hangup' : sdk.getComponent('molecules.EventAsTextTile'),
'm.room.topic' : sdk.getComponent('molecules.EventAsTextTile'),
};
var DateSeparator = sdk.getComponent('molecules.DateSeparator');
var ret = [];
var count = 0;
for (var i = this.state.room.timeline.length-1; i >= 0 && count < this.state.messageCap; --i) {
var mxEv = this.state.room.timeline[i];
var TileType = tileTypes[mxEv.getType()];
var continuation = false;
var last = false;
var dateSeparator = null;
if (i == this.state.room.timeline.length - 1) {
last = true;
}
if (i > 0 && count < this.state.messageCap - 1) {
if (this.state.room.timeline[i].sender &&
this.state.room.timeline[i - 1].sender &&
(this.state.room.timeline[i].sender.userId ===
this.state.room.timeline[i - 1].sender.userId) &&
(this.state.room.timeline[i].getType() ==
this.state.room.timeline[i - 1].getType())
)
{
continuation = true;
}
var ts0 = this.state.room.timeline[i - 1].getTs();
var ts1 = this.state.room.timeline[i].getTs();
if (new Date(ts0).toDateString() !== new Date(ts1).toDateString()) {
dateSeparator = <DateSeparator key={ts1} ts={ts1}/>;
continuation = false;
}
}
if (i === 1) { // n.b. 1, not 0, as the 0th event is an m.room.create and so doesn't show on the timeline
var ts1 = this.state.room.timeline[i].getTs();
dateSeparator = <DateSeparator key={ts1} ts={ts1}/>;
continuation = false;
}
if (!TileType) continue;
ret.unshift(
<li key={mxEv.getId()}><TileType mxEvent={mxEv} continuation={continuation} last={last}/></li>
);
if (dateSeparator) {
ret.unshift(dateSeparator);
}
++count;
}
return ret;
},
uploadNewState: function(new_name, new_topic, new_join_rule, new_history_visibility, new_power_levels) {
var old_name = this.state.room.name;
var old_topic = this.state.room.currentState.getStateEvents('m.room.topic', '');
if (old_topic) {
old_topic = old_topic.getContent().topic;
} else {
old_topic = "";
}
var old_join_rule = this.state.room.currentState.getStateEvents('m.room.join_rules', '');
if (old_join_rule) {
old_join_rule = old_join_rule.getContent().join_rule;
} else {
old_join_rule = "invite";
}
var old_history_visibility = this.state.room.currentState.getStateEvents('m.room.history_visibility', '');
if (old_history_visibility) {
old_history_visibility = old_history_visibility.getContent().history_visibility;
} else {
old_history_visibility = "shared";
}
var deferreds = [];
if (old_name != new_name && new_name != undefined && new_name) {
deferreds.push(
MatrixClientPeg.get().setRoomName(this.state.room.roomId, new_name)
);
}
if (old_topic != new_topic && new_topic != undefined) {
deferreds.push(
MatrixClientPeg.get().setRoomTopic(this.state.room.roomId, new_topic)
);
}
if (old_join_rule != new_join_rule && new_join_rule != undefined) {
deferreds.push(
MatrixClientPeg.get().sendStateEvent(
this.state.room.roomId, "m.room.join_rules", {
join_rule: new_join_rule,
}, ""
)
);
}
if (old_history_visibility != new_history_visibility && new_history_visibility != undefined) {
deferreds.push(
MatrixClientPeg.get().sendStateEvent(
this.state.room.roomId, "m.room.history_visibility", {
history_visibility: new_history_visibility,
}, ""
)
);
}
if (new_power_levels) {
deferreds.push(
MatrixClientPeg.get().sendStateEvent(
this.state.room.roomId, "m.room.power_levels", new_power_levels, ""
)
);
}
if (deferreds.length) {
var self = this;
q.all(deferreds).fail(function(err) {
var ErrorDialog = sdk.getComponent("organisms.ErrorDialog");
Modal.createDialog(ErrorDialog, {
title: "Failed to set state",
description: err.toString()
});
}).finally(function() {
self.setState({
uploadingRoomSettings: false,
});
});
} else {
this.setState({
editingRoomSettings: false,
uploadingRoomSettings: false,
});
}
}
};

View File

@@ -0,0 +1,58 @@
/*
Copyright 2015 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 extend = require('matrix-react-sdk/lib/extend');
var MatrixClientPeg = require('matrix-react-sdk/lib/MatrixClientPeg');
var BaseRegisterController = require('matrix-react-sdk/lib/controllers/templates/Register.js');
var RegisterController = {};
extend(RegisterController, BaseRegisterController);
RegisterController.onRegistered = function(user_id, access_token) {
MatrixClientPeg.replaceUsingAccessToken(
this.state.hs_url, this.state.is_url, user_id, access_token
);
this.setState({
step: 'profile',
busy: true
});
var self = this;
var cli = MatrixClientPeg.get();
cli.getProfileInfo(cli.credentials.userId).done(function(result) {
self.setState({
avatarUrl: result.avatar_url,
busy: false
});
},
function(err) {
console.err(err);
self.setState({
busy: false
});
});
};
RegisterController.onAccountReady = function() {
if (this.props.onLoggedIn) {
this.props.onLoggedIn();
}
};
module.exports = RegisterController;

View File

@@ -1,5 +1,5 @@
/*
Copyright 2015, 2016 OpenMarket Ltd
Copyright 2015 OpenMarket Ltd
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
@@ -44,7 +44,6 @@ ConferenceCall.prototype.setup = function() {
// looking for a 1:1 room with this conf user ID!)
var call = Matrix.createNewMatrixCall(self.client, room.roomId);
call.confUserId = self.confUserId;
call.groupRoomId = self.groupRoomId;
return call;
});
};
@@ -86,15 +85,15 @@ ConferenceCall.prototype._getConferenceUserRoom = function() {
};
/**
* Check if this user ID is in fact a conference bot.
* @param {string} userId The user ID to check.
* Check if this room member is in fact a conference bot.
* @param {RoomMember} The room member to check
* @return {boolean} True if it is a conference bot.
*/
module.exports.isConferenceUser = function(userId) {
if (userId.indexOf("@" + USER_PREFIX) !== 0) {
module.exports.isConferenceUser = function(roomMember) {
if (roomMember.userId.indexOf("@" + USER_PREFIX) !== 0) {
return false;
}
var base64part = userId.split(":")[0].substring(1 + USER_PREFIX.length);
var base64part = roomMember.userId.split(":")[0].substring(1 + USER_PREFIX.length);
if (base64part) {
var decoded = new Buffer(base64part, "base64").toString();
// ! $STUFF : $STUFF

View File

@@ -1,125 +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.
*/
'use strict';
var PushRuleVectorState = require('./PushRuleVectorState');
module.exports = {
/**
* Extract the keyword rules from a list of rules, and parse them
* into a form which is useful for Vector's UI.
*
* Returns an object containing:
* rules: the primary list of keyword rules
* vectorState: a PushRuleVectorState indicating whether those rules are
* OFF/ON/LOUD
* externalRules: a list of other keyword rules, with states other than
* vectorState
*/
parseContentRules: function(rulesets) {
// first categorise the keyword rules in terms of their actions
var contentRules = this._categoriseContentRules(rulesets);
// Decide which content rules to display in Vector UI.
// Vector displays a single global rule for a list of keywords
// whereas Matrix has a push rule per keyword.
// Vector can set the unique rule in ON, LOUD or OFF state.
// Matrix has enabled/disabled plus a combination of (highlight, sound) tweaks.
// The code below determines which set of user's content push rules can be
// displayed by the vector UI.
// Push rules that does not fit, ie defined by another Matrix client, ends
// in externalRules.
// There is priority in the determination of which set will be the displayed one.
// The set with rules that have LOUD tweaks is the first choice. Then, the ones
// with ON tweaks (no tweaks).
if (contentRules.loud.length) {
return {
vectorState: PushRuleVectorState.LOUD,
rules: contentRules.loud,
externalRules: [].concat(contentRules.loud_but_disabled, contentRules.on, contentRules.on_but_disabled, contentRules.other),
};
}
else if (contentRules.loud_but_disabled.length) {
return {
vectorState: PushRuleVectorState.OFF,
rules: contentRules.loud_but_disabled,
externalRules: [].concat(contentRules.on, contentRules.on_but_disabled, contentRules.other),
};
}
else if (contentRules.on.length) {
return {
vectorState: PushRuleVectorState.ON,
rules: contentRules.on,
externalRules: [].concat(contentRules.on_but_disabled, contentRules.other),
};
}
else if (contentRules.on_but_disabled.length) {
return {
vectorState: PushRuleVectorState.OFF,
rules: contentRules.on_but_disabled,
externalRules: contentRules.other,
}
} else {
return {
vectorState: PushRuleVectorState.ON,
rules: [],
externalRules: contentRules.other,
}
}
},
_categoriseContentRules: function(rulesets) {
var contentRules = {on: [], on_but_disabled:[], loud: [], loud_but_disabled: [], other: []};
for (var kind in rulesets.global) {
for (var i = 0; i < Object.keys(rulesets.global[kind]).length; ++i) {
var r = rulesets.global[kind][i];
// check it's not a default rule
if (r.rule_id[0] === '.' || kind !== 'content') {
continue;
}
r.kind = kind; // is this needed? not sure
switch (PushRuleVectorState.contentRuleVectorStateKind(r)) {
case PushRuleVectorState.ON:
if (r.enabled) {
contentRules.on.push(r);
}
else {
contentRules.on_but_disabled.push(r);
}
break;
case PushRuleVectorState.LOUD:
if (r.enabled) {
contentRules.loud.push(r);
}
else {
contentRules.loud_but_disabled.push(r);
}
break;
default:
contentRules.other.push(r);
break;
}
}
}
return contentRules;
},
};

View File

@@ -1,89 +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.
*/
'use strict';
module.exports = {
// Encodes a dictionary of {
// "notify": true/false,
// "sound": string or undefined,
// "highlight: true/false,
// }
// to a list of push actions.
encodeActions: function(action) {
var notify = action.notify;
var sound = action.sound;
var highlight = action.highlight;
if (notify) {
var actions = ["notify"];
if (sound) {
actions.push({"set_tweak": "sound", "value": sound});
}
if (highlight) {
actions.push({"set_tweak": "highlight"});
} else {
actions.push({"set_tweak": "highlight", "value": false});
}
return actions;
} else {
return ["dont_notify"];
}
},
// Decode a list of actions to a dictionary of {
// "notify": true/false,
// "sound": string or undefined,
// "highlight: true/false,
// }
// If the actions couldn't be decoded then returns null.
decodeActions: function(actions) {
var notify = false;
var sound = null;
var highlight = false;
for (var i = 0; i < actions.length; ++i) {
var action = actions[i];
if (action === "notify") {
notify = true;
} else if (action === "dont_notify") {
notify = false;
} else if (typeof action === 'object') {
if (action.set_tweak === "sound") {
sound = action.value
} else if (action.set_tweak === "highlight") {
highlight = action.value;
} else {
// We don't understand this kind of tweak, so give up.
return null;
}
} else {
// We don't understand this kind of action, so give up.
return null;
}
}
if (highlight === undefined) {
// If a highlight tweak is missing a value then it defaults to true.
highlight = true;
}
var result = {notify: notify, highlight: highlight};
if (sound !== null) {
result.sound = sound;
}
return result;
},
};

View File

@@ -1,94 +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.
*/
'use strict';
var StandardActions = require('./StandardActions');
var NotificationUtils = require('./NotificationUtils');
var states = {
/** The push rule is disabled */
OFF: "off",
/** The user will receive push notification for this rule */
ON: "on",
/** The user will receive push notification for this rule with sound and
highlight if this is legitimate */
LOUD: "loud",
};
module.exports = {
/**
* Enum for state of a push rule as defined by the Vector UI.
* @readonly
* @enum {string}
*/
states: states,
/**
* Convert a PushRuleVectorState to a list of actions
*
* @return [object] list of push-rule actions
*/
actionsFor: function(pushRuleVectorState) {
if (pushRuleVectorState === this.ON) {
return StandardActions.ACTION_NOTIFY;
}
else if (pushRuleVectorState === this.LOUD) {
return StandardActions.ACTION_HIGHLIGHT_DEFAULT_SOUND;
}
},
/**
* Convert a pushrule's actions to a PushRuleVectorState.
*
* Determines whether a content rule is in the PushRuleVectorState.ON
* category or in PushRuleVectorState.LOUD, regardless of its enabled
* state. Returns null if it does not match these categories.
*/
contentRuleVectorStateKind: function(rule) {
var decoded = NotificationUtils.decodeActions(rule.actions);
if (!decoded) {
return null;
}
// Count tweaks to determine if it is a ON or LOUD rule
var tweaks = 0;
if (decoded.sound) {
tweaks++;
}
if (decoded.highlight) {
tweaks++;
}
var stateKind = null;
switch (tweaks) {
case 0:
stateKind = this.ON;
break;
case 2:
stateKind = this.LOUD;
break;
}
return stateKind;
},
};
for (var k in states) {
module.exports[k] = states[k];
};

View File

@@ -1,30 +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.
*/
'use strict';
var NotificationUtils = require('./NotificationUtils');
var encodeActions = NotificationUtils.encodeActions;
module.exports = {
ACTION_NOTIFY: encodeActions({notify: true}),
ACTION_NOTIFY_DEFAULT_SOUND: encodeActions({notify: true, sound: "default"}),
ACTION_NOTIFY_RING_SOUND: encodeActions({notify: true, sound: "ring"}),
ACTION_HIGHLIGHT_DEFAULT_SOUND: encodeActions({notify: true, sound: "default", highlight: true}),
ACTION_DONT_NOTIFY: encodeActions({notify: false}),
ACTION_DISABLED: null,
};

View File

@@ -1,134 +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.
*/
'use strict';
var StandardActions = require('./StandardActions');
var PushRuleVectorState = require('./PushRuleVectorState');
class VectorPushRuleDefinition {
constructor(opts) {
this.kind = opts.kind;
this.description = opts.description;
this.vectorStateToActions = opts.vectorStateToActions;
}
// Translate the rule actions and its enabled value into vector state
ruleToVectorState(rule) {
var enabled = false;
var actions = null;
if (rule) {
enabled = rule.enabled;
actions = rule.actions;
}
for (var stateKey in PushRuleVectorState.states) {
var state = PushRuleVectorState.states[stateKey];
var vectorStateToActions = this.vectorStateToActions[state];
if (!vectorStateToActions) {
// No defined actions means that this vector state expects a disabled (or absent) rule
if (!enabled) {
return state;
}
} else {
// The actions must match to the ones expected by vector state
if (enabled && JSON.stringify(rule.actions) === JSON.stringify(vectorStateToActions)) {
return state;
}
}
}
console.error("Cannot translate rule actions into Vector rule state. Rule: " +
JSON.stringify(rule));
return undefined;
}
};
/**
* The descriptions of rules managed by the Vector UI.
*/
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 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 just sent to the user in a 1:1 room
".m.rule.room_one_to_one": new VectorPushRuleDefinition({
kind: "underride",
description: "Messages in one-to-one chats",
vectorStateToActions: {
on: StandardActions.ACTION_NOTIFY,
loud: StandardActions.ACTION_NOTIFY_DEFAULT_SOUND,
off: StandardActions.ACTION_DONT_NOTIFY
}
}),
// Messages just sent to a group chat room
// 1:1 room messages are catched by the .m.rule.room_one_to_one rule if any defined
// By opposition, all other room messages are from group chat rooms.
".m.rule.message": new VectorPushRuleDefinition({
kind: "underride",
description: "Messages in group chats",
vectorStateToActions: {
on: StandardActions.ACTION_NOTIFY,
loud: StandardActions.ACTION_NOTIFY_DEFAULT_SOUND,
off: StandardActions.ACTION_DONT_NOTIFY
}
}),
// Invitation for the user
".m.rule.invite_for_me": new VectorPushRuleDefinition({
kind: "underride",
description: "When I'm invited to a room",
vectorStateToActions: {
on: StandardActions.ACTION_NOTIFY,
loud: StandardActions.ACTION_NOTIFY_DEFAULT_SOUND,
off: StandardActions.ACTION_DISABLED
}
}),
// Incoming call
".m.rule.call": new VectorPushRuleDefinition({
kind: "underride",
description: "Call invitation",
vectorStateToActions: {
on: StandardActions.ACTION_NOTIFY,
loud: StandardActions.ACTION_NOTIFY_RING_SOUND,
off: StandardActions.ACTION_DISABLED
}
}),
// Notifications from bots
".m.rule.suppress_notices": new VectorPushRuleDefinition({
kind: "override",
description: "Messages sent by bot",
vectorStateToActions: {
// .m.rule.suppress_notices is a "negative" rule, we have to invert its enabled value for vector UI
on: StandardActions.ACTION_DISABLED,
loud: StandardActions.ACTION_NOTIFY_DEFAULT_SOUND,
off: StandardActions.ACTION_DONT_NOTIFY,
}
}),
};

View File

@@ -0,0 +1,22 @@
/*
Copyright 2015 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.
*/
.mx_MemberAvatar {
z-index: 20;
border-radius: 20px;
background-color: #dbdbdb;
}

View File

@@ -1,5 +1,5 @@
/*
Copyright 2015, 2016 OpenMarket Ltd
Copyright 2015 OpenMarket 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

@@ -1,5 +1,5 @@
/*
Copyright 2015, 2016 OpenMarket Ltd
Copyright 2015 OpenMarket Ltd
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
@@ -22,18 +22,11 @@ html {
}
body {
/* Open Sans lacks combining diacritics, so these will fall through
to the next font. Helevetica's diacritics however do not combine
nicely with Open Sans (on OSX, at least) and result in a huge
horizontal mess. Arial empirically gets it right, hence prioritising
Arial here. */
font-family: 'Open Sans', Arial, Helvetica, Sans-Serif;
font-size: 15px;
font-family: 'Lato', Helvetica, Arial, Sans-Serif;
font-size: 16px;
color: #454545;
border: 0px;
margin: 0px;
/* This should render the fonts the same accross browsers */
-webkit-font-smoothing: subpixel-antialiased;
}
div.error {
@@ -41,9 +34,9 @@ div.error {
}
h2 {
color: #454545;
color: #80cef4;
font-weight: 400;
font-size: 18px;
font-size: 20px;
margin-top: 16px;
margin-bottom: 16px;
}
@@ -51,67 +44,55 @@ h2 {
a:hover,
a:link,
a:visited {
color: #76cfa6;
color: #80CEF4;
}
input[type=text].error, input[type=password].error {
border: 1px solid red;
.mx_ContextualMenu_background {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
opacity: 1.0;
z-index: 2000;
}
input[type=text]:focus, textarea:focus {
border: 1px solid #76CFA6;
outline: none;
box-shadow: none;
.mx_ContextualMenu {
border: 1px solid #a9dbf4;
border-radius: 8px;
background-color: #fff;
color: #747474;
position: fixed;
z-index: 2001;
padding: 6px;
}
/* Prevent ugly dotted highlight around selected elements in Firefox */
::-moz-focus-inner {
border: 0;
}
/* applied to side-panels and messagepanel when in RoomSettings */
.mx_fadable {
opacity: 1;
-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.
Stop the scrollbar view from pushing out the container's overall sizing, which causes
flexbox to adapt to the new size and cause the view to keep growing.
*/
.gm-scrollbar-container .gm-scroll-view {
.mx_ContextualMenu_chevron {
padding: 12px;
position: absolute;
right: -21px;
top: 0px;
}
/* Expand thumbs on hoverover */
.gm-scrollbar {
border-radius: 5px ! important;
.mx_ContextualMenu_field {
padding: 3px 6px 3px 6px;
cursor: pointer;
}
.gm-scrollbar.-vertical {
width: 6px;
transition: width 120ms ease-out ! important;
}
.gm-scrollbar.-vertical:hover,
.gm-scrollbar.-vertical:active {
width: 8px;
transition: width 120ms ease-out ! important;
}
.gm-scrollbar.-horizontal {
height: 6px;
transition: height 120ms ease-out ! important;
}
.gm-scrollbar.-horizontal:hover,
.gm-scrollbar.-horizontal:active {
height: 8px;
transition: height 120ms ease-out ! important;
.mx_Dialog_background {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-color: #000;
opacity: 0.2;
z-index: 2000;
}
.mx_Dialog_wrapper {
position: fixed;
z-index: 4000;
top: 0;
left: 0;
width: 100%;
@@ -128,152 +109,52 @@ input[type=text]:focus, textarea:focus {
justify-content: center;
}
/* Spinner Dialog overide */
.mx_Dialog_wrapper.mx_Dialog_spinner .mx_Dialog {
width: auto;
border-radius: 8px;
padding-left: 0px;
box-shadow: none;
}
/* View Source Dialog overide */
.mx_Dialog_wrapper.mx_Dialog_viewsource .mx_Dialog {
padding-left: 10px;
padding-right: 10px;
}
.mx_Dialog {
background-color: #fff;
color: #747474;
z-index: 4010;
text-align: center;
z-index: 2010;
font-weight: 300;
font-size: 15px;
font-size: 16px;
position: relative;
padding-left: 58px;
width: 60%;
max-width: 704px;
box-shadow: 0 1px 0 0 rgba(0, 0, 0, 0.2);
border-radius: 8px;
max-width: 75%;
}
.mx_Dialog_background {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-color: #e9e9e9;
opacity: 0.8;
}
.mx_Dialog_lightbox .mx_Dialog_background {
opacity: 0.85;
background-color: #000;
}
.mx_Dialog_lightbox .mx_Dialog {
border-radius: 0px;
background-color: transparent;
width: 100%;
height: 100%;
max-width: 100%;
max-height: 100%;
pointer-events: none;
.mx_ImageView {
margin: 6px;
/* hack: flexbox bug? */
margin-bottom: 4px;
}
.mx_Dialog_content {
margin: 24px 58px 68px 0;
font-size: 14px;
color: #4a4a4a;
word-wrap: break-word;
margin: 24px;
}
.mx_Dialog_buttons {
padding-bottom: 36px;
padding-bottom: 24px;
}
.mx_Dialog button, .mx_Dialog input[type="submit"] {
.mx_Dialog button {
border: 0px;
height: 36px;
border-radius: 40px;
border: solid 1px #76cfa6;
border-radius: 36px;
font-weight: 400;
font-size: 15px;
margin-left: 0px;
font-size: 16px;
color: #fff;
background-color: #80cef4;
margin-left: 8px;
margin-right: 8px;
padding-left: 1.5em;
padding-right: 1.5em;
outline: none;
color: #76cfa6;
background-color: #fff;
}
.mx_Dialog button.mx_Dialog_primary, .mx_Dialog input[type="submit"].mx_Dialog_primary {
color: #fff;
background-color: #76cfa6;
}
.mx_Dialog button.danger, .mx_Dialog input[type="submit"].danger {
background-color: #ff0064;
border: solid 1px #ff0064;
}
.mx_Dialog button:disabled, .mx_Dialog input[type="submit"]:disabled {
background-color: #777777;
border: solid 1px #777777;
opacity: 0.7;
}
.mx_Dialog_title {
min-height: 16px;
padding-top: 40px;
font-weight: bold;
font-size: 22px;
line-height: 1.4;
color: #454545;
}
.mx_Dialog_title.danger {
color: #ff0064;
}
.mx_TextInputDialog_label {
text-align: left;
padding-bottom: 12px;
}
.mx_TextInputDialog_input {
font-size: 15px;
border-radius: 3px;
border: 1px solid #f0f0f0;
padding: 9px;
color: #454545;
background-color: #fff;
}
.mx_emojione {
height: 1em;
vertical-align: middle;
}
::-moz-selection {
background-color: #76CFA6;
color: white;
}
::selection {
background-color: #76CFA6;
color: white;
}
/** green button with rounded corners */
.mx_textButton {
color: #fff;
background-color: #76cfa6;
border-radius: 17px;
text-align: center;
padding-left: 1em;
padding-right: 1em;
cursor: pointer;
display: inline;
}
.mx_ErrorDialogTitle,
.mx_QuestionDialogTitle {
min-height: 16px;
padding: 12px;
border-bottom: 1px solid #a9dbf4;
font-weight: bold;
font-size: 20px;
line-height: 1.4;
}

View File

@@ -1,3 +1,7 @@
.mx_RoomDropTarget,
.mx_RoomList_favourites_label,
.mx_RoomList_archive_label,
.mx_RoomHeader_search,
.mx_RoomSettings_encrypt,
.mx_CreateRoom_encrypt,
.mx_RightPanel_filebutton

View File

@@ -1,106 +0,0 @@
/*
Copyright 2015, 2016 OpenMarket Ltd
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
.mx_ContextualMenu_wrapper {
position: fixed;
z-index: 2000;
}
.mx_ContextualMenu_background {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
opacity: 1.0;
z-index: 2000;
}
.mx_ContextualMenu {
border: solid 1px rgba(187, 187, 187, 0.5);
border-radius: 4px;
background-color: #f6f6f6;
color: #4a4a4a;
position: absolute;
padding: 6px;
font-size: 14px;
z-index: 2001;
}
.mx_ContextualMenu.mx_ContextualMenu_right {
right: 8px;
}
.mx_ContextualMenu_chevron_right {
position: absolute;
right: -8px;
top: 0px;
width: 0;
height: 0;
border-top: 8px solid transparent;
border-left: 8px solid rgba(187, 187, 187, 0.5);
border-bottom: 8px solid transparent;
}
.mx_ContextualMenu_chevron_right:after {
content:'';
width: 0;
height: 0;
border-top: 7px solid transparent;
border-left: 7px solid #f6f6f6;
border-bottom: 7px solid transparent;
position:absolute;
top: -7px;
right: 1px;
}
.mx_ContextualMenu.mx_ContextualMenu_left {
left: 8px;
}
.mx_ContextualMenu_chevron_left {
position: absolute;
left: -8px;
top: 0px;
width: 0;
height: 0;
border-top: 8px solid transparent;
border-right: 8px solid rgba(187, 187, 187, 0.5);
border-bottom: 8px solid transparent;
}
.mx_ContextualMenu_chevron_left:after{
content:'';
width: 0;
height: 0;
border-top: 7px solid transparent;
border-right: 7px solid #f6f6f6;
border-bottom: 7px solid transparent;
position:absolute;
top: -7px;
left: 1px;
}
.mx_ContextualMenu_field {
padding: 3px 6px 3px 6px;
cursor: pointer;
white-space: nowrap;
}
.mx_ContextualMenu_spinner {
display: block;
margin: 0 auto;
}

View File

@@ -1,138 +0,0 @@
/*
Copyright 2015, 2016 OpenMarket Ltd
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
.mx_RoomStatusBar {
margin-top: 15px;
margin-left: 65px;
min-height: 34px;
}
/* position the indicator in the same place horizontally as .mx_EventTile_avatar. */
.mx_RoomStatusBar_indicator {
padding-left: 18px;
padding-right: 12px;
margin-left: -73px;
float: left;
width: 24px;
text-align: center;
}
.mx_RoomStatusBar_placeholderIndicator span {
color: #4a4a4a;
opacity: 0.5;
position: relative;
top: -4px;
/*
animation-duration: 1s;
animation-name: bounce;
animation-direction: alternate;
animation-iteration-count: infinite;
*/
}
.mx_RoomStatusBar_placeholderIndicator span:nth-child(1) {
animation-delay: 0.3s;
}
.mx_RoomStatusBar_placeholderIndicator span:nth-child(2) {
animation-delay: 0.6s;
}
.mx_RoomStatusBar_placeholderIndicator span:nth-child(3) {
animation-delay: 0.9s;
}
@keyframes bounce {
from {
opacity: 0.5;
top: 0;
}
to {
opacity: 0.2;
top: -3px;
}
}
.mx_RoomStatusBar_scrollDownIndicator {
cursor: pointer;
}
.mx_RoomStatusBar_unreadMessagesBar {
color: #ff0064;
cursor: pointer;
}
.mx_RoomStatusBar_connectionLostBar {
margin-top: 19px;
height: 58px;
}
.mx_RoomStatusBar_connectionLostBar img {
padding-left: 10px;
padding-right: 22px;
vertical-align: middle;
float: left;
}
.mx_RoomStatusBar_connectionLostBar_title {
color: #ff0064;
}
.mx_RoomStatusBar_connectionLostBar_desc {
color: #454545;
font-size: 13px;
opacity: 0.5;
}
.mx_RoomStatusBar_resend_link {
color: #454545 ! important;
text-decoration: underline ! important;
cursor: pointer;
}
.mx_RoomStatusBar_tabCompleteBar {
color: #4a4a4a;
}
.mx_RoomStatusBar_typingBar {
color: #4a4a4a;
opacity: 0.5;
overflow-y: hidden;
display: block;
}
.mx_RoomStatusBar_tabCompleteWrapper {
display: flex;
display: -webkit-flex;
height: 26px;
}
.mx_RoomStatusBar_tabCompleteWrapper .mx_TabCompleteBar {
flex: 1 1 auto;
-webkit-flex: 1 1 auto;
}
.mx_RoomStatusBar_tabCompleteEol {
flex: 0 0 auto;
-webkit-flex: 0 0 auto;
color: #76CFA6;
}
.mx_RoomStatusBar_tabCompleteEol object {
vertical-align: middle;
margin-right: 8px;
margin-top: -2px;
}

View File

@@ -1,70 +0,0 @@
/*
Copyright 2015, 2016 OpenMarket Ltd
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
.mx_SearchBox {
height: 24px;
margin-left: 16px;
margin-right: 16px;
padding-top: 24px;
padding-bottom: 22px;
border-bottom: 1px solid rgba(0, 0, 0, 0.1);
display: flex;
display: -webkit-flex;
}
.mx_SearchBox_searchButton {
margin-right: 10px;
margin-top: 5px;
pointer-events: none;
}
.mx_SearchBox_closeButton {
cursor: pointer;
margin-top: -5px;
}
.mx_SearchBox_search {
flex: 1 1 auto;
-webkit-flex: 1 1 auto;
width: 0px;
font-family: 'Open Sans', Arial, Helvetica, Sans-Serif;
font-size: 12px;
margin-top: -2px;
height: 24px;
border: 0px ! important;
/* border-bottom: 1px solid rgba(0, 0, 0, 0.1) ! important; */
background-color: transparent;
border: 0px;
}
.mx_SearchBox_minimise,
.mx_SearchBox_maximise {
margin-top: 3px;
cursor: pointer;
}
.mx_SearchBox_minimise {
margin-left: 10px;
}
.mx_SearchBox_maximise {
margin-left: 9px;
}
.mx_SearchBox object {
pointer-events: none;
}

View File

@@ -1,61 +0,0 @@
/*
Copyright 2015, 2016 OpenMarket Ltd
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
.mx_UploadBar {
position: relative;
}
.mx_UploadBar_uploadProgressOuter {
height: 5px;
margin-left: 63px;
margin-top: -1px;
padding-bottom: 5px;
}
.mx_UploadBar_uploadProgressInner {
background-color: #76cfa6;
height: 5px;
}
.mx_UploadBar_uploadFilename {
margin-top: 5px;
margin-left: 65px;
opacity: 0.5;
color: #4a4a4a;
}
.mx_UploadBar_uploadIcon {
float: left;
margin-top: 5px;
margin-left: 14px;
}
.mx_UploadBar_uploadCancel {
float: right;
margin-top: 5px;
margin-right: 10px;
position: relative;
opacity: 0.6;
cursor: pointer;
z-index: 1;
}
.mx_UploadBar_uploadBytes {
float: right;
margin-top: 5px;
margin-right: 30px;
color: #76cfa6;
}

View File

@@ -1,202 +0,0 @@
/*
Copyright 2015, 2016 OpenMarket Ltd
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
.mx_UserSettings {
max-width: 960px;
width: 100%;
margin-left: auto;
margin-right: auto;
display: -webkit-box;
display: -moz-box;
display: -ms-flexbox;
display: -webkit-flex;
display: flex;
flex-direction: column;
-webkit-flex-direction: column;
}
.mx_UserSettings .mx_RoomHeader {
-webkit-box-ordinal-group: 1;
-moz-box-ordinal-group: 1;
-ms-flex-order: 1;
-webkit-order: 1;
order: 1;
-webkit-flex: 0 0 70px;
flex: 0 0 70px;
}
.mx_UserSettings_body {
-webkit-box-ordinal-group: 2;
-moz-box-ordinal-group: 2;
-ms-flex-order: 2;
-webkit-order: 2;
order: 2;
-webkit-flex: 1 1 0;
flex: 1 1 0;
margin-top: -20px;
overflow-y: auto;
}
.mx_UserSettings h3 {
clear: both;
margin-left: 63px;
text-transform: uppercase;
color: #3d3b39;
font-weight: 600;
font-size: 13px;
margin-top: 26px;
margin-bottom: 10px;
}
.mx_UserSettings_section h3 {
margin-left: 0px;
}
.mx_UserSettings_spinner {
display: inline-block;
vertical-align: middle;
margin-right: 12px;
width: 32px;
height: 32px;
}
.mx_UserSettings_button {
display: inline;
vertical-align: middle;
border: 0px;
border-radius: 36px;
font-weight: 400;
font-size: 16px;
color: #fff;
background-color: #76cfa6;
width: auto;
margin: auto;
padding: 7px;
padding-left: 1.5em;
padding-right: 1.5em;
cursor: pointer;
}
.mx_UserSettings_button.danger {
background-color: #ff0064;
}
.mx_UserSettings_section {
margin-left: 63px;
margin-top: 28px;
margin-bottom: 28px;
}
.mx_UserSettings_toggle input {
width: 16px;
margin-right: 8px;
margin-bottom: 8px;
}
.mx_UserSettings_toggle label {
padding-bottom: 21px;
}
.mx_UserSettings_accountTable
.mx_UserSettings_notifTable
{
display: table;
}
.mx_UserSettings_notifTable .mx_Spinner {
position: absolute;
}
.mx_UserSettings_profileTable
{
display: table;
float: left;
}
.mx_UserSettings_profileTableRow
{
display: table-row;
}
.mx_UserSettings_profileLabelCell
{
padding-bottom: 21px;
display: table-cell;
font-weight: bold;
padding-right: 24px;
}
.mx_UserSettings_profileInputCell {
display: table-cell;
padding-bottom: 21px;
width: 240px;
}
.mx_UserSettings_profileInputCell input,
.mx_UserSettings_profileInputCell .mx_EditableText
{
display: inline-block;
border: 0px;
border-bottom: 1px solid rgba(151, 151, 151, 0.5);
padding: 0px;
width: 240px;
color: rgba(74, 74, 74, 0.9);
font-family: 'Open Sans', Helvetica, Arial, Sans-Serif;
font-size: 16px;
}
.mx_UserSettings_addThreepid {
display: table-cell;
padding-left: 0.5em;
position: relative;
cursor: pointer;
}
.mx_UserSettings_changePasswordButton {
float: right;
margin-right: 32px;
margin-left: 32px;
}
.mx_UserSettings_logout {
float: right;
margin-right: 32px;
margin-left: 32px;
}
.mx_UserSettings_avatarPicker {
margin-left: 32px;
margin-right: 32px;
float: right;
cursor: pointer;
}
.mx_UserSettings_avatarPicker_img .mx_BaseAvatar_image {
object-fit: cover;
}
.mx_UserSettings_avatarPicker_edit {
text-align: center;
margin-top: 10px;
}
.mx_UserSettings_avatarPicker_edit > input {
display: none;
}

View File

@@ -1,36 +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.
*/
.mx_MultiInviteDialog ul {
height: 300px;
overflow-y: auto;
}
.mx_MultiInviteDialog li {
list-style-type: none;
}
.mx_MultiInviteDialog_statusText {
text-indent: 20px;
}
.mx_MultiInviteDialog p.error {
color: #ff0064;
}
.mx_MultiInviteDialog p.invited {
color: #76cfa6;
}

View File

@@ -1,22 +0,0 @@
.mx_UserPill {
color: white;
background-color: #76cfa6;
padding: 2px 8px;
border-radius: 16px;
}
.mx_RoomPill {
background-color: white;
color: #76cfa6;
border: 1px solid #76cfa6;
padding: 2px 8px;
border-radius: 16px;
}
.mx_Markdown_BOLD {
font-weight: bold;
}
.mx_Markdown_ITALIC {
font-style: italic;
}

View File

@@ -1,47 +0,0 @@
/*
Copyright 2015, 2016 OpenMarket Ltd
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
.mx_MImageBody {
display: block;
margin-right: 34px;
}
.mx_MImageBody_thumbnail {
max-width: 100%;
/*
background-color: #fff;
border: 2px solid #fff;
border-radius: 1px;
*/
}
.mx_MImageBody_download {
color: #76cfa6;
cursor: pointer;
}
.mx_MImageBody_download a {
color: #76cfa6;
text-decoration: none;
}
.mx_MImageBody_download object {
margin-left: -16px;
padding-right: 4px;
margin-top: -4px;
vertical-align: middle;
pointer-events: none;
}

View File

@@ -1,79 +0,0 @@
.mx_Autocomplete {
position: absolute;
bottom: 0;
z-index: 1000;
width: 100%;
border: 1px solid #e5e5e5;
background: white;
border-bottom: none;
border-radius: 4px 4px 0 0;
max-height: 50vh;
overflow: auto
}
.mx_Autocomplete_ProviderSection {
border-bottom: 1px solid #e5e5e5;
}
.mx_Autocomplete_Completion_container_pill {
margin: 12px;
display: flex;
}
/* a "block" completion takes up a whole line */
.mx_Autocomplete_Completion_block {
height: 34px;
display: flex;
padding: 0 12px;
user-select: none;
cursor: pointer;
align-items: center;
color: #4a4a4a;
}
.mx_Autocomplete_Completion_block * {
margin: 0 3px;
}
.mx_Autocomplete_Completion_pill {
border-radius: 17px;
height: 34px;
display: flex;
user-select: none;
cursor: pointer;
align-items: center;
color: #4a4a4a;
}
.mx_Autocomplete_Completion_pill * {
margin: 0 3px;
}
/* container for pill-style completions */
.mx_Autocomplete_Completion_container_pill {
margin: 12px;
display: flex;
flex-flow: wrap;
}
.mx_Autocomplete_Completion.selected {
background: #f6f6f6;
outline: none;
}
.mx_Autocomplete_provider_name {
margin: 12px;
color: #454545;
font-weight: 400;
opacity: 0.4;
}
/* styling for common completion elements */
.mx_Autocomplete_Completion_subtitle {
font-style: italic;
flex: 1;
}
.mx_Autocomplete_Completion_description {
color: gray;
}

View File

@@ -1,112 +0,0 @@
/*
Copyright 2015, 2016 OpenMarket Ltd
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
.mx_EntityTile {
display: table-row;
position: relative;
color: #454545;
cursor: pointer;
}
.mx_EntityTile_invite {
display: table-cell;
vertical-align: middle;
margin-left: 10px;
width: 26px;
}
.mx_EntityTile_avatar {
display: table-cell;
padding-left: 3px;
padding-right: 12px;
padding-top: 4px;
padding-bottom: 4px;
vertical-align: middle;
width: 36px;
height: 36px;
position: relative;
}
.mx_EntityTile_power {
position: absolute;
width: 16px;
height: 17px;
top: 0px;
right: 6px;
}
.mx_EntityTile_name {
display: table-cell;
vertical-align: middle;
overflow: hidden;
font-size: 14px;
text-overflow: ellipsis;
}
.mx_EntityTile_details {
display: table-cell;
padding-right: 14px;
vertical-align: middle;
}
.mx_EntityTile_name_hover {
font-size: 13px;
overflow: hidden;
text-overflow: ellipsis;
}
.mx_EntityTile_chevron {
margin-top: 8px;
margin-right: -4px;
margin-left: 6px;
float: right;
}
.mx_EntityTile_ellipsis .mx_EntityTile_name {
font-style: italic;
font-color: #454545;
}
.mx_EntityTile_invitePlaceholder .mx_EntityTile_name {
font-style: italic;
font-color: #454545;
}
.mx_EntityTile_unavailable .mx_EntityTile_avatar,
.mx_EntityTile_unavailable .mx_EntityTile_name,
.mx_EntityTile_unavailable .mx_EntityTile_name_hover,
.mx_EntityTile_offline_beenactive .mx_EntityTile_avatar,
.mx_EntityTile_offline_beenactive .mx_EntityTile_name,
.mx_EntityTile_offline_beenactive .mx_EntityTile_name_hover
{
opacity: 0.66;
}
.mx_EntityTile_offline_neveractive .mx_EntityTile_avatar,
.mx_EntityTile_offline_neveractive .mx_EntityTile_name,
.mx_EntityTile_offline_neveractive .mx_EntityTile_name_hover
{
opacity: 0.25;
}
.mx_EntityTile_unknown .mx_EntityTile_avatar,
.mx_EntityTile_unknown .mx_EntityTile_name,
.mx_EntityTile_unknown .mx_EntityTile_name_hover
{
opacity: 0.25;
}

View File

@@ -1,267 +0,0 @@
/*
Copyright 2015, 2016 OpenMarket Ltd
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
.mx_EventTile {
max-width: 100%;
clear: both;
padding-top: 18px;
font-size: 14px;
position: relative;
}
.mx_EventTile.mx_EventTile_info {
padding-top: 0px;
}
.mx_EventTile_avatar {
position: absolute;
top: 14px;
left: 8px;
cursor: pointer;
z-index: 2;
}
.mx_EventTile.mx_EventTile_info .mx_EventTile_avatar {
top: 8px;
left: 44px;
}
.mx_EventTile_continuation {
padding-top: 0px ! important;
}
.mx_EventTile_verified {
background-color: #eaf5f0;
}
.mx_EventTile_unverified {
background-color: #ffa0a0;
}
.mx_EventTile .mx_SenderProfile {
color: #454545;
opacity: 0.5;
font-size: 14px;
display: block; /* anti-zalgo, with overflow hidden */
overflow-y: hidden;
cursor: pointer;
padding-left: 65px; /* left gutter */
padding-bottom: 0px;
padding-top: 0px;
margin: 0px;
line-height: 22px;
}
.mx_EventTile .mx_MessageTimestamp {
display: block;
visibility: hidden;
white-space: nowrap;
color: #acacac;
font-size: 11px;
left: 8px;
position: absolute;
}
.mx_EventTile_line {
position: relative;
/* ideally should be 100px, but 95px gives us a max thumbnail size of 800x600, which is nice */
margin-right: 110px;
padding-left: 65px; /* left gutter */
padding-top: 4px;
padding-bottom: 2px;
border-radius: 4px;
min-height: 24px;
line-height: 22px;
}
/* HACK to override line-height which is already marked important elsewhere */
.mx_EventTile_bigEmoji.mx_EventTile_bigEmoji {
font-size: 48px ! important;
line-height: 48px ! important;
}
/* this is used for the tile for the event which is selected via the URL.
* TODO: ultimately we probably want some transition on here.
*/
.mx_EventTile_selected .mx_EventTile_line {
border-left: #76cfa6 5px solid;
padding-left: 60px;
background-color: #f7f7f7;
}
.mx_EventTile:hover .mx_EventTile_line,
.mx_EventTile.menu .mx_EventTile_line
{
background-color: #f7f7f7;
}
.mx_EventTile_searchHighlight {
background-color: #76cfa6;
color: #fff;
border-radius: 5px;
padding-left: 2px;
padding-right: 2px;
cursor: pointer;
}
.mx_EventTile_searchHighlight a {
background-color: #76cfa6;
color: #fff;
}
.mx_EventTile_sending {
color: #ddd;
}
.mx_EventTile_notSent {
color: #f44;
}
.mx_EventTile_highlight,
.mx_EventTile_highlight .markdown-body
{
color: #FF0064;
}
.mx_EventTile_contextual {
opacity: 0.4;
}
.mx_EventTile_msgOption {
float: right;
text-align: right;
z-index: 1;
position: relative;
width: 90px;
height: 1px; /* Hack to stop the height of this pushing the messages apart. Replaces marigin-top: -6px. This interacts better with a read marker being in between. Content overflows. */
margin-right: 10px;
}
.mx_EventTile_msgOption a {
text-decoration: none;
}
.mx_EventTile_last .mx_MessageTimestamp,
.mx_EventTile:hover .mx_MessageTimestamp,
.mx_EventTile.menu .mx_MessageTimestamp
{
visibility: visible;
}
.mx_EventTile_selected .mx_MessageTimestamp {
left: 3px;
}
.mx_EventTile_editButton {
position: absolute;
display: inline-block;
visibility: hidden;
cursor: pointer;
top: 6px;
right: 6px;
}
.mx_EventTile:hover .mx_EventTile_editButton,
.mx_EventTile.menu .mx_EventTile_editButton
{
visibility: visible;
}
.mx_EventTile.menu .mx_MessageTimestamp {
visibility: visible;
}
.mx_EventTile_readAvatars {
position: relative;
display: inline-block;
width: 14px;
height: 14px;
top: 29px;
}
.mx_EventTile_continuation .mx_EventTile_readAvatars,
.mx_EventTile_info .mx_EventTile_readAvatars
{
top: 7px;
}
.mx_EventTile_readAvatars .mx_BaseAvatar {
position: absolute;
display: inline-block;
}
.mx_EventTile_readAvatarRemainder {
color: #acacac;
font-size: 11px;
position: absolute;
}
/* all the overflow-y: hidden; are to trap Zalgos -
but they introduce an implicit overflow-x: auto.
so make that explicitly hidden too to avoid random
horizontal scrollbars occasionally appearing, like in
https://github.com/vector-im/vector-web/issues/1154
*/
.mx_EventTile_content {
display: block;
overflow-y: hidden;
overflow-x: hidden;
margin-right: 34px;
}
/* De-zalgoing */
.mx_EventTile_body {
overflow-y: hidden;
}
/* Various markdown overrides */
.mx_EventTile_content .markdown-body {
font-family: inherit ! important;
white-space: normal ! important;
line-height: inherit ! important;
color: inherit;
font-size: 14px;
}
/* have to use overlay rather than auto otherwise Linux and Windows
Chrome gets very confused about vertical spacing:
https://github.com/vector-im/vector-web/issues/754
*/
.mx_EventTile_content .markdown-body pre {
overflow-x: overlay;
overflow-y: visible;
}
.mx_EventTile_content .markdown-body h1,
.mx_EventTile_content .markdown-body h2,
.mx_EventTile_content .markdown-body h3,
.mx_EventTile_content .markdown-body h4,
.mx_EventTile_content .markdown-body h5,
.mx_EventTile_content .markdown-body h6
{
font-family: inherit ! important;
}
.mx_EventTile_content .markdown-body a {
color: #76cfa6;
}
.mx_EventTile_content .markdown-body .hljs {
display: inline ! important;
}
/* end of overrides */

View File

@@ -1,65 +0,0 @@
/*
Copyright 2015, 2016 OpenMarket Ltd
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
.mx_LinkPreviewWidget {
margin-top: 15px;
margin-right: 15px;
margin-bottom: 15px;
display: -webkit-flex;
display: flex;
border-left: 4px solid #ddd;
color: #888;
}
.mx_LinkPreviewWidget_image {
-webkit-flex: 0 0 100px;
flex: 0 0 100px;
margin-left: 15px;
text-align: center;
cursor: pointer;
}
.mx_LinkPreviewWidget_caption {
margin-left: 15px;
-webkit-flex: 1 1 auto;
flex: 1 1 auto;
}
.mx_LinkPreviewWidget_title {
display: inline;
font-weight: bold;
}
.mx_LinkPreviewWidget_siteName {
display: inline;
}
.mx_LinkPreviewWidget_description {
margin-top: 8px;
white-space: normal;
word-break: break-word;
}
.mx_LinkPreviewWidget_cancel {
visibility: hidden;
cursor: pointer;
-webkit-flex: 0 0 40px;
flex: 0 0 40px;
}
.mx_LinkPreviewWidget:hover .mx_LinkPreviewWidget_cancel {
visibility: visible;
}

View File

@@ -1,57 +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.
*/
.mx_MemberDeviceInfo {
font-size: 12px;
display: table-row;
height: 17px;
}
.mx_MemberDeviceInfo div {
display: table-cell;
}
.mx_MemberDeviceInfo_textButton {
color: #fff;
background-color: #76cfa6;
border-radius: 17px;
text-align: center;
padding-left: 1em;
padding-right: 1em;
cursor: pointer;
}
.mx_MemberDeviceInfo div.mx_MemberDeviceInfo_verified,
.mx_MemberDeviceInfo div.mx_MemberDeviceInfo_unverified,
.mx_MemberDeviceInfo div.mx_MemberDeviceInfo_blocked {
color: #fff;
width: 17px;
border-radius: 17px;
text-align: center;
}
.mx_MemberDeviceInfo div.mx_MemberDeviceInfo_verified {
background-color: #76cfa6;
}
.mx_MemberDeviceInfo div.mx_MemberDeviceInfo_unverified {
background-color: #eca46f;
}
.mx_MemberDeviceInfo div.mx_MemberDeviceInfo_blocked {
background-color: #e55e5e;
}

View File

@@ -1,80 +0,0 @@
/*
Copyright 2015, 2016 OpenMarket Ltd
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
.mx_MemberInfo {
margin-top: 20px;
height: 100%;
overflow-y: auto;
}
.mx_MemberInfo h2 {
margin-top: 6px;
}
.mx_MemberInfo_cancel {
float: right;
margin-right: 18px;
cursor: pointer;
}
.mx_MemberInfo_avatar {
clear: both;
}
.mx_MemberInfo_avatar .mx_BaseAvatar {
cursor: not-allowed;
}
.mx_MemberInfo_avatar .mx_BaseAvatar.mx_BaseAvatar_image {
cursor: pointer;
}
.mx_MemberInfo_profile {
margin-bottom: 16px;
}
.mx_MemberInfo h3 {
text-transform: uppercase;
color: #3d3b39;
font-weight: 600;
font-size: 13px;
margin-top: 16px;
margin-bottom: 14px;
}
.mx_MemberInfo_profileField {
font-color: #999999;
font-size: 13px;
position: relative;
background-color: #fff;
}
.mx_MemberInfo_buttons {
margin-bottom: 16px;
}
.mx_MemberInfo_field {
cursor: pointer;
font-size: 13px;
color: #76cfa6;
margin-left: 8px;
line-height: 23px;
}
.mx_MemberInfo_devices {
display: table;
border-spacing: 5px;
}

View File

@@ -1,103 +0,0 @@
/*
Copyright 2015, 2016 OpenMarket Ltd
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
.mx_MemberList {
height: 100%;
margin-top: 12px;
margin-right: 20px;
-webkit-flex: 1;
flex: 1;
display: -webkit-box;
display: -moz-box;
display: -ms-flexbox;
display: -webkit-flex;
display: flex;
flex-direction: column;
-webkit-flex-direction: column;
}
.mx_MemberList .mx_Spinner {
flex: 0;
-webkit-flex: 0;
}
.mx_MemberList_chevron {
position: absolute;
right: 35px;
margin-top: -15px;
}
.mx_MemberList_border {
overflow-y: auto;
order: 1;
-webkit-flex: 1 1 0;
flex: 1 1 0px;
}
.mx_MemberList .mx_SearchableEntityList {
order: 1;
flex: 0 0 auto;
-webkit-flex: 0 0 auto;
}
.mx_MemberList .mx_SearchableEntityList_expanded {
flex: 1 0 0;
-webkit-flex: 1 0 0;
}
.mx_MemberList_joined {
order: 2;
flex: 1 0 0;
-webkit-flex: 1 0 0;
overflow-y: auto;
}
/*
.mx_MemberList_invited {
order: 3;
flex: 0 0 100px;
-webkit-flex: 0 0 100px;
overflow-y: auto;
}
*/
.mx_MemberList_invited h2 {
text-transform: uppercase;
color: #3d3b39;
font-weight: 600;
font-size: 13px;
padding-left: 3px;
padding-right: 12px;
margin-top: 8px;
margin-bottom: 4px;
}
/* we have to have display: table in order for the horizontal wrapping to work */
.mx_MemberList_wrapper {
display: table;
table-layout: fixed;
width: 100%;
}
.mx_MemberList_outerWrapper {
height: 0px;
}

View File

@@ -1,124 +0,0 @@
/*
Copyright 2015, 2016 OpenMarket Ltd
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
.mx_MessageComposer_wrapper {
max-width: 960px;
vertical-align: middle;
margin: auto;
border-top: 1px solid #e5e5e5;
}
.mx_MessageComposer_autocomplete_wrapper {
position: relative;
height: 0;
}
.mx_MessageComposer_row {
display: flex;
flex-direction: row;
align-items: center;
width: 100%;
}
.mx_MessageComposer .mx_MessageComposer_avatar {
padding-left: 10px;
padding-right: 28px;
}
.mx_MessageComposer .mx_MessageComposer_avatar .mx_BaseAvatar {
display: block;
}
.mx_MessageComposer_composecontrols {
width: 100%;
}
.mx_MessageComposer_noperm_error {
width: 100%;
height: 60px;
text-align: center;
font-style: italic;
color: #888;
}
.mx_MessageComposer_input {
flex: 1;
vertical-align: middle;
min-height: 60px;
max-height: 120px;
display: flex;
align-items: center;
overflow: auto;
font-size: 14px;
margin-right: 6px;
}
.mx_MessageComposer_input_rte {
border-top: 2px solid #76cfa6; /* placeholder RTE indicator */
}
.mx_MessageComposer_input .DraftEditor-root {
flex: 1;
}
.mx_MessageComposer_input textarea {
display: block;
width: 100%;
padding: 0px;
margin-top: 6px;
margin-bottom: 6px;
border: 0px;
resize: none;
outline: none;
-webkit-box-shadow: none;
-moz-box-shadow: none;
box-shadow: none;
color: #454545;
background-color: #fff;
font-size: 14px;
/* needed for FF */
font-family: 'Open Sans', Arial, Helvetica, Sans-Serif;
}
/* hack for FF as vertical alignment of custom placeholder text is broken */
.mx_MessageComposer_input textarea::-moz-placeholder {
line-height: 100%;
color: #76cfa6;
opacity: 1.0;
}
.mx_MessageComposer_input textarea::-webkit-input-placeholder {
color: #76cfa6;
}
.mx_MessageComposer_upload,
.mx_MessageComposer_hangup,
.mx_MessageComposer_voicecall,
.mx_MessageComposer_videocall {
/*display: table-cell;*/
/*vertical-align: middle;*/
/*padding-left: 10px;*/
padding-right: 5px;
cursor: pointer;
padding-top: 4px;
}
.mx_MessageComposer_upload object,
.mx_MessageComposer_hangup object,
.mx_MessageComposer_voicecall object,
.mx_MessageComposer_videocall object {
pointer-events: none;
}

View File

@@ -1,250 +0,0 @@
/*
Copyright 2015, 2016 OpenMarket Ltd
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
/* add 20px to the height of the header when editing */
.mx_RoomHeader_editing {
-webit-flex: 0 0 93px ! important;
flex: 0 0 93px ! important;
}
.mx_RoomHeader_wrapper {
max-width: 960px;
margin: auto;
height: 70px;
-webkit-align-items: center;
align-items: center;
display: -webkit-box;
display: -moz-box;
display: -ms-flexbox;
display: -webkit-flex;
display: flex;
}
.mx_RoomHeader_leftRow {
margin-left: -2px;
-webkit-box-ordinal-group: 1;
-moz-box-ordinal-group: 1;
-ms-flex-order: 1;
-webkit-order: 1;
order: 1;
-webkit-flex: 1;
flex: 1;
}
.mx_RoomHeader_spinner {
height: 36px;
-webkit-box-ordinal-group: 2;
-moz-box-ordinal-group: 2;
-ms-flex-order: 2;
-webkit-order: 2;
order: 2;
padding-left: 12px;
padding-right: 12px;
}
.mx_RoomHeader_textButton {
height: 36px;
background-color: #76cfa6;
border-radius: 36px;
margin-right: 8px;
color: #fff;
line-height: 34px;
margin-top: -2px;
text-align: center;
-webkit-box-ordinal-group: 2;
-moz-box-ordinal-group: 2;
-ms-flex-order: 2;
-webkit-order: 2;
order: 2;
cursor: pointer;
/*
-webkit-flex: 0 0 90px;
flex: 0 0 90px;
*/
padding-left: 12px;
padding-right: 12px;
}
.mx_RoomHeader_cancelButton {
-webkit-box-ordinal-group: 2;
-moz-box-ordinal-group: 2;
-ms-flex-order: 2;
-webkit-order: 2;
order: 2;
cursor: pointer;
padding-left: 12px;
padding-right: 12px;
}
.mx_RoomHeader_rightRow {
margin-top: 4px;
background-color: #fff;
display: flex;
align-items: center;
-webkit-box-ordinal-group: 3;
-moz-box-ordinal-group: 3;
-ms-flex-order: 3;
-webkit-order: 3;
order: 3;
}
.mx_RoomHeader_info {
display: table-cell;
width: 100%;
vertical-align: middle;
}
.mx_RoomHeader_simpleHeader {
line-height: 70px;
color: #454545;
font-size: 22px;
font-weight: bold;
overflow: hidden;
margin-left: 63px;
text-overflow: ellipsis;
width: 100%;
}
.mx_RoomHeader_simpleHeader .mx_RoomHeader_cancelButton {
float: right;
}
.mx_RoomHeader_name {
vertical-align: middle;
width: 100%;
height: 31px;
overflow: hidden;
color: #454545;
font-weight: bold;
font-size: 22px;
padding-left: 19px;
padding-right: 16px;
/* why isn't text-overflow working? */
text-overflow: ellipsis;
border-bottom: 1px solid transparent;
}
.mx_RoomHeader_nametext {
display: inline-block;
}
.mx_RoomHeader_settingsHint {
color: #a2a2a2 ! important;
}
.mx_RoomHeader_searchStatus {
display: inline-block;
font-weight: normal;
opacity: 0.6;
}
.mx_RoomHeader_settingsButton object {
pointer-events: none;
}
.mx_RoomHeader_name,
.mx_RoomHeader_avatar,
.mx_RoomHeader_avatarPicker,
.mx_RoomHeader_avatarPicker_edit {
cursor: pointer;
}
.mx_RoomHeader_name:hover div:not(.mx_RoomHeader_editable) {
color: #76cfa6;
}
.mx_RoomHeader_placeholder {
color: #a2a2a2 ! important;
}
.mx_RoomHeader_editable {
border-bottom: 1px solid #c7c7c7 ! important;
min-width: 150px;
cursor: text;
}
.mx_RoomHeader_editable:focus {
border-bottom: 1px solid #76CFA6 ! important;
outline: none;
box-shadow: none;
}
.mx_RoomHeader_topic {
vertical-align: bottom;
float: left;
max-height: 42px;
color: #A2A2A2;
font-weight: 300;
font-size: 13px;
margin-left: 19px;
margin-right: 16px;
overflow: hidden;
text-overflow: ellipsis;
border-bottom: 1px solid transparent;
}
.mx_RoomHeader_avatar {
display: table-cell;
width: 48px;
height: 50px;
vertical-align: middle;
}
.mx_RoomHeader_avatar .mx_BaseAvatar_image {
object-fit: cover;
}
.mx_RoomHeader_avatarPicker_edit {
position: absolute;
margin-left: 16px;
margin-top: 4px;
}
.mx_RoomHeader_avatarPicker_edit > label {
cursor: pointer;
}
.mx_RoomHeader_avatarPicker_edit > input {
display: none;
}
.mx_RoomHeader_button {
margin-left: 12px;
cursor: pointer;
}
.mx_RoomHeader_button object {
pointer-events: none;
}
.mx_RoomHeader_voipButton {
display: table-cell;
}
.mx_RoomHeader_voipButtons {
margin-top: 18px;
}

View File

@@ -1,35 +0,0 @@
/*
Copyright 2015, 2016 OpenMarket Ltd
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
.mx_RoomList {
padding-top: 8px;
padding-bottom: 12px;
min-height: 400px;
}
.mx_RoomList_expandButton {
margin-left: 8px;
cursor: pointer;
padding-left: 12px;
padding-right: 12px;
}
/* Evil hacky override until Chrome fixes drop and drag table cells
and we can correctly fix horizontal wrapping in the sidebar again */
.mx_RoomList_scrollbar .gm-scroll-view {
overflow-x: hidden ! important;
overflow-y: scroll ! important;
}

View File

@@ -1,68 +0,0 @@
/*
Copyright 2015, 2016 OpenMarket Ltd
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
.mx_RoomPreviewBar {
text-align: center;
height: 176px;
-webkit-align-items: center;
align-items: center;
flex-direction: column;
-webkit-flex-direction: column;
justify-content: center;
-webkit-justify-content: center;
display: -webkit-box;
display: -moz-box;
display: -ms-flexbox;
display: -webkit-flex;
display: flex;
}
.mx_RoomPreviewBar_wrapper {
}
.mx_RoomPreviewBar_invite_text {
color: #454545;
}
.mx_RoomPreviewBar_join_text {
color: #ff0064;
}
.mx_RoomPreviewBar_preview_text {
margin-top: 25px;
color: #a4a4a4;
}
.mx_RoomPreviewBar_join_text a {
text-decoration: underline;
cursor: pointer;
}
.mx_RoomPreviewBar_warning {
display: -webkit-flex;
display: flex;
-webkit-align-items: center;
align-items: center;
padding: 8px;
}
.mx_RoomPreviewBar_warningIcon {
padding: 12px;
}

View File

@@ -1,202 +0,0 @@
/*
Copyright 2015, 2016 OpenMarket Ltd
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
.mx_RoomSettings {
margin-left: 65px;
margin-bottom: 20px;
}
.mx_RoomSettings_leaveButton {
height: 36px;
background-color: #76cfa6;
border-radius: 36px;
margin-right: 8px;
color: #fff;
line-height: 34px;
text-align: center;
float: right;
cursor: pointer;
padding-left: 12px;
padding-right: 12px;
margin-right: 32px;
}
.mx_RoomSettings_powerLevels {
display: table;
}
.mx_RoomSettings_powerLevel {
display: table-row;
}
.mx_RoomSettings_powerLevelKey,
.mx_RoomSettings_powerLevel .mx_PowerSelector {
display: table-cell;
padding-bottom: 5px;
}
.mx_RoomSettings_powerLevelKey {
text-align: right;
padding-right: 0.3em;
}
.mx_RoomSettings h3 {
text-transform: uppercase;
color: #3d3b39;
font-weight: 600;
font-size: 13px;
margin-top: 36px;
margin-bottom: 10px;
}
.mx_RoomSettings .mx_RoomSettings_toggles label {
margin-bottom: 8px;
display: block;
}
.mx_RoomSettings .mx_RoomSettings_toggles input[type="checkbox"],
.mx_RoomSettings .mx_RoomSettings_toggles input[type="radio"] {
margin-right: 7px;
}
.mx_RoomSettings .mx_RoomSettings_tags input[type="checkbox"] {
margin-left: 1em;
margin-right: 7px;
}
.mx_RoomSettings .mx_RoomSettings_tags {
margin-bottom: 8px;
}
.mx_RoomSettings .mx_RoomSettings_roomColor {
display: inline-block;
position: relative;
width: 37px;
height: 37px;
border: 1px solid #979797;
margin-right: 13px;
}
.mx_RoomSettings .mx_RoomSettings_roomColor_selected {
position: absolute;
left: 10px;
top: 4px;
}
.mx_RoomSettings .mx_RoomSettings_roomColorPrimary {
height: 10px;
position: absolute;
bottom: 0px;
width: 100%;
}
.mx_RoomSettings .mx_RoomSettings_aliasLabel {
margin-bottom: 8px;
}
.mx_RoomSettings .mx_RoomSettings_aliasesTable {
margin-top: 12px;
margin-bottom: 0px;
margin-left: 56px;
display: table;
}
.mx_RoomSettings .mx_RoomSettings_aliasesTableRow {
display: table-row;
margin-bottom: 16px;
}
.mx_RoomSettings .mx_RoomSettings_alias {
max-width: 400px;
margin-bottom: 16px;
/*
commented out so margin applies
display: table-cell; */
}
.mx_RoomSettings .mx_RoomSettings_addAlias,
.mx_RoomSettings .mx_RoomSettings_deleteAlias {
display: table-cell;
padding-left: 0.5em;
position: relative;
cursor: pointer;
}
.mx_RoomSettings .mx_RoomSettings_addAlias img,
.mx_RoomSettings .mx_RoomSettings_deleteAlias img {
visibility: hidden;
}
.mx_RoomSettings .mx_RoomSettings_aliasesTableRow:hover .mx_RoomSettings_addAlias img,
.mx_RoomSettings .mx_RoomSettings_aliasesTableRow:hover .mx_RoomSettings_deleteAlias img {
visibility: visible;
}
.mx_RoomSettings_warning {
color: #ff0064;
font-weight: bold;
margin-top: 8px;
margin-bottom: 8px;
}
.mx_RoomSettings_editable {
border: 0px;
border-bottom: 1px solid #c7c7c7;
padding: 0px;
min-width: 240px;
}
.mx_RoomSettings_editable:focus {
border-bottom: 1px solid #76CFA6;
outline: none;
box-shadow: none;
}
.mx_RoomSettings_deleteAlias,
.mx_RoomSettings_addAlias {
display: table-cell;
visibility: visible;
}
.mx_RoomSettings_deleteAlias:hover,
.mx_RoomSettings_addAlias:hover {
visibility: visible;
}
.mx_RoomSettings_aliasPlaceholder {
color: #a2a2a2;
}
.mx_RoomSettings_buttons {
text-align: right;
margin-bottom: 16px;
}
.mx_RoomSettings_button {
display: inline;
border: 0px;
height: 36px;
border-radius: 36px;
font-weight: 400;
font-size: 15px;
color: #fff;
background-color: #76cfa6;
width: auto;
margin: auto;
padding: 6px;
padding-left: 1em;
padding-right: 1em;
}

View File

@@ -1,191 +0,0 @@
/*
Copyright 2015, 2016 OpenMarket Ltd
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
.mx_RoomTile {
position: relative;
cursor: pointer;
font-size: 13px;
display: block;
height: 34px;
}
.mx_RoomTile_nameContainer {
display: inline-block;
width: 180px;
height: 24px;
}
.mx_RoomTile_avatar {
display: inline-block;
padding-top: 5px;
padding-bottom: 5px;
padding-left: 16px;
padding-right: 6px;
width: 24px;
height: 24px;
vertical-align: middle;
}
.mx_RoomTile_avatar_container:hover:before,
.mx_RoomTile_avatar_container.mx_RoomTile_avatar_roomTagMenu:before {
display: block;
position: absolute;
content: "";
border-radius: 40px;
background-image: url("img/icons_ellipsis.svg");
background-size: 25px;
left: 15px;
width: 24px;
height: 24px;
z-index: 4;
}
.mx_RoomTile_avatar_container:hover:after,
.mx_RoomTile_avatar_container.mx_RoomTile_avatar_roomTagMenu:after {
display: block;
position: absolute;
content: "";
border-radius: 40px;
background: #4A4A4A;
top: 5px;
width: 24px;
height: 24px;
opacity: 0.6;
z-index: 2;
}
.collapsed .mx_RoomTile_avatar_container:hover:before {
display: none;
}
.collapsed .mx_RoomTile_avatar_container:hover:after {
display: none;
}
.mx_RoomTile_name {
display: inline-block;
position: relative;
width: 165px;
vertical-align: middle;
padding-left: 6px;
padding-right: 6px;
padding-top: 2px;
padding-bottom: 3px;
color: rgba(69, 69, 69, 0.8);
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.mx_RoomTile_ellipsis .mx_RoomTile_name {
font-style: italic;
color: #454545;
}
.mx_RoomTile_invite {
/* color: rgba(69, 69, 69, 0.5);
*/
}
.collapsed .mx_RoomTile_nameContainer {
height: 0;
}
.collapsed .mx_RoomTile_name {
display: none;
}
.collapsed .mx_RoomTile_badge {
top: -2px;
min-width: 12px;
height: 16px;
border-radius: 16px;
padding: 0px 4px 0px 4px;
z-index: 200;
}
/* Hide the bottom of speech bubble */
.collapsed .mx_RoomTile_highlight .mx_RoomTile_badge:after {
display: none;
}
/* This is the bottom of the speech bubble */
.mx_RoomTile_highlight .mx_RoomTile_badge:after {
content: "";
position: absolute;
display: block;
width: 0;
height: 0;
margin-left: 6px;
border-top: 8px solid #ff0064;
border-right: 10px solid transparent;
}
.mx_RoomTile_badge {
display: inline-block;
min-width: 19px;
height: 17px;
position: absolute;
right: 8px; /*gutter */
top: 9px;
border-radius: 14px;
color: #fff;
font-weight: 600;
font-size: 11px;
text-align: center;
padding-top: 1px;
padding-left: 4px;
padding-right: 4px;
}
.mx_RoomTile .mx_RoomTile_badge.mx_RoomTile_badgeButton,
.mx_RoomTile.mx_RoomTile_notificationStateMenu .mx_RoomTile_badge {
letter-spacing: 0.1em;
opacity: 1;
}
.mx_RoomTile.mx_RoomTile_noBadges .mx_RoomTile_badge.mx_RoomTile_badgeButton,
.mx_RoomTile.mx_RoomTile_notificationStateMenu.mx_RoomTile_noBadges .mx_RoomTile_badge {
background-color: rgb(214, 214, 214);
}
.mx_RoomTile_unreadNotify .mx_RoomTile_badge {
background-color: #76cfa6;
}
.mx_RoomTile_highlight .mx_RoomTile_badge {
background-color: #ff0064;
}
.mx_RoomTile_unread, .mx_RoomTile_highlight {
font-weight: 800;
}
.mx_RoomTile_selected {
background-color: rgba(118,207,166,0.2);
}
.mx_RoomTile .mx_RoomTile_name.mx_RoomTile_badgeShown {
width: 140px;
}
.mx_RoomTile_arrow {
position: absolute;
right: 0px;
}
.mx_RoomTile:hover {
}

View File

@@ -1,81 +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.
*/
.mx_SearchableEntityList {
display: flex;
display: -webkit-flex;
flex-direction: column;
-webkit-flex-direction: column;
}
.mx_SearchableEntityList_query {
font-family: 'Open Sans', Arial, Helvetica, Sans-Serif;
border-radius: 3px;
border: 1px solid #f0f0f0;
padding: 9px;
color: #454545;
background-color: #fff;
margin-left: 3px;
font-size: 15px;
margin-bottom: 8px;
width: 189px;
}
.mx_SearchableEntityList_query::-moz-placeholder {
color: #454545;
opacity: 0.5;
font-size: 12px;
}
.mx_SearchableEntityList_query::-webkit-input-placeholder {
color: #454545;
opacity: 0.5;
font-size: 12px;
}
.mx_SearchableEntityList_listWrapper {
flex: 1;
-webkit-flex: 1;
overflow-y: auto;
}
.mx_SearchableEntityList_list {
display: table;
table-layout: fixed;
width: 100%;
}
.mx_SearchableEntityList_list .mx_EntityTile_chevron {
display: none;
}
.mx_SearchableEntityList_hrWrapper {
width: 100%;
flex: 0 0 auto;
-webkit-flex: 0 0 auto;
}
.mx_SearchableEntityList hr {
height: 1px;
border: 0px;
color: #e1dddd;
background-color: #e1dddd;
margin-right: 15px;
margin-top: 11px;
margin-bottom: 11px;
}

View File

@@ -1,54 +0,0 @@
/*
Copyright 2015, 2016 OpenMarket Ltd
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
.mx_TabCompleteBar {
overflow: hidden;
}
.mx_TabCompleteBar_item {
display: inline-block;
margin-right: 15px;
margin-bottom: 2px;
cursor: pointer;
}
.mx_TabCompleteBar_command {
margin-right: 8px;
background-color: #76CFA6;
padding-left: 8px;
padding-right: 8px;
padding-top: 2px;
padding-bottom: 2px;
margin-bottom: 6px;
border-radius: 30px;
}
.mx_TabCompleteBar_command .mx_TabCompleteBar_text {
opacity: 1.0;
vertical-align: initial;
color: #fff;
}
.mx_TabCompleteBar_item img {
margin-right: 8px;
vertical-align: middle;
}
.mx_TabCompleteBar_text {
color: #4a4a4a;
vertical-align: middle;
opacity: 0.5;
}

View File

@@ -1,45 +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.
*/
.mx_TopUnreadMessagesBar {
margin: auto; /* centre horizontally */
max-width: 960px;
padding-top: 5px;
padding-bottom: 5px;
border-bottom: 1px solid #e5e5e5;
}
.mx_TopUnreadMessagesBar_scrollUp {
display: inline;
cursor: pointer;
}
.mx_TopUnreadMessagesBar_scrollUp img {
padding-left: 10px;
padding-right: 31px;
vertical-align: middle;
}
.mx_TopUnreadMessagesBar_scrollUp span {
opacity: 0.5;
}
.mx_TopUnreadMessagesBar_close {
float: right;
padding-right: 14px;
padding-top: 3px;
cursor: pointer;
}

View File

@@ -1,47 +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.
*/
.mx_DevicesPanel {
display: table;
table-layout: fixed;
width: 880px;
border-spacing: 2px;
}
.mx_DevicesPanel_header {
display: table-header-group;
font-weight: bold;
}
.mx_DevicesPanel_header > div {
display: table-cell;
}
.mx_DevicesPanel_header .mx_DevicesPanel_deviceLastSeen {
width: 30%;
}
.mx_DevicesPanel_header .mx_DevicesPanel_deviceButtons {
width: 20%;
}
.mx_DevicesPanel_device {
display: table-row;
}
.mx_DevicesPanel_device > div {
display: table-cell;
}

View File

@@ -1,33 +0,0 @@
/*
Copyright 2015, 2016 OpenMarket Ltd
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
.mx_IntegrationsManager {
display: -webkit-flex;
display: flex;
width: 100%;
height: 100%;
-webkit-align-items: center;
align-items: center;
justify-content: center;
-webkit-justify-content: center;
}
.mx_IntegrationsManager iframe {
background-color: #fff;
border: 0px;
width: 720px;
height: 512px;
}

View File

@@ -1,5 +1,5 @@
/*
Copyright 2015, 2016 OpenMarket Ltd
Copyright 2015 OpenMarket Ltd
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
@@ -14,11 +14,24 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
.mx_SetDisplayNameDialog_input {
border-radius: 3px;
border: 1px solid #f0f0f0;
padding: 9px;
color: #454545;
.mx_MImageTile_thumbnail {
/*
background-color: #fff;
font-size: 15px;
border: 2px solid #fff;
border-radius: 1px;
*/
}
.mx_MImageTile_download {
color: #80cef4;
cursor: pointer;
}
.mx_MImageTile_download a {
color: #80cef4;
text-decoration: none;
}
.mx_MImageTile_download img {
padding-right: 8px;
}

View File

@@ -1,5 +1,5 @@
/*
Copyright 2016 OpenMarket Ltd
Copyright 2015 OpenMarket Ltd
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
@@ -14,7 +14,6 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
.mx_PresenceLabel {
font-size: 11px;
.mx_MNoticeTile {
opacity: 0.5;
}
}

View File

@@ -1,9 +1,12 @@
/*
Copyright 2015, 2016 OpenMarket Ltd
Copyright 2015 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.
@@ -11,6 +14,7 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
.mx_UnknownBody {
.mx_MTextTile {
white-space: pre-wrap;
}

View File

@@ -1,5 +1,5 @@
/*
Copyright 2015, 2016 OpenMarket Ltd
Copyright 2015 OpenMarket Ltd
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
@@ -14,21 +14,21 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
.mx_BaseAvatar {
position: relative;
}
.mx_BaseAvatar_initial {
position: absolute;
z-index: 1;
color: #fff;
.mx_MatrixToolbar {
text-align: center;
speak: none;
pointer-events: none;
font-weight: normal;
background-color: #ff0064;
color: #fff;
font-weight: bold;
padding: 6px;
}
.mx_BaseAvatar_image {
border-radius: 40px;
vertical-align: top;
.mx_MatrixToolbar button {
margin-left: 12px;
}
.mx_MatrixToolbar_close {
float: right;
margin-top: 3px;
margin-right: 12px;
cursor: pointer;
}

View File

@@ -1,5 +1,5 @@
/*
Copyright 2015, 2016 OpenMarket Ltd
Copyright 2015 OpenMarket Ltd
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
@@ -14,7 +14,3 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
.mx_MNoticeBody {
white-space: pre-wrap;
opacity: 0.6;
}

View File

@@ -0,0 +1,134 @@
/*
Copyright 2015 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.
*/
.mx_MemberTile {
display: table-row;
height: 49px;
position: relative;
}
.mx_MemberTile_avatar {
display: table-cell;
padding-left: 14px;
padding-right: 12px;
padding-top: 3px;
padding-bottom: 3px;
vertical-align: middle;
width: 40px;
height: 40px;
position: relative;
}
.mx_MemberTile_inviteTile {
cursor: pointer;
}
.mx_MemberTile_inviteEditing {
display: initial ! important;
}
.mx_MemberTile_inviteEditing .mx_MemberTile_avatar {
display: none;
}
.mx_MemberTile_inviteEditing .mx_MemberTile_name {
width: 200px;
}
.mx_MemberTile_inviteEditing .mx_MemberTile_name input {
border-radius: 3px;
border: 1px solid #c7c7c7;
font-weight: 300;
font-size: 14px;
padding: 9px;
margin-top: 6px;
margin-left: 14px;
}
.mx_MemberTile_power {
position: absolute;
width: 48px;
height: 48px;
left: 10px;
top: -1px;
}
.mx_MemberTile_name {
display: table-cell;
vertical-align: middle;
overflow: hidden;
text-overflow: ellipsis;
}
.mx_MemberTile_details {
display: table-cell;
padding-right: 14px;
vertical-align: middle;
}
.mx_MemberTile_hover {
background-color: #f0f0f0;
font-size: 12px;
color: #747474;
}
.mx_MemberTile_userId {
font-weight: bold;
overflow: hidden;
text-overflow: ellipsis;
}
.mx_MemberTile_leave {
cursor: pointer;
margin-top: 8px;
margin-right: -4px;
margin-left: 6px;
float: right;
}
/*
.mx_MemberTile_nameWrapper {
display: table-cell;
vertical-align: middle;
overflow: hidden;
text-overflow: ellipsis;
}
.mx_MemberTile_nameSpan {
}
*/
.mx_MemberTile_unavailable .mx_MemberTile_avatar,
.mx_MemberTile_unavailable .mx_MemberTile_name,
.mx_MemberTile_unavailable .mx_MemberTile_nameSpan
{
opacity: 0.66;
}
.mx_MemberTile_offline .mx_MemberTile_avatar,
.mx_MemberTile_offline .mx_MemberTile_name,
.mx_MemberTile_offline .mx_MemberTile_nameSpan
{
opacity: 0.25;
}
.mx_MemberTile_zalgo {
font-family: Helvetica, Arial, Sans-Serif;
}
.mx_MemberTile:hover .mx_MessageTimestamp {
display: block;
}

View File

@@ -0,0 +1,84 @@
/*
Copyright 2015 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.
*/
.mx_MessageComposer_wrapper {
max-width: 720px;
height: 50px;
vertical-align: middle;
margin: auto;
background-color: #fff;
border-radius: 25px;
border: 1px solid #a9dbf4;
}
.mx_MessageComposer_row {
display: table-row;
width: 100%;
height: 50px;
}
.mx_MessageComposer .mx_MessageComposer_avatar {
display: table-cell;
padding-left: 5px;
padding-right: 10px;
height: 50px;
}
.mx_MessageComposer .mx_MessageComposer_avatar img {
margin-top: 5px;
border-radius: 20px;
background-color: #dbdbdb;
}
.mx_MessageComposer_input {
display: table-cell;
width: 100%;
vertical-align: middle;
height: 50px;
}
.mx_MessageComposer_input textarea {
font-size: 15px;
width: 100%;
height: 1.2em;
padding-top: 0.7em;
padding-bottom: 0.7em;
border: 0px;
resize: none;
outline: none;
-webkit-box-shadow: none;
-moz-box-shadow: none;
box-shadow: none;
/* needed for FF */
font-family: 'Lato', Helvetica, Arial, Sans-Serif;
}
/* hack for FF as vertical alignment of custom placeholder text is broken */
.mx_MessageComposer_input textarea::-moz-placeholder {
line-height: 100%;
}
.mx_MessageComposer_upload {
display: table-cell;
vertical-align: middle;
padding-right: 15px;
cursor: pointer;
}
.mx_MessageComposer_upload img {
margin-top: 5px;
}

View File

@@ -0,0 +1,111 @@
/*
Copyright 2015 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.
*/
.mx_MessageTile {
max-width: 100%;
clear: both;
margin-top: 32px;
margin-left: 56px;
}
.mx_MessageTile_avatar {
padding-left: 12px;
padding-right: 12px;
margin-left: -64px;
margin-top: -7px;
float: left;
}
.mx_MessageTile_avatar img {
background-color: #dbdbdb;
border-radius: 20px;
border: 0px;
}
.mx_MessageTile_continuation {
margin-top: 8px ! important;
}
.mx_MessageTile .mx_SenderProfile {
color: #454545;
opacity: 0.5;
font-size: 14px;
margin-bottom: 4px;
display: block;
}
.mx_MessageTile .mx_MessageTimestamp {
color: #454545;
opacity: 0.5;
font-size: 14px;
float: right;
}
.mx_MessageTile_content {
padding-right: 100px;
display: block;
}
.mx_MessageTile_notice .mx_MessageTile_content {
opacity: 0.5;
}
.mx_MessageTile_sending {
color: #ddd;
}
.mx_MessageTile_notSent {
color: #f11;
}
.mx_MessageTile_highlight {
color: #FF0064;
}
.mx_MessageTile_msgOption {
float: right;
}
.mx_MessageTimestamp {
display: none;
}
.mx_MessageTile_last .mx_MessageTimestamp {
display: block;
}
.mx_MessageTile:hover .mx_MessageTimestamp {
display: block;
}
.mx_MessageTile_editButton {
float: right;
display: none;
border: 0px;
outline: none;
}
.mx_MessageTile:hover .mx_MessageTile_editButton {
display: inline-block;
}
.mx_MessageTile.menu .mx_MessageTile_editButton {
display: inline-block;
}
.mx_MessageTile.menu .mx_MessageTimestamp {
display: inline-block;
}

View File

@@ -1,5 +1,5 @@
/*
Copyright 2015, 2016 OpenMarket Ltd
Copyright 2015 OpenMarket 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

@@ -1,5 +1,5 @@
/*
Copyright 2015, 2016 OpenMarket Ltd
Copyright 2015 OpenMarket Ltd
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
@@ -14,11 +14,14 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
.mx_MTextBody {
white-space: pre-wrap;
}
.mx_MTextBody pre{
overflow-y: auto;
max-height: 30vh;
.mx_RoomDropTarget {
font-size: 14px;
text-align: center;
margin-left: 8px;
margin-right: 8px;
padding-top: 16px;
padding-bottom: 16px;
background-color: #fbfbfb;
border: 1px dashed #d7d7d7;
border-radius: 8px;
}

View File

@@ -0,0 +1,173 @@
/*
Copyright 2015 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.
*/
.mx_RoomHeader {
}
.mx_RoomHeader_wrapper {
max-width: 720px;
margin: auto;
height: 88px;
border-bottom: 1px solid #a8dbf3;
display: -webkit-box;
display: -moz-box;
display: -ms-flexbox;
display: -webkit-flex;
display: flex;
}
.mx_RoomHeader_leftRow {
height: 48px;
margin-top: 18px;
-webkit-box-ordinal-group: 1;
-moz-box-ordinal-group: 1;
-ms-flex-order: 1;
-webkit-order: 1;
order: 1;
-webkit-flex: 1;
flex: 1;
}
.mx_RoomHeader_textButton {
height: 48px;
margin-top: 18px;
background-color: #80cef4;
border-radius: 48px;
margin-right: 8px;
color: #fff;
line-height: 48px;
text-align: center;
-webkit-box-ordinal-group: 2;
-moz-box-ordinal-group: 2;
-ms-flex-order: 2;
-webkit-order: 2;
order: 2;
cursor: pointer;
/*
-webkit-flex: 0 0 90px;
flex: 0 0 90px;
*/
padding-left: 12px;
padding-right: 12px;
}
.mx_RoomHeader_rightRow {
height: 48px;
margin-top: 18px;
background-color: #fff;
border-radius: 48px;
border: 1px solid #a9dbf4;
-webkit-box-ordinal-group: 3;
-moz-box-ordinal-group: 3;
-ms-flex-order: 3;
-webkit-order: 3;
order: 3;
}
.mx_RoomHeader_info {
display: table-cell;
height: 48px;
vertical-align: middle;
}
.mx_RoomHeader_simpleHeader {
line-height: 88px;
color: #80cef4;
font-weight: 400;
font-size: 20px;
overflow: hidden;
text-overflow: ellipsis;
}
.mx_RoomHeader_name {
vertical-align: middle;
height: 28px;
color: #80cef4;
font-weight: 400;
font-size: 20px;
padding-left: 16px;
padding-right: 16px;
text-overflow: ellipsis;
}
.mx_RoomHeader_nameEditing {
padding-left: 16px;
padding-right: 16px;
margin-top: -5px;
}
.mx_RoomHeader_name input, .mx_RoomHeader_nameInput {
border-radius: 3px;
width: 260px;
border: 1px solid #c7c7c7;
font-weight: 300;
font-size: 14px;
padding: 9px;
}
.mx_RoomHeader_nameInput {
margin-top: 6px;
}
.mx_RoomHeader_topic {
vertical-align: bottom;
float: left;
max-height: 38px;
color: #70b5d7;
font-weight: 300;
padding-left: 16px;
padding-right: 16px;
overflow: hidden;
text-overflow: ellipsis;
}
.mx_RoomHeader_avatar {
display: table-cell;
width: 48px;
height: 50px;
vertical-align: middle;
}
.mx_RoomHeader_avatar img {
border-radius: 24px;
}
.mx_RoomHeader_button {
height: 48px;
display: table-cell;
vertical-align: middle;
padding-left: 8px;
padding-right: 8px;
}
.mx_RoomHeader_button img {
cursor: pointer;
}
.mx_RoomHeader_voipButton {
display: table-cell;
}
.mx_RoomHeader_voipButtons {
margin-top: 18px;
}

View File

@@ -0,0 +1,70 @@
/*
Copyright 2015 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.
*/
.mx_RoomSettings {
max-height: 250px;
padding-top: 12px;
}
.mx_RoomSettings_settings {
display: table;
margin: 5px 0;
}
.mx_RoomSettings_settings > div {
display: table-row;
}
.mx_RoomSettings_settings > div > * {
display: table-cell;
margin: 0 10px;
}
.mx_RoomSettings input,
.mx_RoomSettings textarea {
border-radius: 3px;
border: 1px solid #c7c7c7;
font-weight: 300;
font-size: 14px;
padding: 9px;
margin-top: 6px;
}
.mx_RoomSettings_description {
width: 330px;
}
.mx_RoomSettings_buttons {
text-align: right;
margin-bottom: 16px;
}
.mx_RoomSettings_button {
display: inline;
border: 0px;
height: 36px;
border-radius: 36px;
font-weight: 400;
font-size: 16px;
color: #fff;
background-color: #80cef4;
width: auto;
margin: auto;
padding: 6px;
padding-left: 1em;
padding-right: 1em;
}

View File

@@ -0,0 +1,104 @@
/*
Copyright 2015 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.
*/
.mx_RoomTile {
cursor: pointer;
display: table-row;
color: #818794;
}
.mx_RoomTile_avatar {
display: table-cell;
padding-right: 10px;
padding-top: 3px;
padding-bottom: 3px;
padding-left: 10px;
vertical-align: middle;
width: 36px;
height: 36px;
position: relative;
}
.mx_RoomTile_avatar img {
border-radius: 20px;
background-color: #dbdbdb;
}
.mx_RoomTile_name {
display: table-cell;
vertical-align: middle;
overflow: hidden;
text-overflow: ellipsis;
padding-right: 16px;
}
.collapsed .mx_RoomTile_name {
display: none;
}
/*
.mx_RoomTile_nameBadge {
display: table;
width: 100%;
height: 50px;
}
.mx_RoomTile_badgeCell {
display: table-cell;
vertical-align: middle;
width: 26px;
}
.mx_RoomTile_badge {
background-color: #80cef4;
color: #fff;
border-radius: 26px;
font-weight: 400;
font-size: 14px;
line-height: 28px;
width: 26px;
height: 26px;
text-align: center;
}
*/
.mx_RoomTile_badge {
background-color: #ff0064;
border: 3px solid #fff;
border-radius: 16px;
width: 9px;
height: 9px;
position: absolute;
right: 9px;
bottom: 3px;
}
.mx_RoomTile_unread,
.mx_RoomTile_highlight,
.mx_RoomTile_invited
{
font-weight: bold;
color: #000;
}
.mx_RoomTile_selected {
background-color: #f3f8fa;
color: #80cef4;
font-weight: bold;
}
.mx_RoomTile:hover {
}

View File

@@ -1,5 +1,5 @@
/*
Copyright 2015, 2016 OpenMarket Ltd
Copyright 2015 OpenMarket Ltd
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
@@ -14,19 +14,20 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
.mx_RoomTooltip_chevron {
position: absolute;
left: -9px;
top: 7px;
}
.mx_RoomTooltip {
display: none;
position: fixed;
border: 1px solid #a4a4a4;
border: 1px solid #a9dbf4;
border-radius: 8px;
background-color: #fff;
z-index: 2000;
left: 52px;
z-index: 1000;
margin-top: 6px;
left: 64px;
padding: 6px;
}
.mx_RoomTooltip_chevron {
position: absolute;
left: -9px;
top: 8px;
}

View File

@@ -1,5 +1,5 @@
/*
Copyright 2015, 2016 OpenMarket Ltd
Copyright 2015 OpenMarket Ltd
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
@@ -13,3 +13,8 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
.mx_SenderProfile_zalgo {
font-family: Helvetica, Arial, Sans-Serif;
display: table-row ! important;
}

View File

@@ -1,5 +1,5 @@
/*
Copyright 2015, 2016 OpenMarket Ltd
Copyright 2015 OpenMarket Ltd
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
@@ -25,7 +25,7 @@ limitations under the License.
.mx_ServerConfig_help:link {
opacity: 0.8;
font-size: 13px;
font-size: 14px;
font-weight: 300;
color: #4a4a4a;
}

View File

@@ -1,5 +1,5 @@
/*
Copyright 2015, 2016 OpenMarket Ltd
Copyright 2015 OpenMarket 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

@@ -1,5 +1,5 @@
/*
Copyright 2015, 2016 OpenMarket Ltd
Copyright 2015 OpenMarket Ltd
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
@@ -16,15 +16,14 @@ limitations under the License.
.mx_IncomingCallBox {
text-align: center;
border: 1px solid #a4a4a4;
border: 1px solid #a9dbf4;
border-radius: 8px;
background-color: #fff;
position: fixed;
position: absolute;
z-index: 1000;
left: 235px;
top: 155px;
padding: 6px;
margin-top: -3px;
margin-left: -20px;
width: 200px;
}
.mx_IncomingCallBox_chevron {
@@ -40,15 +39,14 @@ limitations under the License.
}
.mx_IncomingCallBox_buttons {
display: flex;
display: -webkit-flex;
display: table-row;
}
.mx_IncomingCallBox_buttons_cell {
vertical-align: middle;
display: table-cell;
padding: 6px;
flex: 1;
-webkit-flex: 1;
width: 50%;
}
.mx_IncomingCallBox_buttons_decline,
@@ -59,7 +57,6 @@ limitations under the License.
line-height: 36px;
border-radius: 36px;
color: #fff;
margin: auto;
}
.mx_IncomingCallBox_buttons_decline {

View File

@@ -1,5 +1,5 @@
/*
Copyright 2015, 2016 OpenMarket Ltd
Copyright 2015 OpenMarket Ltd
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
@@ -31,15 +31,9 @@ limitations under the License.
}
.mx_VideoView_localVideoFeed {
width: 25%;
height: 25%;
width: 20%;
position: absolute;
left: 10px;
bottom: 10px;
left: 16px;
bottom: 28px;
z-index: 100;
}
.mx_VideoView_localVideoFeed video {
width: auto;
height: 100%;
}

View File

@@ -1,5 +1,5 @@
/*
Copyright 2015, 2016 OpenMarket Ltd
Copyright 2015 OpenMarket Ltd
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
@@ -15,10 +15,10 @@ limitations under the License.
*/
.mx_CreateRoom {
width: 960px;
width: 720px;
margin-left: auto;
margin-right: auto;
color: #4a4a4a;
color: #4a4a4a;
}
.mx_CreateRoom input,
@@ -26,7 +26,7 @@ limitations under the License.
border-radius: 3px;
border: 1px solid #c7c7c7;
font-weight: 300;
font-size: 13px;
font-size: 14px;
padding: 9px;
margin-top: 6px;
}

View File

@@ -1,5 +1,5 @@
/*
Copyright 2015, 2016 OpenMarket Ltd
Copyright 2015 OpenMarket Ltd
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
@@ -34,25 +34,16 @@ limitations under the License.
cursor: pointer;
}
.mx_LeftPanel_callView {
}
.mx_LeftPanel .mx_RoomList_scrollbar {
.mx_LeftPanel .mx_RoomList {
-webkit-box-ordinal-group: 1;
-moz-box-ordinal-group: 1;
-ms-flex-order: 1;
-webkit-order: 1;
order: 1;
overflow-y: auto;
-webkit-flex: 1 1 0;
flex: 1 1 0;
overflow-y: auto;
}
.mx_LeftPanel.collapsed .mx_BottomLeftMenu {
flex: 0 0 120px;
}
.mx_LeftPanel .mx_BottomLeftMenu {
@@ -62,45 +53,17 @@ limitations under the License.
-webkit-order: 3;
order: 3;
border-top: 1px solid rgba(0, 0, 0, 0.1);
margin-left: 16px; /* gutter */
margin-right: 16px; /* gutter */
-webkit-flex: 0 0 60px;
flex: 0 0 60px;
z-index: 1;
-webkit-flex: 0 0 170px;
flex: 0 0 170px;
border-top: 1px solid #f3f8fa;
}
.mx_LeftPanel .mx_BottomLeftMenu_options {
margin-top: 18px;
.mx_LeftPanel .mx_BottomLeftMenu .mx_RoomTile {
color: #378bb4;
}
.mx_BottomLeftMenu_options object {
pointer-events: none;
}
.mx_LeftPanel .mx_BottomLeftMenu_createRoom,
.mx_LeftPanel .mx_BottomLeftMenu_directory,
.mx_LeftPanel .mx_BottomLeftMenu_settings {
display: inline-block;
cursor: pointer;
}
.collapsed .mx_BottomLeftMenu_createRoom,
.collapsed .mx_BottomLeftMenu_directory,
.collapsed .mx_BottomLeftMenu_settings {
margin-left: 0px ! important;
padding-top: 3px ! important;
padding-bottom: 3px ! important;
}
.mx_LeftPanel .mx_BottomLeftMenu_directory {
margin-left: 10px;
}
.mx_LeftPanel .mx_BottomLeftMenu_settings {
float: right;
}
.mx_LeftPanel.collapsed .mx_BottomLeftMenu_settings {
float: none;
}
.mx_LeftPanel .mx_BottomLeftMenu .mx_BottomLeftMenu_options {
margin-top: 12px;
width: 100%;
}

View File

@@ -1,5 +1,5 @@
/*
Copyright 2015, 2016 OpenMarket Ltd
Copyright 2015 OpenMarket Ltd
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
@@ -14,47 +14,47 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
.mx_MatrixToolbar {
background-color: #76cfa6;
color: #fff;
.mx_MemberList {
height: 100%;
margin-bottom: 100px;
padding: 8px;
-webkit-flex: 1;
flex: 1;
display: -webkit-box;
display: -moz-box;
display: -ms-flexbox;
display: -webkit-flex;
display: flex;
-webkit-align-items: center;
align-items: center;
flex-direction: column;
-webkit-flex-direction: column;
}
.mx_MatrixToolbar_warning {
margin-left: 16px;
margin-right: 8px;
margin-top: -2px;
.mx_MemberList_chevron {
position: absolute;
right: 35px;
margin-top: -15px;
}
.mx_MatrixToolbar_content {
-webkit-flex: 1;
flex: 1;
.mx_MemberList_border {
border: 1px solid #a9dbf4;
overflow-y: auto;
border-radius: 8px;
background-color: #fff;
order: 1;
-webkit-flex: 1 1 0;
flex: 1 1 0px;
}
.mx_MatrixToolbar_link
{
color: #fff ! important;
text-decoration: underline ! important;
cursor: pointer;
.mx_MemberList_wrapper {
display: table;
table-layout: fixed;
width: 100%;
}
.mx_MatrixToolbar_close {
cursor: pointer;
}
.mx_MatrixToolbar_close img {
display: block;
float: right;
margin-right: 10px;
}
.mx_MatrixToolbar_action {
margin-right: 16px;
.mx_MemberList h2 {
margin: 14px;
}

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