Compare commits

..

109 Commits

Author SHA1 Message Date
David Baker
966f44baa1 bump js-sdk -> 0.3.0 on the right branch 2015-10-28 18:06:10 +00:00
Kegsay
184af9df76 Merge pull request #25 from matrix-org/246-creating-room-state
Add creatingRoom state to know when to show a spinner.
2015-10-28 11:38:47 +00:00
Kegan Dougal
2a1b9cd716 Add creatingRoom state to know when to show a spinner. 2015-10-27 17:01:03 +00:00
Kegsay
15af44f5fc Merge pull request #24 from matrix-org/linkify
Add callback support for linkified users/aliases
2015-10-27 12:25:20 +00:00
Kegsay
e6f9c6e777 Merge pull request #23 from matrix-org/kegan/reg-errors-176
Add missing enum value to registration password complexity check
2015-10-27 11:11:39 +00:00
Kegan Dougal
16ddb47466 Defer entirely to the end app for handling links 2015-10-27 10:44:41 +00:00
Kegsay
66b577dc89 Merge pull request #22 from matrix-org/kegan/delete-empty-files
Remove empty controllers
2015-10-27 10:19:14 +00:00
Kegan Dougal
77d1b9af04 Hook up aliases via listeners too. 2015-10-27 09:58:55 +00:00
Kegan Dougal
24ac801417 Invoke onUserClick to allow impls to do whatever on user clicks. 2015-10-26 17:59:49 +00:00
Kegan Dougal
11ef1ac336 Prevent the url from being butchered when clicking user IDs 2015-10-26 17:36:03 +00:00
Kegan Dougal
a1444d3214 Linkify room aliases. Add listener for user ID clicks. 2015-10-26 17:32:31 +00:00
Kegsay
a2b77ad5b5 Merge pull request #21 from matrix-org/220-login-error-msgs
Login error messages
2015-10-26 16:58:46 +00:00
David Baker
5a760b71d0 Make ChangeAvatar support room avatars and tweak RoomAvatar respond to componentWillReceiveProps 2015-10-23 17:34:53 +01:00
Matthew Hodgson
03dfd57a79 really kill mime types as they are ugly (3rd time lucky) 2015-10-22 16:32:27 +01:00
David Baker
7e93b75aa0 API Change: better fallback for room avatars 2015-10-22 13:08:35 +01:00
David Baker
549d992293 API change: Make EventTiles which hold stuff common to all events that appear in a room timeline. 2015-10-21 17:50:40 +01:00
Kegan Dougal
ac5111c162 Add missing enum value 2015-10-21 17:44:05 +01:00
Kegan Dougal
0488f03b5a Remove empty controllers 2015-10-21 15:36:59 +01:00
Kegan Dougal
d4a5ab11d4 Fix NPE if you cold boot vector on a URL with a room which you were invited to but not yet joined. 2015-10-21 14:45:39 +01:00
Kegan Dougal
d1af5a2232 More tweaks on error messages 2015-10-21 14:30:59 +01:00
Matthew Hodgson
feaf2a319b Merge pull request #20 from matrix-org/screen-sharing
Screen sharing
2015-10-21 01:35:16 +01:00
Matthew Hodgson
3b988b0eac set up remoteAudioElement 2015-10-21 01:21:39 +01:00
Matthew Hodgson
98ea35253a shift-click the video button to screenshare rather than overriding the button entirely. 2015-10-21 01:21:21 +01:00
Kegan Dougal
8ff7d87b38 Bodge to make video = screen sharing 2015-10-20 16:45:26 +01:00
Kegan Dougal
48f162b9df Better error messages 2015-10-20 14:03:37 +01:00
David Baker
3d8d9bac8e Allow the dispatcher to dispatch sync if required. 2015-10-20 11:02:54 +01:00
David Baker
1041ee654e Update for breaking js-sdk RoomAvatar / MemberAvatar changes 2015-10-20 10:31:29 +01:00
Kegan Dougal
78f2f7cfd0 Add in voip mute video/audio code. Needs dev js-sdk 2015-10-20 09:55:00 +01:00
David Baker
6baf405a05 Remove the 'resending' state which was duplicating a property of the event itself for no obvious reason. Remove onResend whose purpose was mostly to manage that state (and really should have been 'onResendClicked'). Listen for action to see when a message is resent. 2015-10-15 14:09:19 +01:00
David Baker
02a2e06d52 unused code 2015-10-13 16:03:24 +01:00
David Baker
9e596ebb75 Merge pull request #19 from stevenhammerton/sh-cas-auth
Add support for CAS login
2015-10-13 14:25:39 +01:00
David Baker
f7d3d4f9a9 Remove console.log 2015-10-13 11:44:45 +01:00
David Baker
d12ca92ea7 Avoid double updating: setting the state will cause a re-render so forcing an update is redundant. Also bump js sdk dep to newest to match vector. 2015-10-13 11:12:06 +01:00
Steven Hammerton
fc333067c2 Rename required var to match convention 2015-10-12 17:38:04 +01:00
David Baker
030124a59a Make state.members always defined 2015-10-12 16:25:49 +01:00
Steven Hammerton
4e0d930014 Pull down some CAS stuff from vector into controller and logic class 2015-10-12 10:20:03 +01:00
Matthew Hodgson
f6d577d0c6 track RHS collapse state 2015-10-11 16:07:01 +01:00
Matthew Hodgson
8228a7d485 track whether the LHS is collapsed. (shouldn't this be vector specific too?) 2015-10-11 13:49:44 +01:00
Matthew Hodgson
c5e3891a5a shrink default roomavatar size to 36x36. surely this is vector specific... 2015-10-11 02:08:39 +01:00
Steven Hammerton
3f67d8541f Add support for CAS login 2015-10-10 18:54:19 +01:00
David Baker
05d19121d8 Slightly change memberlist api to shift the sorting into the right place (in the skin). 2015-10-09 17:24:48 +01:00
David Baker
e158eec94d Unset matrix client first otherwise login sets it, the it gets unset and you can't log in again. 2015-10-09 13:48:17 +01:00
David Baker
53a7f4b3a8 Set state to ready only if the SDK is synced. 2015-10-09 12:05:40 +01:00
David Baker
0791cac572 Add method to Modal to create dialog with instantiated React elements as well as Classes. 2015-10-09 11:54:46 +01:00
David Baker
05f7a3b4d1 Remove now redundant displayname stuff from UserSettings now it's in ChangeDisplayName 2015-10-07 18:44:32 +01:00
David Baker
79e468217a Add button for user settings and a change display name widget 2015-10-07 18:19:29 +01:00
David Baker
27ca7b48f7 Just do all dispatches async: setting the flag obviously does not work for more than 2 nested dispatches. 2015-10-05 18:43:22 +01:00
David Baker
b8dd2452db Display correct message for when an invited but not joined user is kicked. 2015-10-05 16:44:50 +01:00
David Baker
a1892ee963 Improve url / screen handling
Including taking you to the room you asked for originally after login
2015-10-05 15:31:08 +01:00
David Baker
ad313d7711 Merge branch 'vector-merge' 2015-10-02 18:55:53 +01:00
David Baker
55f656f150 0.0.2 2015-10-02 18:55:09 +01:00
David Baker
711272a7c9 Merge pull request #18 from matrix-org/vector-merge
Vector merge
2015-10-02 18:50:52 +01:00
David Baker
2d3b87d56d Don't set empty mime types on metadata. 2015-10-02 18:37:15 +01:00
David Baker
2bce4e4d62 Merge c62d97ca04 from vector-im
The hack is *evil*. Not dirty.
2015-10-02 14:37:34 +01:00
David Baker
54048ee37c Update Readme 2015-10-01 16:02:21 +01:00
David Baker
7de136a930 Port over new logic for filtering actions: makes the end call button appear & disappear approriately 2015-10-01 10:19:18 +01:00
David Baker
5004a3a5b3 Make end call button work for conf calls 2015-10-01 09:42:58 +01:00
David Baker
cb89d3760a Hacks to make sure we don't end up with multiple split-brain CallHandlers when npm linked. 2015-09-30 18:21:25 +01:00
David Baker
b68665ead5 Add support for the basic notion of conference calls and an experimental concept of modules to provide the actual functionality. Rejig Skinner to be simpler. 2015-09-30 16:50:46 +01:00
David Baker
9fb5702c2f make MatrixClientPeg an actual global too otherwise things go very wierd 2015-09-28 17:46:49 +01:00
David Baker
8af6c2275b Make it easier to override default avatar urls 2015-09-28 17:06:13 +01:00
David Baker
3792d5494a Dispatcher should be a global too 2015-09-28 14:48:50 +01:00
David Baker
3be50e327d Manually merge memberlist fix from vector master 2015-09-28 11:32:00 +01:00
David Baker
4472f63b5f We need js sdk 0.2.1 for getRoomIdForAlias 2015-09-25 17:49:35 +01:00
David Baker
6348c2cf99 Change how viewing a room alias works to make way for jumping into a room once you've logged in. 2015-09-25 17:22:42 +01:00
David Baker
bc2eca16f9 Unused guff 2015-09-25 16:25:20 +01:00
David Baker
fe369858b7 Unused variables & redundant stuff 2015-09-25 15:17:46 +01:00
David Baker
56530d80d7 Add link to guthub issue about webpack's loader path silliness 2015-09-25 13:53:58 +01:00
David Baker
d172aaf41f Add -loader packages as deps
Even though we don't use webpack directly, webpack needs the loaders
to be in the dependency package's node_modules directory, so this
lets packages that depend on us use webpack.
2015-09-25 13:39:21 +01:00
David Baker
5af43dc6a9 Remove unused action dispatch 2015-09-23 09:39:49 +01:00
David Baker
96627d4477 Port membertile fix 2015-09-22 16:51:16 +01:00
David Baker
3838569625 Port memberlist branch fixes 2015-09-22 16:37:39 +01:00
David Baker
b32658cfd0 Load fewer events when switching to a room: they take surprisingly long to render. 2015-09-22 15:18:16 +01:00
David Baker
980ce7fdae Remainder of the controllers from vector 2015-09-21 17:23:51 +01:00
David Baker
49c5f7cb95 Use same protocol as client was loaded over for recaptcha to prevent JS origin errors. 2015-09-21 16:53:50 +01:00
David Baker
1b82d92fa1 Port registration fixes 2015-09-21 16:38:12 +01:00
David Baker
65498600de port login fixes 2015-09-21 16:36:17 +01:00
David Baker
28c4a648be Port roomlist fixes 2015-09-21 16:31:31 +01:00
David Baker
e2c9afb278 port create room 2015-09-21 16:28:39 +01:00
David Baker
29d2ed7191 Merge userselector changes 2015-09-21 16:17:29 +01:00
David Baker
82aa603596 Merge in resend support 2015-09-21 16:14:19 +01:00
David Baker
a8eb93bd6f Member list 2015-09-18 18:39:16 +01:00
David Baker
31ee667102 Room header 2015-09-18 14:34:36 +01:00
David Baker
b9538a077c Missed files from last commit 2015-09-18 13:54:20 +01:00
David Baker
343de6245f Port ServerConfig changes 2015-09-18 13:33:51 +01:00
David Baker
08b5888d03 More porting: make sending messages work again! 2015-09-18 10:44:57 +01:00
David Baker
abeed92501 Partial porting over of vector controller logic to react sdk. 2015-09-17 18:23:38 +01:00
David Baker
6eb18f0268 Fix reskindex 2015-09-17 18:21:35 +01:00
David Baker
d938ba70d3 Port over room leaving 2015-09-17 12:10:01 +01:00
David Baker
88aaf82c88 Backport: linkify emotes 2015-09-16 16:23:35 +01:00
David Baker
f3b30477ce Backport: add other presets into room presets 2015-09-16 16:10:39 +01:00
David Baker
a4cbbf0d92 Backport Notifier improvements from Vector, including TextForEvent 2015-09-16 14:48:49 +01:00
David Baker
25ab56106a Backport labels & placeholders for Editable text from Vector 2015-09-16 14:18:25 +01:00
David Baker
6cca5f4c05 backport fixes from vector 2015-09-16 13:48:24 +01:00
David Baker
aba4c1e9af We don't use catw here anymore 2015-09-15 14:46:41 +01:00
David Baker
2d0c8ac9ff Working skin stuff, minus css 2015-09-15 13:34:36 +01:00
David Baker
f3b9f8c799 WIP reworking of skinning and app integration process 2015-09-11 15:42:11 +01:00
David Baker
9b73d6ed6d react-tools is dead. Long live Babel. 2015-09-10 15:28:30 +01:00
David Baker
a06e1f23ea Spurious react-loader require 2015-09-10 15:08:26 +01:00
David Baker
635041470f There is no MatrixChat in the sdk anymore (well, only a controller). Advertise the Component Broker instead. 2015-09-10 15:07:34 +01:00
David Baker
a124e53a9a Component broker loading files never really worked very well anyway, and now it can't work because they're all defined elsewhere. 2015-09-10 15:06:42 +01:00
David Baker
6cc88e4ef3 Remove stuff that's going into the base skin 2015-09-09 16:57:55 +01:00
David Baker
e1a6ede17b Ignore all module caches and make the built js the default include root 2015-09-08 18:45:00 +01:00
David Baker
8fbce5fce8 de-tab 2015-09-08 16:17:06 +01:00
David Baker
2d9419c380 Merge pull request #17 from ndarilek/master
Make HTML more semantic to improve accessibility
2015-08-10 13:45:36 +01:00
Nolan Darilek
bd0db01515 Add landmarks around major view regions for easier navigation. 2015-08-08 18:09:37 -05:00
Nolan Darilek
517bb01f33 Event tiles are now items in a list, improves accessibility. 2015-08-08 17:36:19 -05:00
David Baker
ec8a815688 Ignore olm. There is no olm. 2015-08-07 11:51:13 +01:00
David Baker
5a87b9759f update to new js-sdk 2015-08-07 11:27:13 +01:00
453 changed files with 6002 additions and 18583 deletions

View File

@@ -1,4 +0,0 @@
{
"presets": ["react", "es2015", "es2016"],
"plugins": ["transform-class-properties", "transform-object-rest-spread", "transform-async-to-generator", "transform-runtime", "add-module-exports"]
}

16
.gitignore vendored
View File

@@ -1,14 +1,2 @@
/build
/cert.pem
/dist
/karma-reports
/key.pem
/lib
/node_modules
/packages/
/webapp
/.npmrc
.DS_Store
npm-debug.log
electron/dist
electron/pub
node_modules
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

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

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

@@ -1,12 +0,0 @@
Vector is written mainly by the Vector team, building upon the Matrix React
SDK. Vector also welcomes external contributions. Third party contributors
include:
* Nolan Darilek (https://github.com/ndarilek)
Accessibility and semantic markup contributions
* 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,602 +0,0 @@
Changes in [0.9.2](https://github.com/vector-im/riot-web/releases/tag/v0.9.2) (2016-12-16)
==========================================================================================
[Full Changelog](https://github.com/vector-im/riot-web/compare/v0.9.1...v0.9.2)
* Remove the client side filtering from the room dir
[\#2750](https://github.com/vector-im/riot-web/pull/2750)
* Configure olm memory size
[\#2745](https://github.com/vector-im/riot-web/pull/2745)
* Support room dir 3rd party network filtering
[\#2747](https://github.com/vector-im/riot-web/pull/2747)
Changes in [0.9.1](https://github.com/vector-im/riot-web/releases/tag/v0.9.1) (2016-12-09)
==========================================================================================
[Full Changelog](https://github.com/vector-im/riot-web/compare/v0.9.1-rc.2...v0.9.1)
* Update README to say how to build the desktop app
[\#2732](https://github.com/vector-im/riot-web/pull/2732)
* Add signing ID in release_config.yaml
[\#2731](https://github.com/vector-im/riot-web/pull/2731)
* Makeover!
[\#2722](https://github.com/vector-im/riot-web/pull/2722)
* Fix broken tests
[\#2730](https://github.com/vector-im/riot-web/pull/2730)
* Make the 'loading' tests work in isolation
[\#2727](https://github.com/vector-im/riot-web/pull/2727)
* Use a PNG for the icon on non-Windows
[\#2708](https://github.com/vector-im/riot-web/pull/2708)
* Add missing brackets to call to toUpperCase
[\#2703](https://github.com/vector-im/riot-web/pull/2703)
Changes in [0.9.1-rc.2](https://github.com/vector-im/riot-web/releases/tag/v0.9.1-rc.2) (2016-12-06)
====================================================================================================
[Full Changelog](https://github.com/vector-im/riot-web/compare/v0.9.1-rc.1...v0.9.1-rc.2)
* Fix clicking on notifications
[\#2700](https://github.com/vector-im/riot-web/pull/2700)
* Desktop app: Only show window when ready
[\#2697](https://github.com/vector-im/riot-web/pull/2697)
Changes in [0.9.1-rc.1](https://github.com/vector-im/riot-web/releases/tag/v0.9.1-rc.1) (2016-12-05)
====================================================================================================
[Full Changelog](https://github.com/vector-im/riot-web/compare/v0.9.0...v0.9.1-rc.1)
* Final bits to prepare electron distribtion:
[\#2653](https://github.com/vector-im/riot-web/pull/2653)
* Update name & repo to reflect renamed repository
[\#2692](https://github.com/vector-im/riot-web/pull/2692)
* Document cross_origin_renderer_url
[\#2680](https://github.com/vector-im/riot-web/pull/2680)
* Add css for the iframes for e2e attachments
[\#2659](https://github.com/vector-im/riot-web/pull/2659)
* Fix config location in some more places
[\#2670](https://github.com/vector-im/riot-web/pull/2670)
* CSS updates for s/block/blacklist for e2e
[\#2662](https://github.com/vector-im/riot-web/pull/2662)
* Update to electron 1.4.8
[\#2660](https://github.com/vector-im/riot-web/pull/2660)
* Add electron config
[\#2644](https://github.com/vector-im/riot-web/pull/2644)
* Move getDefaultDeviceName into the Platforms
[\#2643](https://github.com/vector-im/riot-web/pull/2643)
* Add Freenode & Mozilla domains
[\#2641](https://github.com/vector-im/riot-web/pull/2641)
* Include config.sample.json in dist tarball
[\#2614](https://github.com/vector-im/riot-web/pull/2614)
Changes in [0.9.0](https://github.com/vector-im/vector-web/releases/tag/v0.9.0) (2016-11-19)
============================================================================================
[Full Changelog](https://github.com/vector-im/vector-web/compare/v0.8.4...v0.9.0)
* Add a cachebuster to /version
[\#2596](https://github.com/vector-im/vector-web/pull/2596)
* Add a 'View decrypted source' button
[\#2587](https://github.com/vector-im/vector-web/pull/2587)
* Fix changelog dialog to read new version format
[\#2577](https://github.com/vector-im/vector-web/pull/2577)
* Build all of the vector dir in the build process
[\#2558](https://github.com/vector-im/vector-web/pull/2558)
* Support for get_app_version
[\#2553](https://github.com/vector-im/vector-web/pull/2553)
* Add CSS for mlist truncation
[\#2565](https://github.com/vector-im/vector-web/pull/2565)
* Add menu option for `external_url` if present
[\#2560](https://github.com/vector-im/vector-web/pull/2560)
* Make auto-update configureable
[\#2555](https://github.com/vector-im/vector-web/pull/2555)
* Missed files electron windows fixes
[\#2556](https://github.com/vector-im/vector-web/pull/2556)
* Add some CSS for scalar error popup
[\#2554](https://github.com/vector-im/vector-web/pull/2554)
* Catch unhandled errors in the electron process
[\#2552](https://github.com/vector-im/vector-web/pull/2552)
* Slight grab-bag of fixes for electron on Windows
[\#2551](https://github.com/vector-im/vector-web/pull/2551)
* Electron app (take 3)
[\#2535](https://github.com/vector-im/vector-web/pull/2535)
* Use webpack-dev-server instead of http-server
[\#2542](https://github.com/vector-im/vector-web/pull/2542)
* Better support no-config when loading from file
[\#2541](https://github.com/vector-im/vector-web/pull/2541)
* Fix loading with no config from HTTP
[\#2540](https://github.com/vector-im/vector-web/pull/2540)
* Move 'new version' support into Platform
[\#2532](https://github.com/vector-im/vector-web/pull/2532)
* Add Notification support to the Web Platform
[\#2533](https://github.com/vector-im/vector-web/pull/2533)
* Use the defaults if given a blank config file
[\#2534](https://github.com/vector-im/vector-web/pull/2534)
* Implement Platforms
[\#2531](https://github.com/vector-im/vector-web/pull/2531)
hanges in [0.8.4](https://github.com/vector-im/vector-web/releases/tag/v0.8.4) (2016-11-04)
============================================================================================
[Full Changelog](https://github.com/vector-im/vector-web/compare/v0.8.4-rc.2...v0.8.4)
* No changes
Changes in [0.8.4-rc.2](https://github.com/vector-im/vector-web/releases/tag/v0.8.4-rc.2) (2016-11-02)
======================================================================================================
[Full Changelog](https://github.com/vector-im/vector-web/compare/v0.8.4-rc.1...v0.8.4-rc.2)
* Fix the version in the generated distribution package
Changes in [0.8.4-rc.1](https://github.com/vector-im/vector-web/releases/tag/v0.8.4-rc.1) (2016-11-02)
======================================================================================================
[Full Changelog](https://github.com/vector-im/vector-web/compare/v0.8.3...v0.8.4-rc.1)
Breaking Changes
----------------
* End-to-end encryption now requires one-time keys to be
signed, so end-to-end encryption will not interoperate
with previous releases of vector-web. End-to-end encryption
remains in beta.
Other Changes
-------------
* Rename the package script/output dir to 'dist'
[\#2528](https://github.com/vector-im/vector-web/pull/2528)
* Avoid errors if olm is missing
[\#2518](https://github.com/vector-im/vector-web/pull/2518)
* Put a cachebuster in the names of CSS and JS files
[\#2515](https://github.com/vector-im/vector-web/pull/2515)
* Bump to olm 2.0.0
[\#2517](https://github.com/vector-im/vector-web/pull/2517)
* Don't include the world in the published packages
[\#2516](https://github.com/vector-im/vector-web/pull/2516)
* Use webpack to copy olm.js
[\#2514](https://github.com/vector-im/vector-web/pull/2514)
* Don't include two copies of the CSS in the tarball
[\#2513](https://github.com/vector-im/vector-web/pull/2513)
* Correct text alignment on room directory search
[\#2512](https://github.com/vector-im/vector-web/pull/2512)
* Correct spelling of 'rel'
[\#2510](https://github.com/vector-im/vector-web/pull/2510)
* readme tweaks
[\#2507](https://github.com/vector-im/vector-web/pull/2507)
* s/vector/riot/ in the readme
[\#2491](https://github.com/vector-im/vector-web/pull/2491)
* Switch to babel 6, again
[\#2480](https://github.com/vector-im/vector-web/pull/2480)
* Revert "Switch to babel 6"
[\#2472](https://github.com/vector-im/vector-web/pull/2472)
* Switch to babel 6
[\#2461](https://github.com/vector-im/vector-web/pull/2461)
Changes in [0.8.3](https://github.com/vector-im/vector-web/releases/tag/v0.8.3) (2016-10-12)
============================================================================================
[Full Changelog](https://github.com/vector-im/vector-web/compare/v0.8.2...v0.8.3)
* Centre images in dialog buttons
[\#2453](https://github.com/vector-im/vector-web/pull/2453)
* Only show quote option if RTE is enabled
[\#2448](https://github.com/vector-im/vector-web/pull/2448)
* Fix join button for 'matrix' networks
[\#2443](https://github.com/vector-im/vector-web/pull/2443)
* Don't stop paginating if no rooms match
[\#2422](https://github.com/vector-im/vector-web/pull/2422)
Changes in [0.8.2](https://github.com/vector-im/vector-web/releases/tag/v0.8.2) (2016-10-05)
============================================================================================
[Full Changelog](https://github.com/vector-im/vector-web/compare/v0.8.1...v0.8.2)
* Add native joining of 3p networks to room dir
[\#2379](https://github.com/vector-im/vector-web/pull/2379)
* Update to linkify 2.1.3
[\#2406](https://github.com/vector-im/vector-web/pull/2406)
* Use 'Sign In' / 'Sign Out' universally
[\#2383](https://github.com/vector-im/vector-web/pull/2383)
* Prevent network dropdown resizing slightly
[\#2382](https://github.com/vector-im/vector-web/pull/2382)
* Room directory: indicate when there are no results
[\#2380](https://github.com/vector-im/vector-web/pull/2380)
* Room dir: New filtering & 3rd party networks
[\#2362](https://github.com/vector-im/vector-web/pull/2362)
* Update linkify version
[\#2359](https://github.com/vector-im/vector-web/pull/2359)
* Directory search join button
[\#2339](https://github.com/vector-im/vector-web/pull/2339)
Changes in [0.8.1](https://github.com/vector-im/vector-web/releases/tag/v0.8.1) (2016-09-21)
============================================================================================
[Full Changelog](https://github.com/vector-im/vector-web/compare/v0.8.0...v0.8.1)
Changes in [0.8.0](https://github.com/vector-im/vector-web/releases/tag/v0.8.0) (2016-09-21)
============================================================================================
[Full Changelog](https://github.com/vector-im/vector-web/compare/v0.7.5-r3...v0.8.0)
* Dbkr/rebrand
[\#2285](https://github.com/vector-im/vector-web/pull/2285)
* Listen for close_scalar and close the dialog box when received
[\#2282](https://github.com/vector-im/vector-web/pull/2282)
* Revert "improve lipstick and support scalar logout"
[\#2281](https://github.com/vector-im/vector-web/pull/2281)
* improve lipstick and support scalar logout
[\#2280](https://github.com/vector-im/vector-web/pull/2280)
* Fix changelog links
[\#2071](https://github.com/vector-im/vector-web/pull/2071)
* Paginate Room Directory
[\#2241](https://github.com/vector-im/vector-web/pull/2241)
* Make redeploy script symlink config
[\#2240](https://github.com/vector-im/vector-web/pull/2240)
* Update the version of olm to 1.3.0
[\#2210](https://github.com/vector-im/vector-web/pull/2210)
* Directory network selector
[\#2219](https://github.com/vector-im/vector-web/pull/2219)
* Wmwragg/two state sublist headers
[\#2235](https://github.com/vector-im/vector-web/pull/2235)
* Wmwragg/correct incoming call positioning
[\#2222](https://github.com/vector-im/vector-web/pull/2222)
* Wmwragg/remove old filter
[\#2211](https://github.com/vector-im/vector-web/pull/2211)
* Wmwragg/multi invite bugfix
[\#2198](https://github.com/vector-im/vector-web/pull/2198)
* Wmwragg/chat multi invite
[\#2181](https://github.com/vector-im/vector-web/pull/2181)
* shuffle bottomleftmenu around a bit
[\#2182](https://github.com/vector-im/vector-web/pull/2182)
* Improve autocomplete behaviour (styling)
[\#2175](https://github.com/vector-im/vector-web/pull/2175)
* First wave of E2E visuals
[\#2163](https://github.com/vector-im/vector-web/pull/2163)
* FilePanel and NotificationPanel support
[\#2113](https://github.com/vector-im/vector-web/pull/2113)
* Cursor: pointer on member info create room button
[\#2151](https://github.com/vector-im/vector-web/pull/2151)
* Support for adding DM rooms to the MemberInfo Panel
[\#2147](https://github.com/vector-im/vector-web/pull/2147)
* Wmwragg/one to one indicators
[\#2139](https://github.com/vector-im/vector-web/pull/2139)
* Added back the Directory listing button, with new tootlip
[\#2136](https://github.com/vector-im/vector-web/pull/2136)
* wmwragg/chat invite dialog fix
[\#2134](https://github.com/vector-im/vector-web/pull/2134)
* Wmwragg/one to one chat
[\#2110](https://github.com/vector-im/vector-web/pull/2110)
* Support toggling DM status of rooms
[\#2111](https://github.com/vector-im/vector-web/pull/2111)
* Formatting toolbar for RTE message composer.
[\#2082](https://github.com/vector-im/vector-web/pull/2082)
* jenkins.sh: install olm from jenkins artifacts
[\#2092](https://github.com/vector-im/vector-web/pull/2092)
* e2e device CSS
[\#2085](https://github.com/vector-im/vector-web/pull/2085)
* Bump to olm 1.1.0
[\#2069](https://github.com/vector-im/vector-web/pull/2069)
* Improve readability of the changelog dialog
[\#2056](https://github.com/vector-im/vector-web/pull/2056)
* Turn react consistency checks back on in develop builds
[\#2009](https://github.com/vector-im/vector-web/pull/2009)
* Wmwragg/direct chat sublist
[\#2028](https://github.com/vector-im/vector-web/pull/2028)
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

View File

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

352
README.md
View File

@@ -1,268 +1,142 @@
Riot
====
matrix-react-sdk
================
Riot (formerly known as Vector) is a Matrix web client built using the Matrix
React SDK (https://github.com/matrix-org/matrix-react-sdk).
This is a react-based SDK for inserting a Matrix chat/voip client into a web page.
Getting Started
===============
This package provides the logic and 'controller' parts for the UI components. This
forms one part of a complete matrix client, but it not useable in isolation. It
must be used from a 'skin'. A skin provides:
* The HTML for the UI components (in the form of React `render` methods)
* The CSS for this HTML
* The containing application
* Zero or more 'modules' containing non-UI functionality
The easiest way to test Riot is to just use the hosted copy at
https://riot.im/app. The develop branch is continuously deployed by Jenkins at
https://riot.im/develop for those who like living dangerously.
Skins are modules are exported from such a package in the `lib` directory.
`lib/skins` contains one directory per-skin, named after the skin, and the
`modules` directory contains modules as their javascript files.
To host your own copy of Riot, the quickest bet is to use a pre-built
released version of Riot:
A basic skin is provided in the matrix-react-skin package. This also contains
a minimal application that instantiates the basic skin making a working matrix
client.
1. Download the latest version from https://github.com/vector-im/vector-web/releases
1. Untar the tarball on your web server
1. Move (or symlink) the vector-x.x.x directory to an appropriate name
1. If desired, copy `config.sample.json` to `config.json` and edit it
as desired. See below for details.
1. Enter the URL into your browser and log into Riot!
You can use matrix-react-sdk directly, but to do this you would have to provide
'views' for each UI component. To get started quickly, use matrix-react-skin.
Note that Chrome does not allow microphone or webcam access for sites served
over http (except localhost), so for working VoIP you will need to serve Riot
over https.
Important Security Note
=======================
We do not recommend running Riot from the same domain name as your Matrix
homeserver. The reason is the risk of XSS (cross-site-scripting)
vulnerabilities that could occur if someone caused Riot to load and render
malicious user generated content from a Matrix API which then had trusted
access to Riot (or other apps) due to sharing the same domain.
We have put some coarse mitigations into place to try to protect against this
situation, but it's still not good practice to do it in the first place. See
https://github.com/vector-im/vector-web/issues/1977 for more details.
Building From Source
====================
Riot is a modular webapp built with modern ES6 and requires a npm build system
to build.
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-web directory: `cd vector-web`
1. Install the prerequisites: `npm install`
1. If you are using the `develop` branch of vector-web, you will probably need
to rebuild 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 dist` to build a tarball to deploy. Untaring this file will give
a version-specific directory containing all the files that need to go on your
web server.
Note that `npm run dist` is not supported on Windows, so Windows users can run `npm
run build`, which will build all the necessary files into the `webapp`
directory. The version of Riot will not appear in Settings without
using the dist script. You can then mount the `webapp` directory on your
webserver to actually serve up the app, which is entirely static content.
config.json
===========
You can configure the app by copying `config.sample.json` to
`config.json` and customising it:
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.
1. `integrations_ui_url`: URL to the web interface for the integrations server.
1. `integrations_rest_url`: URL to the REST interface for the integrations server.
1. `roomDirectory`: config for the public room directory. This section is optional.
1. `roomDirectory.servers`: List of other Home Servers' directories to include in the drop
down list. Optional.
1. `update_base_url` (electron app only): HTTPS URL to a web server to download
updates from. This should be the path to the directory containing `macos`
and `win32` (for update packages, not installer packages).
1. `cross_origin_renderer_url`: URL to a static HTML page hosting code to help display
encrypted file attachments. This MUST be hosted on a completely separate domain to
anything else since it is used to isolate the privileges of file attachments to this
domain. Default: `usercontent.riot.im`. This needs to contain v1.html from
https://github.com/matrix-org/usercontent/blob/master/v1.html
Running as a Desktop app
How to customise the SDK
========================
Riot can also be run as a desktop app, wrapped in electron. You can download a
pre-built version from https://riot.im/download/desktop/ or, if you prefer,
built it yourself.
The SDK uses the 'atomic' design pattern as seen at http://patternlab.io to
encourage a very modular and reusable architecture, making it easy to
customise and use UI widgets independently of the rest of the SDK and your app.
In practice this means:
To run as a desktop app:
```
npm install
npm install electron
node_modules/.bin/electron .
```
* The UI of the app is strictly split up into a hierarchy of components.
* Each component has its own:
* View object defined as a React javascript class containing embedded
HTML expressed in React's JSX notation.
* CSS file, which defines the styling specific to that component.
* Components are loosely grouped into the 5 levels outlined by atomic design:
* atoms: fundamental building blocks (e.g. a timestamp tag)
* molecules: "group of atoms which functions together as a unit"
(e.g. a message in a chat timeline)
* organisms: "groups of molecules (and atoms) which form a distinct section
of a UI" (e.g. a view of a chat room)
* templates: "a reusable configuration of organisms" - used to combine and
style organisms into a well-defined global look and feel
* pages: specific instances of templates.
To build packages, use electron-builder. This is configured to output:
* dmg + zip for macOS
* exe + nupkg for Windows
* deb for Linux
But this can be customised by editing the `build` section of package.json
as per https://github.com/electron-userland/electron-builder/wiki/Options
Good separation between the components is maintained by adopting various best
practices that anyone working with the SDK needs to be be aware of and uphold:
See https://github.com/electron-userland/electron-builder/wiki/Multi-Platform-Build
for dependencies required for building packages for various platforms.
* Views are named with upper camel case (e.g. molecules/MessageTile.js)
The only platform that can build packages for all three platforms is macOS:
```
brew install wine --without-x11
brew install mono
npm install
npm run build:electron
```
* The view's CSS file MUST have the same name (e.g. molecules/MessageTile.css)
For other packages, use electron-builder manually. For example, to build a package
for 64 bit Linux:
```
npm install
npm run build
node_modules/.bin/build -l --x64
```
* Per-view CSS is optional - it could choose to inherit all its styling from
the context of the rest of the app, although this is unusual for any but
the simplest atoms and molecules.
All electron packages go into `electron/dist/`
* The view MUST *only* refer to the CSS rules defined in its own CSS file.
'Stealing' styling information from other components (including parents)
is not cool, as it breaks the independence of the components.
Many thanks to @aviraldg for the initial work on the electron integration.
* CSS classes are named with an app-specific namespacing prefix to try to avoid
CSS collisions. The base skin shipped by Matrix.org with the matrix-react-sdk
uses the naming prefix "mx_". A company called Yoyodyne Inc might use a
prefix like "yy_" for its app-specific classes.
Other options for running as a desktop app:
* https://github.com/krisak/vector-electron-desktop
* @asdf:matrix.org points out that you can use nativefier and it just works(tm)
```
sudo npm install nativefier -g
nativefier https://riot.im/app/
```
* CSS classes use upper camel case when they describe React components - e.g.
.mx_MessageTile is the selector for the CSS applied to a MessageTile view.
Development
===========
* CSS classes for DOM elements within a view which aren't components are named
by appending a lower camel case identifier to the view's class name - e.g.
.mx_MessageTile_randomDiv is how you'd name the class of an arbitrary div
within the MessageTile view.
Before attempting to develop on Riot you **must** read the developer guide
for `matrix-react-sdk` at https://github.com/matrix-org/matrix-react-sdk, which
also defines the design, architecture and style for Riot too.
* We deliberately use vanilla CSS 3.0 to avoid adding any more magic
dependencies into the mix than we already have. App developers are welcome
to use whatever floats their boat however.
The idea of Riot 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.
* The CSS for a component can however override the rules for child components.
For instance, .mx_RoomList .mx_RoomTile {} would be the selector to override
styles of RoomTiles when viewed in the context of a RoomList view.
Overrides *must* be scoped to the View's CSS class - i.e. don't just define
.mx_RoomTile {} in RoomList.css - only RoomTile.css is allowed to define its
own CSS. Instead, say .mx_RoomList .mx_RoomTile {} to scope the override
only to the context of RoomList views. N.B. overrides should be relatively
rare as in general CSS inheritence should be enough.
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)
* Components should render only within the bounding box of their outermost DOM
element. Page-absolute positioning and negative CSS margins and similar are
generally not cool and stop the component from being reused easily in
different places.
**However, as of July 2016 this layering abstraction is broken due to rapid
development on Riot forcing `matrix-react-sdk` to move fast at the expense of
maintaining a clear abstraction between the two.** Hacking on Riot inevitably
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 Riot specific behaviour
in the `matrix-react-sdk` (grep for `vector` / `riot`). This separation problem will be
solved asap once development on Riot (and thus matrix-react-sdk) has
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.
* We don't use the atomify library itself, as React already provides most
of the modularity requirements it brings to the table.
Please note that Riot is intended to run correctly without access to the public
internet. So please don't depend on resources (JS libs, CSS, images, fonts)
hosted by external CDNs or servers but instead please package all dependencies
into Riot itself.
With all this in mind, here's how you go about skinning the react SDK UI
components to embed a Matrix client into your app:
Setting up a dev environment
============================
* Create a new NPM project. Be sure to directly depend on react, (otherwise
you can end up with two copies of react).
* Create an index.js file that sets up react. Add require statements for
React and matrix-react-sdk. Load a skin using the 'loadSkin' method on the
SDK and call Render. This can be a skin provided by a separate package or
a skin in the same package.
* Add a way to build your project: we suggest copying the scripts block
from matrix-react-skin (which uses babel and webpack). You could use
different tools but remember that at least the skins and modules of
your project should end up in plain (ie. non ES6, non JSX) javascript in
the lib directory at the end of the build process, as well as any
packaging that you might do.
* Create an index.html file pulling in your compiled javascript and the
CSS bundle from the skin you use. For now, you'll also need to manually
import CSS from any skins that your skin inherts from.
Much of the functionality in Riot 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.
To Create Your Own Skin
=======================
To actually change the look of a skin, you can create a base skin (which
does not use views from any other skin) or you can make a derived skin.
Note that derived skins are currently experimental: for example, the CSS
from the skins it is based on will not be automatically included.
First clone and build `matrix-js-sdk`:
To make a skin, create React classes for any custom components you wish to add
in a skin within `src/skins/<skin name>`. These can be based off the files in
`views` in the `matrix-react-skin` package, modifying the require() statement
appropriately.
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`
If you make a derived skin, you only need copy the files you wish to customise.
Then similarly with `matrix-react-sdk`:
Once you've made all your view files, you need to make a `skinfo.json`. This
contains all the metadata for a skin. This is a JSON file with, currently, a
single key, 'baseSkin'. Set this to the empty string if your skin is a base skin,
or for a derived skin, set it to the path of your base skin's skinfo.json file, as
you would use in a require call.
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`
Now you have the basis of a skin, you need to generate a skindex.json file. The
`reskindex.js` tool in matrix-react-sdk does this for you. It is suggested that
you add an npm script to run this, as in matrix-react-skin.
Finally, build and start Riot 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 Riot.
When you make changes to `matrix-react-sdk`, you will need to run `npm run
build` in the relevant directory. You can do this automatically by instead
running `npm start` in the directory, to start a development builder which
will watch for changes to the files and rebuild automatically.
If you add or remove any components from the Riot skin, you will need to rebuild
the skin's index by running, `npm run reskindex`.
If any of these steps error with, `file table overflow`, you are probably on a mac
which has a very low limit on max open files. Run `ulimit -Sn 1024` and try again.
You'll need to do this in each new terminal you open before building Riot.
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)
For more specific detail on any of these steps, look at matrix-react-skin.

View File

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

View File

@@ -1,52 +0,0 @@
# VoIP Conferencing
This is a draft proposal for a naive voice/video conferencing implementation for
Matrix clients. There are many possible conferencing architectures possible for
Matrix (Multipoint Conferencing Unit (MCU); Stream Forwarding Unit (SFU); Peer-
to-Peer mesh (P2P), etc; events shared in the group room; events shared 1:1;
possibly even out-of-band signalling).
This is a starting point for a naive MCU implementation which could provide one
possible Matrix-wide solution in future, which retains backwards compatibility
with standard 1:1 calling.
* A client chooses to initiate a conference for a given room by starting a
voice or video call with a 'conference focus' user. This is a virtual user
(typically Application Service) which implements a conferencing bridge. It
isn't defined how the client discovers or selects this user.
* The conference focus user MUST join the room in which the client has
initiated the conference - this may require the client to invite the
conference focus user to the room, depending on the room's `join_rules`. The
conference focus user needs to be in the room to let the bridge eject users
from the conference who have left the room in which it was initiated, and aid
discovery of the conference by other users in the room. The bridge
identifies the room to join based on the user ID by which it was invited.
The format of this identifier is implementation dependent for now.
* If a client leaves the group chat room, they MUST be ejected from the
conference. If a client leaves the 1:1 room with the conference focus user,
they SHOULD be ejected from the conference.
* For now, rooms can contain multiple conference focus users - it's left to
user or client implementation to select which to converge on. In future this
could be mediated using a state event (e.g. `im.vector.call.mcu`), but we
can't do that right now as by default normal users can't set arbitrary state
events on a room.
* To participate in the conference, other clients initiates a standard 1:1
voice or video call to the conference focus user.
* For best UX, clients SHOULD show the ongoing voice/video call in the UI
context of the group room rather than 1:1 with the focus user. If a client
recognises a conference user present in the room, it MAY chose to highlight
this in the UI (e.g. with a "conference ongoing" notification, to aid
discovery). Clients MAY hide the 1:1 room with the focus user (although in
future this room could be used for floor control or other direct
communication with the conference focus)
* When all users have left the conference, the 'conference focus' user SHOULD
leave the room.
* If a conference focus user joins a room but does not receive a 1:1 voice or
video call, it SHOULD time out after a period of time and leave the room.

Binary file not shown.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 102 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 673 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 14 KiB

View File

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

View File

@@ -1,72 +0,0 @@
{
"update_base_url": "https://riot.im/download/desktop/update/",
"default_hs_url": "https://matrix.org",
"default_is_url": "https://vector.im",
"brand": "Riot",
"integrations_ui_url": "https://scalar.vector.im/",
"integrations_rest_url": "https://scalar.vector.im/api",
"enableLabs": true,
"roomDirectory": {
"servers": [
"matrix.org"
],
"serverConfig": {
"matrix.org": {
"networks": [
"_matrix",
"gitter",
"irc:freenode",
"irc:mozilla",
"irc:snoonet",
"irc:oftc"
]
}
},
"networks": {
"gitter": {
"protocol": "gitter",
"portalRoomPattern": "#gitter_.*:matrix.org",
"name": "Gitter",
"icon": "https://gitter.im/favicon.ico",
"example": "org/community",
"nativePattern": "[^\\s]+/[^\\s]+$"
},
"irc:freenode": {
"protocol": "irc",
"domain": "chat.freenode.net",
"portalRoomPattern": "#freenode_.*:matrix.org",
"name": "Freenode",
"icon": "https://matrix.org/_matrix/media/v1/download/matrix.org/DHLHpDDgWNNejFmrewvwEAHX",
"example": "#channel",
"nativePattern": "^#[^\\s]+$"
},
"irc:mozilla": {
"protocol": "irc",
"domain": "chat.freenode.net",
"portalRoomPattern": "#mozilla_.*:matrix.org",
"name": "Mozilla",
"icon": "https://matrix.org/_matrix/media/v1/download/matrix.org/DHLHpDDgWNNejFmrewvwEAHX",
"example": "#channel",
"nativePattern": "^#[^\\s]+$"
},
"irc:snoonet": {
"protocol": "irc",
"domain": "ipv6-irc.snoonet.org",
"portalRoomPattern": "#_snoonet_.*:matrix.org",
"name": "Snoonet",
"icon": "https://matrix.org/_matrix/media/v1/download/matrix.org/DHLHpDDgWNNejFmrewvwEAHX",
"example": "#channel",
"nativePattern": "^#[^\\s]+$"
},
"irc:oftc": {
"protocol": "irc",
"domain": "irc.oftc.net",
"portalRoomPattern": "#_oftc_.*:matrix.org",
"name": "OFTC",
"icon": "https://matrix.org/_matrix/media/v1/download/matrix.org/DHLHpDDgWNNejFmrewvwEAHX",
"example": "#channel",
"nativePattern": "^#[^\\s]+$"
}
}
}
}

View File

@@ -1,214 +0,0 @@
// @flow
/*
Copyright 2016 Aviral Dasgupta
Copyright 2016 OpenMarket Ltd
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
// Squirrel on windows starts the app with various flags
// as hooks to tell us when we've been installed/uninstalled
// etc.
const check_squirrel_hooks = require('./squirrelhooks');
if (check_squirrel_hooks()) return;
const electron = require('electron');
const url = require('url');
const VectorMenu = require('./vectormenu');
let vectorConfig = {};
try {
vectorConfig = require('../../webapp/config.json');
} catch (e) {
// it would be nice to check the error code here and bail if the config
// is unparseable, but we get MODULE_NOT_FOUND in the case of a missing
// file or invalid json, so node is just very unhelpful.
// Continue with the defaults (ie. an empty config)
}
const PERMITTED_URL_SCHEMES = [
'http:',
'https:',
'mailto:',
];
const UPDATE_POLL_INTERVAL_MS = 60 * 60 * 1000;
const INITIAL_UPDATE_DELAY_MS = 30 * 1000;
let mainWindow = null;
let appQuitting = false;
function safeOpenURL(target) {
// openExternal passes the target to open/start/xdg-open,
// so put fairly stringent limits on what can be opened
// (for instance, open /bin/sh does indeed open a terminal
// with a shell, albeit with no arguments)
const parsed_url = url.parse(target);
if (PERMITTED_URL_SCHEMES.indexOf(parsed_url.protocol) > -1) {
// explicitly use the URL re-assembled by the url library,
// so we know the url parser has understood all the parts
// of the input string
const new_target = url.format(parsed_url);
electron.shell.openExternal(new_target);
}
}
function onWindowOrNavigate(ev, target) {
// always prevent the default: if something goes wrong,
// we don't want to end up opening it in the electron
// app, as we could end up opening any sort of random
// url in a window that has node scripting access.
ev.preventDefault();
safeOpenURL(target);
}
function onLinkContextMenu(ev, params) {
const popup_menu = new electron.Menu();
popup_menu.append(new electron.MenuItem({
label: params.linkURL,
click() {
safeOpenURL(params.linkURL);
},
}));
popup_menu.popup();
ev.preventDefault();
}
function installUpdate() {
// for some reason, quitAndInstall does not fire the
// before-quit event, so we need to set the flag here.
appQuitting = true;
electron.autoUpdater.quitAndInstall();
}
function pollForUpdates() {
try {
electron.autoUpdater.checkForUpdates();
} catch (e) {
console.log("Couldn't check for update", e);
}
}
function startAutoUpdate(update_base_url) {
if (update_base_url.slice(-1) !== '/') {
update_base_url = update_base_url + '/';
}
try {
// For reasons best known to Squirrel, the way it checks for updates
// is completely different between macOS and windows. On macOS, it
// hits a URL that either gives it a 200 with some json or
// 204 No Content. On windows it takes a base path and looks for
// files under that path.
if (process.platform == 'darwin') {
electron.autoUpdater.setFeedURL(update_base_url + 'macos/');
} else if (process.platform == 'win32') {
electron.autoUpdater.setFeedURL(update_base_url + 'win32/' + process.arch + '/');
} else {
// Squirrel / electron only supports auto-update on these two platforms.
// I'm not even going to try to guess which feed style they'd use if they
// implemented it on Linux, or if it would be different again.
console.log("Auto update not supported on this platform");
}
// We check for updates ourselves rather than using 'updater' because we need to
// do it in the main process (and we don't really need to check every 10 minutes:
// every hour should be just fine for a desktop app)
// However, we still let the main window listen for the update events.
// We also wait a short time before checking for updates the first time because
// of squirrel on windows and it taking a small amount of time to release a
// lock file.
setTimeout(pollForUpdates, INITIAL_UPDATE_DELAY_MS);
setInterval(pollForUpdates, UPDATE_POLL_INTERVAL_MS);
} catch (err) {
// will fail if running in debug mode
console.log("Couldn't enable update checking", err);
}
}
// handle uncaught errors otherwise it displays
// stack traces in popup dialogs, which is terrible (which
// it will do any time the auto update poke fails, and there's
// no other way to catch this error).
// Assuming we generally run from the console when developing,
// this is far preferable.
process.on('uncaughtException', function (error) {
console.log("Unhandled exception", error);
});
electron.ipcMain.on('install_update', installUpdate);
electron.app.on('ready', () => {
if (vectorConfig.update_base_url) {
console.log("Starting auto update with base URL: " + vectorConfig.update_base_url);
startAutoUpdate(vectorConfig.update_base_url);
} else {
console.log("No update_base_url is defined: auto update is disabled");
}
const icon_path = `${__dirname}/../img/riot.` + (
process.platform == 'win32' ? 'ico' : 'png'
);
mainWindow = new electron.BrowserWindow({
icon: icon_path,
width: 1024, height: 768,
show: false,
});
mainWindow.loadURL(`file://${__dirname}/../../webapp/index.html`);
electron.Menu.setApplicationMenu(VectorMenu);
mainWindow.once('ready-to-show', () => {
mainWindow.show();
});
mainWindow.on('closed', () => {
mainWindow = null;
});
mainWindow.on('close', (e) => {
if (process.platform == 'darwin' && !appQuitting) {
// On Mac, closing the window just hides it
// (this is generally how single-window Mac apps
// behave, eg. Mail.app)
e.preventDefault();
mainWindow.hide();
return false;
}
});
mainWindow.webContents.on('new-window', onWindowOrNavigate);
mainWindow.webContents.on('will-navigate', onWindowOrNavigate);
mainWindow.webContents.on('context-menu', function(ev, params) {
if (params.linkURL) {
onLinkContextMenu(ev, params);
}
});
});
electron.app.on('window-all-closed', () => {
electron.app.quit();
});
electron.app.on('activate', () => {
mainWindow.show();
});
electron.app.on('before-quit', () => {
appQuitting = true;
});
// Set the App User Model ID to match what the squirrel
// installer uses for the shortcut icon.
// This makes notifications work on windows 8.1 (and is
// a noop on other platforms).
electron.app.setAppUserModelId('com.squirrel.riot-web.Riot');

View File

@@ -1,30 +0,0 @@
const path = require('path');
const spawn = require('child_process').spawn;
const app = require('electron').app;
function run_update_exe(args, done) {
const updateExe = path.resolve(path.dirname(process.execPath), '..', 'Update.exe');
spawn(updateExe, args, {
detached: true
}).on('close', done);
};
function check_squirrel_hooks() {
if (process.platform != 'win32') return false;
const cmd = process.argv[1];
const target = path.basename(process.execPath);
if (cmd === '--squirrel-install' || cmd === '--squirrel-updated') {
run_update_exe(['--createShortcut=' + target + ''], app.quit);
return true;
} else if (cmd === '--squirrel-uninstall') {
run_update_exe(['--removeShortcut=' + target + ''], app.quit);
return true;
} else if (cmd === '--squirrel-obsolete') {
app.quit();
return true;
}
return false;
}
module.exports = check_squirrel_hooks;

View File

@@ -1,201 +0,0 @@
/*
Copyright 2016 OpenMarket Ltd
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
const electron = require('electron');
// Menu template from http://electron.atom.io/docs/api/menu/, edited
const template = [
{
label: 'Edit',
submenu: [
{
role: 'undo'
},
{
role: 'redo'
},
{
type: 'separator'
},
{
role: 'cut'
},
{
role: 'copy'
},
{
role: 'paste'
},
{
role: 'pasteandmatchstyle'
},
{
role: 'delete'
},
{
role: 'selectall'
}
]
},
{
label: 'View',
submenu: [
{
type: 'separator'
},
{
role: 'resetzoom'
},
{
role: 'zoomin'
},
{
role: 'zoomout'
},
{
type: 'separator'
},
{
role: 'togglefullscreen'
},
{
label: 'Toggle Developer Tools',
accelerator: process.platform == 'darwin' ? 'Alt+Command+I' : 'Ctrl+Shift+I',
click: function(item, focusedWindow) {
if (focusedWindow) focusedWindow.toggleDevTools();
}
}
]
},
{
role: 'window',
submenu: [
{
role: 'minimize'
},
{
role: 'close'
}
]
},
{
role: 'help',
submenu: [
{
label: 'riot.im',
click () { electron.shell.openExternal('https://riot.im/') }
}
]
}
];
// macOS has specific menu conventions...
if (process.platform === 'darwin') {
// first macOS menu is the name of the app
const name = electron.app.getName()
template.unshift({
label: name,
submenu: [
{
role: 'about'
},
{
type: 'separator'
},
{
role: 'services',
submenu: []
},
{
type: 'separator'
},
{
role: 'hide'
},
{
role: 'hideothers'
},
{
role: 'unhide'
},
{
type: 'separator'
},
{
role: 'quit'
}
]
})
// Edit menu.
// This has a 'speech' section on macOS
template[1].submenu.push(
{
type: 'separator'
},
{
label: 'Speech',
submenu: [
{
role: 'startspeaking'
},
{
role: 'stopspeaking'
}
]
}
)
// Window menu.
// This also has specific functionality on macOS
template[3].submenu = [
{
label: 'Close',
accelerator: 'CmdOrCtrl+W',
role: 'close'
},
{
label: 'Minimize',
accelerator: 'CmdOrCtrl+M',
role: 'minimize'
},
{
label: 'Zoom',
role: 'zoom'
},
{
type: 'separator'
},
{
label: 'Bring All to Front',
role: 'front'
}
]
} else {
template.unshift({
label: 'File',
submenu: [
// For some reason, 'about' does not seem to work on windows.
/*{
role: 'about'
},*/
{
role: 'quit'
}
]
});
}
module.exports = electron.Menu.buildFromTemplate(template)

View File

@@ -1,136 +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'),
]
},
],
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,154 +1,41 @@
{
"name": "riot-web",
"productName": "Riot",
"main": "electron/src/electron-main.js",
"version": "0.9.2",
"description": "A feature-rich client for Matrix.org",
"author": "Vector Creations Ltd.",
"name": "matrix-react-sdk",
"version": "0.0.2",
"description": "SDK for matrix.org using React",
"author": "matrix.org",
"repository": {
"type": "git",
"url": "https://github.com/vector-im/riot-web"
"url": "https://github.com/matrix-org/matrix-react-sdk"
},
"license": "Apache-2.0",
"files": [
"AUTHORS.rst",
"CONTRIBUTING.rst",
"deploy",
"docs",
"karma.conf.js",
"lib",
"release.sh",
"scripts",
"src",
"test",
"webpack.config.js"
],
"style": "bundle.css",
"matrix-react-parent": "matrix-react-sdk",
"main": "lib/index.js",
"bin": {
"reskindex": "./reskindex.js"
},
"scripts": {
"reskindex": "reskindex -h src/header",
"build:res": "cpx \"{src/skins/vector/fonts,src/skins/vector/img}/**\" webapp/ && cpx \"{res/media,res/vector-icons}/**\" webapp/",
"build:config": "cpx config.json webapp/",
"build:emojione": "cpx \"node_modules/emojione/assets/svg/*\" webapp/emojione/svg/",
"build:modernizr": "modernizr -c .modernizr.json -d src/vector/modernizr.js",
"build:css": "mkdirp build && catw \"src/skins/vector/css/**/*.css\" -o build/components.css --no-watch",
"build:compile": "babel --source-maps -d lib src",
"build:bundle": "NODE_ENV=production webpack -p --progress",
"build:bundle:dev": "webpack --optimize-occurence-order --progress",
"build:electron": "npm run clean && npm run build && build -wml --ia32 --x64",
"build": "node scripts/babelcheck.js && npm run build:res && npm run build:config && npm run build:emojione && npm run build:css && npm run build:bundle",
"build:dev": "node scripts/babelcheck.js && npm run build:res && npm run build:config && npm run build:emojione && npm run build:css && npm run build:bundle:dev",
"dist": "scripts/package.sh",
"start:res": "parallelshell \"cpx -w \\\"{src/skins/vector/fonts,src/skins/vector/img}/**\\\" webapp/\" \"cpx -w \\\"{res/media,res/vector-icons}/**\\\" webapp/\"",
"start:config": "cpx -w config.json webapp/",
"start:emojione": "cpx \"node_modules/emojione/assets/svg/*\" webapp/emojione/svg/ -w",
"start:js": "webpack-dev-server -w --progress",
"start:js:prod": "NODE_ENV=production webpack-dev-server -w --progress",
"start:skins:css": "mkdirp build && catw \"src/skins/vector/css/**/*.css\" -o build/components.css",
"start": "node scripts/babelcheck.js && parallelshell \"npm run start:emojione\" \"npm run start:res\" \"npm run start:config\" \"npm run start:js\" \"npm run start:skins:css\"",
"start:prod": "parallelshell \"npm run start:emojione\" \"npm run start:js:prod\" \"npm run start:skins:css\"",
"clean": "rimraf build lib webapp electron/dist",
"prepublish": "npm run build:compile",
"test": "karma start --single-run=true --autoWatch=false --browsers PhantomJS --colors=false",
"test:multi": "karma start"
"build": "babel src -d lib --source-maps",
"start": "babel src -w -d lib --source-maps",
"clean": "rimraf lib",
"prepublish": "npm run build"
},
"dependencies": {
"babel-polyfill": "^6.5.0",
"babel-runtime": "^6.11.6",
"browser-request": "^0.3.3",
"classnames": "^2.1.2",
"draft-js": "^0.8.1",
"extract-text-webpack-plugin": "^0.9.1",
"favico.js": "^0.3.10",
"filesize": "^3.1.2",
"flux": "~2.0.3",
"gemini-scrollbar": "matrix-org/gemini-scrollbar#b302279",
"gfm.css": "^1.1.1",
"highlight.js": "^9.0.0",
"linkifyjs": "^2.1.3",
"matrix-js-sdk": "0.7.2",
"matrix-react-sdk": "0.8.2",
"modernizr": "^3.1.0",
"flux": "^2.0.3",
"glob": "^5.0.14",
"linkifyjs": "^2.0.0-beta.4",
"matrix-js-sdk": "^0.3.0",
"optimist": "^0.6.1",
"q": "^1.4.1",
"react": "^15.4.0",
"react-dnd": "^2.1.4",
"react-dnd-html5-backend": "^2.1.2",
"react-dom": "^15.4.0",
"react-gemini-scrollbar": "matrix-org/react-gemini-scrollbar#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"
},
"//deps": "The loader packages are here because webpack in a project that depends on us needs them in this package's node_modules folder",
"//depsbuglink": "https://github.com/webpack/webpack/issues/1472",
"devDependencies": {
"babel-cli": "^6.5.2",
"babel-core": "^6.14.0",
"babel-eslint": "^6.1.0",
"babel-loader": "^6.2.5",
"babel-plugin-add-module-exports": "^0.2.1",
"babel-plugin-transform-async-to-generator": "^6.16.0",
"babel-plugin-transform-class-properties": "^6.16.0",
"babel-plugin-transform-object-rest-spread": "^6.16.0",
"babel-plugin-transform-runtime": "^6.15.0",
"babel-preset-es2015": "^6.16.0",
"babel-preset-es2016": "^6.16.0",
"babel-preset-es2017": "^6.16.0",
"babel-preset-react": "^6.16.0",
"babel-preset-stage-2": "^6.17.0",
"catw": "^1.0.1",
"cpx": "^1.3.2",
"css-raw-loader": "^0.1.1",
"electron-builder": "^10.4.1",
"emojione": "^2.2.3",
"expect": "^1.16.0",
"fs-extra": "^0.30.0",
"html-webpack-plugin": "^2.24.0",
"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",
"mkdirp": "^0.5.1",
"mocha": "^2.4.5",
"parallelshell": "^1.2.0",
"phantomjs-prebuilt": "^2.1.7",
"react-addons-perf": "^15.4.0",
"react-addons-test-utils": "^15.4.0",
"babel": "^5.8.23",
"rimraf": "^2.4.3",
"source-map-loader": "^0.1.5",
"webpack": "^1.12.14",
"webpack-dev-server": "^1.16.2"
},
"optionalDependencies": {
"olm": "https://matrix.org/packages/npm/olm/olm-2.0.0.tgz"
},
"build": {
"appId": "im.riot.app",
"category": "Network",
"electronVersion": "1.4.11",
"//asar=false": "https://github.com/electron-userland/electron-builder/issues/675",
"asar": false,
"dereference": true,
"//files": "We bundle everything, so we only need to include webapp/",
"files": [
"electron/src/**",
"electron/img/**",
"webapp/**",
"package.json"
],
"linux": {
"target": "deb",
"maintainer": "support@riot.im"
},
"win": {
"target": "squirrel"
}
},
"directories": {
"buildResources": "electron/build",
"output": "electron/dist"
"json-loader": "^0.5.3",
"source-map-loader": "^0.1.5"
}
}

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 +0,0 @@
signing_id: packages@riot.im

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 10 KiB

View File

@@ -1,12 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<browserconfig>
<msapplication>
<tile>
<square70x70logo src="/vector-icons/mstile-70x70.png"/>
<square150x150logo src="/vector-icons/mstile-150x150.png"/>
<square310x310logo src="/vector-icons/mstile-310x310.png"/>
<wide310x150logo src="/vector-icons/mstile-310x150.png"/>
<TileColor>#da532c</TileColor>
</tile>
</msapplication>
</browserconfig>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 673 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 102 KiB

View File

@@ -1,41 +0,0 @@
{
"name": "Riot",
"icons": [
{
"src": "\/icons\/android-chrome-36x36.png",
"sizes": "36x36",
"type": "image\/png",
"density": "0.75"
},
{
"src": "\/icons\/android-chrome-48x48.png",
"sizes": "48x48",
"type": "image\/png",
"density": "1.0"
},
{
"src": "\/icons\/android-chrome-72x72.png",
"sizes": "72x72",
"type": "image\/png",
"density": "1.5"
},
{
"src": "\/icons\/android-chrome-96x96.png",
"sizes": "96x96",
"type": "image\/png",
"density": "2.0"
},
{
"src": "\/icons\/android-chrome-144x144.png",
"sizes": "144x144",
"type": "image\/png",
"density": "3.0"
},
{
"src": "\/icons\/android-chrome-192x192.png",
"sizes": "192x192",
"type": "image\/png",
"density": "4.0"
}
]
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.9 KiB

83
reskindex.js Executable file
View File

@@ -0,0 +1,83 @@
#!/usr/bin/env node
var fs = require('fs');
var path = require('path');
var glob = require('glob');
var args = require('optimist').argv;
var header = args.h || args.header;
if (args._.length == 0) {
console.log("No skin given");
process.exit(1);
}
var skin = args._[0];
try {
fs.accessSync(path.join('src', 'skins', skin), fs.F_OK);
} catch (e) {
console.log("Skin "+skin+" not found");
process.exit(1);
}
var skinfoFile = path.join('src', 'skins', skin, 'skinfo.json');
try {
fs.accessSync(skinfoFile, fs.F_OK);
} catch (e) {
console.log("Skin "+skin+" has no skinfo.json");
process.exit(1);
}
try {
fs.accessSync(path.join('src', 'skins', skin, 'views'), fs.F_OK);
} catch (e) {
console.log("Skin "+skin+" has no views directory");
process.exit(1);
}
var skindex = path.join('src', 'skins', skin, 'skindex.js');
var viewsDir = path.join('src', 'skins', skin, 'views');
var strm = fs.createWriteStream(skindex);
if (header) {
strm.write(fs.readFileSync(header));
strm.write('\n');
}
strm.write("/*\n");
strm.write(" * THIS FILE IS AUTO-GENERATED\n");
strm.write(" * You can edit it you like, but your changes will be overwritten,\n");
strm.write(" * so you'd just be trying to swim upstream like a salmon.\n");
strm.write(" * You are not a salmon.\n");
strm.write(" */\n\n");
var mySkinfo = JSON.parse(fs.readFileSync(skinfoFile, "utf8"));
strm.write("var skin = {};\n");
strm.write('\n');
var files = glob.sync('**/*.js', {cwd: viewsDir});
for (var i = 0; i < files.length; ++i) {
var file = files[i].replace('.js', '');
var module = (file.replace(/\//g, '.'));
strm.write("skin['"+module+"'] = require('./views/"+file+"');\n");
strm.uncork();
}
strm.write("\n");
if (mySkinfo.baseSkin) {
strm.write("module.exports = require('"+mySkinfo.baseSkin+"');");
strm.write("var extend = require('matrix-react-sdk/lib/extend');\n");
strm.write("extend(module.exports, skin);\n");
} else {
strm.write("module.exports = skin;");
}
strm.end();

View File

@@ -1,26 +0,0 @@
#!/usr/bin/env node
var exec = require('child_process').exec;
// Makes sure the babel executable in the path is babel 6 (or greater), not
// babel 5, which it is if you upgrade from an older version of react-sdk and
// run 'npm install' since the package has changed to babel-cli, so 'babel'
// remains installed and the executable in node_modules/.bin remains as babel
// 5.
// This script is duplicated from matrix-react-sdk because it can't reliably
// be pulled in from react-sdk while npm install is failing, as it will do
// if the environment is in the erroneous state this script checks for.
exec("babel -V", function (error, stdout, stderr) {
if ((error && error.code) || parseInt(stdout.substr(0,1), 10) < 6) {
console.log("\033[31m\033[1m"+
'*****************************************\n'+
'* vector-web has moved to babel 6 *\n'+
'* Please "rm -rf node_modules && npm i" *\n'+
'* then restore links as appropriate *\n'+
'*****************************************\n'+
"\033[91m");
process.exit(1);
}
});

View File

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

View File

@@ -1,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,38 +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
# install olm. A naive 'npm i ./olm/olm-*.tgz' fails because it uses the url
# from our package.json (or even matrix-js-sdk's) in preference.
tar -C olm -xz < olm/olm-*.tgz
rm -r node_modules/olm
cp -r olm/package node_modules/olm
# 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
rm dist/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
DIST_VERSION=$VECTOR_SHA-react-$REACT_SHA-js-$JSSDK_SHA scripts/package.sh -d

View File

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

View File

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

View File

@@ -1,177 +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, arg_config_location = (
None, None, None, None, None
)
def download_file(url):
local_filename = url.split('/')[-1]
r = requests.get(url, stream=True)
with open(local_filename, 'wb') as f:
for chunk in r.iter_content(chunk_size=1024):
if chunk: # filter out keep-alive new chunks
f.write(chunk)
return local_filename
def untar_to(tarball, dest):
with tarfile.open(tarball) as tar:
tar.extractall(dest)
def create_symlink(source, linkname):
try:
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_to(filename, arg_extract_path)
extracted_dir = os.path.join(arg_extract_path, name_str)
if arg_should_clean:
os.remove(filename)
create_symlink(source=extracted_dir, linkname=arg_symlink)
if arg_config_location:
create_symlink(source=arg_config_location, linkname=os.path.join(extracted_dir, 'config.json'))
return jsonify({})
if __name__ == "__main__":
parser = argparse.ArgumentParser("Runs a Vector redeployment server.")
parser.add_argument(
"-j", "--jenkins", dest="jenkins", default="https://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."
)
)
parser.add_argument(
"--config", dest="config", help=(
"Write a symlink to config.json in the extracted tarball. \
To this location."
)
)
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
arg_config_location = args.config
print(
"Listening on port %s. Extracting to %s%s. Symlinking to %s. Jenkins URL: %s. Config location: %s" %
(args.port, arg_extract_path,
" (clean after)" if arg_should_clean else "", arg_symlink, arg_jenkins_url, arg_config_location)
)
app.run(host="0.0.0.0", port=args.port, debug=True)

308
src/CallHandler.js Normal file
View File

@@ -0,0 +1,308 @@
/*
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.
*/
/*
* Manages a list of all the currently active calls.
*
* This handler dispatches when voip calls are added/updated/removed from this list:
* {
* action: 'call_state'
* room_id: <room ID of the call>
* }
*
* To know the state of the call, this handler exposes a getter to
* obtain the call for a room:
* var call = CallHandler.getCall(roomId)
* var state = call.call_state; // ringing|ringback|connected|ended|busy|stop_ringback|stop_ringing
*
* This handler listens for and handles the following actions:
* {
* action: 'place_call',
* type: 'voice|video',
* room_id: <room that the place call button was pressed in>
* }
*
* {
* action: 'incoming_call'
* call: MatrixCall
* }
*
* {
* action: 'hangup'
* room_id: <room that the hangup button was pressed in>
* }
*
* {
* action: 'answer'
* room_id: <room that the answer button was pressed in>
* }
*/
var MatrixClientPeg = require('./MatrixClientPeg');
var Modal = require('./Modal');
var sdk = require('./index');
var Matrix = require("matrix-js-sdk");
var dis = require("./dispatcher");
var Modulator = require("./Modulator");
global.mxCalls = {
//room_id: MatrixCall
};
var calls = global.mxCalls;
function play(audioId) {
// TODO: Attach an invisible element for this instead
// which listens?
var audio = document.getElementById(audioId);
if (audio) {
audio.load();
audio.play();
}
}
function pause(audioId) {
// TODO: Attach an invisible element for this instead
// which listens?
var audio = document.getElementById(audioId);
if (audio) {
audio.pause();
}
}
function _setCallListeners(call) {
call.on("error", function(err) {
console.error("Call error: %s", err);
console.error(err.stack);
call.hangup();
_setCallState(undefined, call.roomId, "ended");
});
call.on("hangup", function() {
_setCallState(undefined, call.roomId, "ended");
});
// map web rtc states to dummy UI state
// ringing|ringback|connected|ended|busy|stop_ringback|stop_ringing
call.on("state", function(newState, oldState) {
if (newState === "ringing") {
_setCallState(call, call.roomId, "ringing");
pause("ringbackAudio");
}
else if (newState === "invite_sent") {
_setCallState(call, call.roomId, "ringback");
play("ringbackAudio");
}
else if (newState === "ended" && oldState === "connected") {
_setCallState(undefined, call.roomId, "ended");
pause("ringbackAudio");
play("callendAudio");
}
else if (newState === "ended" && oldState === "invite_sent" &&
(call.hangupParty === "remote" ||
(call.hangupParty === "local" && call.hangupReason === "invite_timeout")
)) {
_setCallState(call, call.roomId, "busy");
pause("ringbackAudio");
play("busyAudio");
var ErrorDialog = sdk.getComponent("organisms.ErrorDialog");
Modal.createDialog(ErrorDialog, {
title: "Call Timeout",
description: "The remote side failed to pick up."
});
}
else if (oldState === "invite_sent") {
_setCallState(call, call.roomId, "stop_ringback");
pause("ringbackAudio");
}
else if (oldState === "ringing") {
_setCallState(call, call.roomId, "stop_ringing");
pause("ringbackAudio");
}
else if (newState === "connected") {
_setCallState(call, call.roomId, "connected");
pause("ringbackAudio");
}
});
}
function _setCallState(call, roomId, status) {
console.log(
"Call state in %s changed to %s (%s)", roomId, status, (call ? call.state : "-")
);
calls[roomId] = call;
if (call) {
call.call_state = status;
}
dis.dispatch({
action: 'call_state',
room_id: roomId
});
}
function _onAction(payload) {
function placeCall(newCall) {
_setCallListeners(newCall);
_setCallState(newCall, newCall.roomId, "ringback");
if (payload.type === 'voice') {
newCall.placeVoiceCall();
}
else if (payload.type === 'video') {
newCall.placeVideoCall(
payload.remote_element,
payload.local_element
);
}
else if (payload.type === 'screensharing') {
newCall.placeScreenSharingCall(
payload.remote_element,
payload.local_element
);
}
else {
console.error("Unknown conf call type: %s", payload.type);
}
}
switch (payload.action) {
case 'place_call':
if (module.exports.getAnyActiveCall()) {
var ErrorDialog = sdk.getComponent("organisms.ErrorDialog");
Modal.createDialog(ErrorDialog, {
title: "Existing Call",
description: "You are already in a call."
});
return; // don't allow >1 call to be placed.
}
var room = MatrixClientPeg.get().getRoom(payload.room_id);
if (!room) {
console.error("Room %s does not exist.", payload.room_id);
return;
}
var members = room.getJoinedMembers();
if (members.length <= 1) {
var ErrorDialog = sdk.getComponent("organisms.ErrorDialog");
Modal.createDialog(ErrorDialog, {
description: "You cannot place a call with yourself."
});
return;
}
else if (members.length === 2) {
console.log("Place %s call in %s", payload.type, payload.room_id);
var call = Matrix.createNewMatrixCall(
MatrixClientPeg.get(), payload.room_id
);
placeCall(call);
}
else { // > 2
dis.dispatch({
action: "place_conference_call",
room_id: payload.room_id,
type: payload.type,
remote_element: payload.remote_element,
local_element: payload.local_element
});
}
break;
case 'place_conference_call':
console.log("Place conference call in %s", payload.room_id);
if (!Modulator.hasConferenceHandler()) {
var ErrorDialog = sdk.getComponent("organisms.ErrorDialog");
Modal.createDialog(ErrorDialog, {
description: "Conference calls are not supported in this client"
});
} else {
var ConferenceHandler = Modulator.getConferenceHandler();
ConferenceHandler.createNewMatrixCall(
MatrixClientPeg.get(), payload.room_id
).done(function(call) {
placeCall(call);
}, function(err) {
console.error("Failed to setup conference call: %s", err);
});
}
break;
case 'incoming_call':
if (module.exports.getAnyActiveCall()) {
payload.call.hangup("busy");
return; // don't allow >1 call to be received, hangup newer one.
}
var call = payload.call;
_setCallListeners(call);
_setCallState(call, call.roomId, "ringing");
break;
case 'hangup':
if (!calls[payload.room_id]) {
return; // no call to hangup
}
calls[payload.room_id].hangup();
_setCallState(null, payload.room_id, "ended");
break;
case 'answer':
if (!calls[payload.room_id]) {
return; // no call to answer
}
calls[payload.room_id].answer();
_setCallState(calls[payload.room_id], payload.room_id, "connected");
dis.dispatch({
action: "view_room",
room_id: payload.room_id
});
break;
}
}
// FIXME: Nasty way of making sure we only register
// with the dispatcher once
if (!global.mxCallHandler) {
dis.register(_onAction);
}
var callHandler = {
getCallForRoom: function(roomId) {
var call = module.exports.getCall(roomId);
if (call) return call;
if (Modulator.hasConferenceHandler()) {
var ConferenceHandler = Modulator.getConferenceHandler();
call = ConferenceHandler.getConferenceCallForRoom(roomId);
}
if (call) return call;
return null;
},
getCall: function(roomId) {
return calls[roomId] || null;
},
getAnyActiveCall: function() {
var roomsWithCalls = Object.keys(calls);
for (var i = 0; i < roomsWithCalls.length; i++) {
if (calls[roomsWithCalls[i]] &&
calls[roomsWithCalls[i]].call_state !== "ended") {
return calls[roomsWithCalls[i]];
}
}
return null;
}
};
// Only things in here which actually need to be global are the
// calls list (done separately) and making sure we only register
// with the dispatcher once (which uses this mechanism but checks
// separately). This could be tidied up.
if (global.mxCallHandler === undefined) {
global.mxCallHandler = callHandler;
}
module.exports = global.mxCallHandler;

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.
@@ -16,9 +16,13 @@ limitations under the License.
'use strict';
var url = require ('url');
function getServiceUrl() {
var parsedUrl = url.parse(window.location.href);
return parsedUrl.protocol + "//" + parsedUrl.host + parsedUrl.pathname;
}
module.exports = {
NotificationUtils: require('./NotificationUtils'),
PushRuleVectorState: require('./PushRuleVectorState'),
VectorPushRulesDefinitions: require('./VectorPushRulesDefinitions'),
ContentRules: require('./ContentRules'),
getServiceUrl: getServiceUrl
};

86
src/ContentMessages.js Normal file
View File

@@ -0,0 +1,86 @@
/*
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 q = require('q');
var extend = require('./extend');
function infoForImageFile(imageFile) {
var deferred = q.defer();
// Load the file into an html element
var img = document.createElement("img");
var reader = new FileReader();
reader.onload = function(e) {
img.src = e.target.result;
// Once ready, returns its size
img.onload = function() {
deferred.resolve({
w: img.width,
h: img.height
});
};
img.onerror = function(e) {
deferred.reject(e);
};
};
reader.onerror = function(e) {
deferred.reject(e);
};
reader.readAsDataURL(imageFile);
return deferred.promise;
}
function sendContentToRoom(file, roomId, matrixClient) {
var content = {
body: file.name,
info: {
size: file.size,
}
};
// if we have a mime type for the file, add it to the message metadata
if (file.type) {
content.info.mimetype = file.type;
}
var def = q.defer();
if (file.type.indexOf('image/') == 0) {
content.msgtype = 'm.image';
infoForImageFile(file).then(function(imageInfo) {
extend(content.info, imageInfo);
def.resolve();
});
} else {
content.msgtype = 'm.file';
def.resolve();
}
return def.promise.then(function() {
return matrixClient.uploadContent(file);
}).then(function(url) {
content.url = url;
return matrixClient.sendMessage(roomId, content);
});
}
module.exports = {
sendContentToRoom: sendContentToRoom
};

105
src/MatrixClientPeg.js Normal file
View File

@@ -0,0 +1,105 @@
/*
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';
// A thing that holds your Matrix Client
var Matrix = require("matrix-js-sdk");
var matrixClient = null;
var localStorage = window.localStorage;
function deviceId() {
var id = Math.floor(Math.random()*16777215).toString(16);
id = "W" + "000000".substring(id.length) + id;
if (localStorage) {
id = localStorage.getItem("mx_device_id") || id;
localStorage.setItem("mx_device_id", id);
}
return id;
}
function createClient(hs_url, is_url, user_id, access_token) {
var opts = {
baseUrl: hs_url,
idBaseUrl: is_url,
accessToken: access_token,
userId: user_id
};
if (localStorage) {
opts.sessionStore = new Matrix.WebStorageSessionStore(localStorage);
opts.deviceId = deviceId();
}
matrixClient = Matrix.createClient(opts);
}
if (localStorage) {
var hs_url = localStorage.getItem("mx_hs_url");
var is_url = localStorage.getItem("mx_is_url") || 'https://matrix.org';
var access_token = localStorage.getItem("mx_access_token");
var user_id = localStorage.getItem("mx_user_id");
if (access_token && user_id && hs_url) {
createClient(hs_url, is_url, user_id, access_token);
}
}
class MatrixClient {
get() {
return matrixClient;
}
unset() {
matrixClient = null;
}
replaceUsingUrls(hs_url, is_url) {
matrixClient = Matrix.createClient({
baseUrl: hs_url,
idBaseUrl: is_url
});
}
replaceUsingAccessToken(hs_url, is_url, user_id, access_token) {
if (localStorage) {
try {
localStorage.clear();
} catch (e) {
console.warn("Error using local storage");
}
}
createClient(hs_url, is_url, user_id, access_token);
if (localStorage) {
try {
localStorage.setItem("mx_hs_url", hs_url);
localStorage.setItem("mx_is_url", is_url);
localStorage.setItem("mx_user_id", user_id);
localStorage.setItem("mx_access_token", access_token);
} catch (e) {
console.warn("Error using local storage: can't persist session!");
}
} else {
console.warn("No local storage available: can't persist session!");
}
}
}
if (!global.mxMatrixClient) {
global.mxMatrixClient = new MatrixClient();
}
module.exports = global.mxMatrixClient;

61
src/MatrixTools.js Normal file
View File

@@ -0,0 +1,61 @@
/*
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.
*/
module.exports = {
/**
* Given a room object, return the canonical alias for it
* if there is one. Otherwise return null;
*/
getCanonicalAliasForRoom: function(room) {
var aliasEvents = room.currentState.getStateEvents(
"m.room.aliases"
);
// Canonical aliases aren't implemented yet, so just return the first
for (var j = 0; j < aliasEvents.length; j++) {
var aliases = aliasEvents[j].getContent().aliases;
if (aliases && aliases.length) {
return aliases[0];
}
}
return null;
},
/**
* Given a list of room objects, return the room which has the given alias,
* else null.
*/
getRoomForAlias: function(rooms, room_alias) {
var room;
for (var i = 0; i < rooms.length; i++) {
var aliasEvents = rooms[i].currentState.getStateEvents(
"m.room.aliases"
);
for (var j = 0; j < aliasEvents.length; j++) {
var aliases = aliasEvents[j].getContent().aliases || [];
for (var k = 0; k < aliases.length; k++) {
if (aliases[k] === room_alias) {
room = rooms[i];
break;
}
}
if (room) { break; }
}
if (room) { break; }
}
return room || null;
}
}

84
src/Modal.js Normal file
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.
*/
'use strict';
var React = require('react');
module.exports = {
DialogContainerId: "mx_Dialog_Container",
getOrCreateContainer: function() {
var container = document.getElementById(this.DialogContainerId);
if (!container) {
container = document.createElement("div");
container.id = this.DialogContainerId;
document.body.appendChild(container);
}
return container;
},
createDialogWithElement: function(element, props) {
var self = this;
var closeDialog = function() {
React.unmountComponentAtNode(self.getOrCreateContainer());
if (props && props.onFinished) props.onFinished.apply(null, arguments);
};
var dialog = (
<div className="mx_Dialog_wrapper">
<div className="mx_Dialog">
{element}
</div>
<div className="mx_Dialog_background" onClick={closeDialog}></div>
</div>
);
React.render(dialog, this.getOrCreateContainer());
return {close: closeDialog};
},
createDialog: function (Element, props) {
var self = this;
var closeDialog = function() {
React.unmountComponentAtNode(self.getOrCreateContainer());
if (props && props.onFinished) props.onFinished.apply(null, arguments);
};
// FIXME: If a dialog uses getDefaultProps it clobbers the onFinished
// property set here so you can't close the dialog from a button click!
var dialog = (
<div className="mx_Dialog_wrapper">
<div className="mx_Dialog">
<Element {...props} onFinished={closeDialog}/>
</div>
<div className="mx_Dialog_background" onClick={closeDialog}></div>
</div>
);
React.render(dialog, this.getOrCreateContainer());
return {close: closeDialog};
},
};

111
src/Modulator.js Normal file
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.
*/
/**
* The modulator stores 'modules': classes that provide
* functionality and are not React UI components.
* Modules go into named slots, eg. a conference calling
* module goes into the 'conference' slot. If two modules
* that use the same slot are loaded, this is considered
* to be an error.
*
* There are some module slots that the react SDK knows
* about natively: these have explicit getters.
*
* A module must define:
* - 'slot' (string): The name of the slot it goes into
* and may define:
* - 'start' (function): Called on module load
* - 'stop' (function): Called on module unload
*/
class Modulator {
constructor() {
this.modules = {};
}
getModule(name) {
var m = this.getModuleOrNull(name);
if (m === null) {
throw new Error("No such module: "+name);
}
return m;
}
getModuleOrNull(name) {
if (this.modules == {}) {
throw new Error(
"Attempted to get a module before a skin has been loaded."+
"This is probably because a component has called "+
"getModule at the root level."
);
}
var module = this.modules[name];
if (module) {
return module;
}
return null;
}
hasModule(name) {
var m = this.getModuleOrNull(name);
return m !== null;
}
loadModule(moduleObject) {
if (!moduleObject.slot) {
throw new Error(
"Attempted to load something that is not a module "+
"(does not have a slot name)"
);
}
if (this.modules[moduleObject.slot] !== undefined) {
throw new Error(
"Cannot load module: slot '"+moduleObject.slot+"' is occupied!"
);
}
this.modules[moduleObject.slot] = moduleObject;
}
reset() {
var keys = Object.keys(this.modules);
for (var i = 0; i < keys.length; ++i) {
var k = keys[i];
var m = this.modules[k];
if (m.stop) m.stop();
}
this.modules = {};
}
// ***********
// known slots
// ***********
getConferenceHandler() {
return this.getModule('conference');
}
hasConferenceHandler() {
return this.hasModule('conference');
}
}
// Define one Modulator globally (see Skinner.js)
if (global.mxModulator === undefined) {
global.mxModulator = new Modulator();
}
module.exports = global.mxModulator;

107
src/Presence.js Normal file
View File

@@ -0,0 +1,107 @@
/*
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("./MatrixClientPeg");
// Time in ms after that a user is considered as unavailable/away
var UNAVAILABLE_TIME_MS = 3 * 60 * 1000; // 3 mins
var PRESENCE_STATES = ["online", "offline", "unavailable"];
// The current presence state
var state, timer;
module.exports = {
/**
* Start listening the user activity to evaluate his presence state.
* Any state change will be sent to the Home Server.
*/
start: function() {
var self = this;
this.running = true;
if (undefined === state) {
// The user is online if they move the mouse or press a key
document.onmousemove = function() { self._resetTimer(); };
document.onkeypress = function() { self._resetTimer(); };
this._resetTimer();
}
},
/**
* Stop tracking user activity
*/
stop: function() {
this.running = false;
if (timer) {
clearTimeout(timer);
timer = undefined;
}
state = undefined;
},
/**
* Get the current presence state.
* @returns {string} the presence state (see PRESENCE enum)
*/
getState: function() {
return state;
},
/**
* Set the presence state.
* If the state has changed, the Home Server will be notified.
* @param {string} newState the new presence state (see PRESENCE enum)
*/
setState: function(newState) {
if (newState === state) {
return;
}
if (PRESENCE_STATES.indexOf(newState) === -1) {
throw new Error("Bad presence state: " + newState);
}
if (!this.running) {
return;
}
state = newState;
MatrixClientPeg.get().setPresence(state).done(function() {
console.log("Presence: %s", newState);
}, function(err) {
console.error("Failed to set presence: %s", err);
});
},
/**
* Callback called when the user made no action on the page for UNAVAILABLE_TIME ms.
* @private
*/
_onUnavailableTimerFire: function() {
this.setState("unavailable");
},
/**
* Callback called when the user made an action on the page
* @private
*/
_resetTimer: function() {
var self = this;
this.setState("online");
// Re-arm the timer
clearTimeout(timer);
timer = setTimeout(function() {
self._onUnavailableTimerFire();
}, UNAVAILABLE_TIME_MS);
}
};

36
src/RoomListSorter.js Normal file
View File

@@ -0,0 +1,36 @@
/*
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';
function tsOfNewestEvent(room) {
if (room.timeline.length) {
return room.timeline[room.timeline.length - 1].getTs();
}
else {
return Number.MAX_SAFE_INTEGER;
}
}
function mostRecentActivityFirst(roomList) {
return roomList.sort(function(a,b) {
return tsOfNewestEvent(b) - tsOfNewestEvent(a);
});
}
module.exports = {
mostRecentActivityFirst: mostRecentActivityFirst
};

63
src/Skinner.js Normal file
View File

@@ -0,0 +1,63 @@
/*
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.
*/
class Skinner {
constructor() {
this.components = null;
}
getComponent(name) {
if (this.components === null) {
throw new Error(
"Attempted to get a component before a skin has been loaded."+
"This is probably because either:"+
" a) Your app has not called sdk.loadSkin(), or"+
" b) A component has called getComponent at the root level"
);
}
var comp = this.components[name];
if (comp) {
return comp;
}
throw new Error("No such component: "+name);
}
load(skinObject) {
if (this.components !== null) {
throw new Error(
"Attempted to load a skin while a skin is already loaded"+
"If you want to change the active skin, call resetSkin first"
);
}
this.components = skinObject;
}
reset() {
this.components = null;
}
}
// We define one Skinner globally, because the intention is
// very much that it is a singleton. Relying on there only being one
// copy of the module can be dicey and not work as browserify's
// behaviour with multiple copies of files etc. is erratic at best.
// XXX: We can still end up with the same file twice in the resulting
// JS bundle which is nonideal.
if (global.mxSkinner === undefined) {
global.mxSkinner = new Skinner();
}
module.exports = global.mxSkinner;

299
src/SlashCommands.js Normal file
View File

@@ -0,0 +1,299 @@
/*
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("./MatrixClientPeg");
var MatrixTools = require("./MatrixTools");
var dis = require("./dispatcher");
var encryption = require("./encryption");
var reject = function(msg) {
return {
error: msg
};
};
var success = function(promise) {
return {
promise: promise
};
};
var commands = {
// Change your nickname
nick: function(room_id, args) {
if (args) {
return success(
MatrixClientPeg.get().setDisplayName(args)
);
}
return reject("Usage: /nick <display_name>");
},
encrypt: function(room_id, args) {
if (args == "on") {
var client = MatrixClientPeg.get();
var members = client.getRoom(room_id).currentState.members;
var user_ids = Object.keys(members);
return success(
encryption.enableEncryption(client, room_id, user_ids)
);
}
if (args == "off") {
var client = MatrixClientPeg.get();
return success(
encryption.disableEncryption(client, room_id)
);
}
return reject("Usage: encrypt <on/off>");
},
// Change the room topic
topic: function(room_id, args) {
if (args) {
return success(
MatrixClientPeg.get().setRoomTopic(room_id, args)
);
}
return reject("Usage: /topic <topic>");
},
// Invite a user
invite: function(room_id, args) {
if (args) {
var matches = args.match(/^(\S+)$/);
if (matches) {
return success(
MatrixClientPeg.get().invite(room_id, matches[1])
);
}
}
return reject("Usage: /invite <userId>");
},
// Join a room
join: function(room_id, args) {
if (args) {
var matches = args.match(/^(\S+)$/);
if (matches) {
var room_alias = matches[1];
if (room_alias[0] !== '#') {
return reject("Usage: /join #alias:domain");
}
if (!room_alias.match(/:/)) {
var domain = MatrixClientPeg.get().credentials.userId.replace(/^.*:/, '');
room_alias += ':' + domain;
}
// Try to find a room with this alias
var foundRoom = MatrixTools.getRoomForAlias(
MatrixClientPeg.get().getRooms(),
room_alias
);
if (foundRoom) { // we've already joined this room, view it.
dis.dispatch({
action: 'view_room',
room_id: foundRoom.roomId
});
return success();
}
else {
// attempt to join this alias.
return success(
MatrixClientPeg.get().joinRoom(room_alias).then(
function(room) {
dis.dispatch({
action: 'view_room',
room_id: room.roomId
});
})
);
}
}
}
return reject("Usage: /join <room_alias>");
},
part: function(room_id, args) {
var targetRoomId;
if (args) {
var matches = args.match(/^(\S+)$/);
if (matches) {
var room_alias = matches[1];
if (room_alias[0] !== '#') {
return reject("Usage: /part [#alias:domain]");
}
if (!room_alias.match(/:/)) {
var domain = MatrixClientPeg.get().credentials.userId.replace(/^.*:/, '');
room_alias += ':' + domain;
}
// Try to find a room with this alias
var rooms = MatrixClientPeg.get().getRooms();
for (var i = 0; i < rooms.length; i++) {
var aliasEvents = rooms[i].currentState.getStateEvents(
"m.room.aliases"
);
for (var j = 0; j < aliasEvents.length; j++) {
var aliases = aliasEvents[j].getContent().aliases || [];
for (var k = 0; k < aliases.length; k++) {
if (aliases[k] === room_alias) {
targetRoomId = rooms[i].roomId;
break;
}
}
if (targetRoomId) { break; }
}
if (targetRoomId) { break; }
}
}
if (!targetRoomId) {
return reject("Unrecognised room alias: " + room_alias);
}
}
if (!targetRoomId) targetRoomId = room_id;
return success(
MatrixClientPeg.get().leave(targetRoomId).then(
function() {
dis.dispatch({action: 'view_next_room'});
})
);
},
// Kick a user from the room with an optional reason
kick: function(room_id, args) {
if (args) {
var matches = args.match(/^(\S+?)( +(.*))?$/);
if (matches) {
return success(
MatrixClientPeg.get().kick(room_id, matches[1], matches[3])
);
}
}
return reject("Usage: /kick <userId> [<reason>]");
},
// Ban a user from the room with an optional reason
ban: function(room_id, args) {
if (args) {
var matches = args.match(/^(\S+?)( +(.*))?$/);
if (matches) {
return success(
MatrixClientPeg.get().ban(room_id, matches[1], matches[3])
);
}
}
return reject("Usage: /ban <userId> [<reason>]");
},
// Unban a user from the room
unban: function(room_id, args) {
if (args) {
var matches = args.match(/^(\S+)$/);
if (matches) {
// Reset the user membership to "leave" to unban him
return success(
MatrixClientPeg.get().unban(room_id, matches[1])
);
}
}
return reject("Usage: /unban <userId>");
},
// Define the power level of a user
op: function(room_id, args) {
if (args) {
var matches = args.match(/^(\S+?)( +(\d+))?$/);
var powerLevel = 50; // default power level for op
if (matches) {
var user_id = matches[1];
if (matches.length === 4 && undefined !== matches[3]) {
powerLevel = parseInt(matches[3]);
}
if (powerLevel !== NaN) {
var room = MatrixClientPeg.get().getRoom(room_id);
if (!room) {
return reject("Bad room ID: " + room_id);
}
var powerLevelEvent = room.currentState.getStateEvents(
"m.room.power_levels", ""
);
return success(
MatrixClientPeg.get().setPowerLevel(
room_id, user_id, powerLevel, powerLevelEvent
)
);
}
}
}
return reject("Usage: /op <userId> [<power level>]");
},
// Reset the power level of a user
deop: function(room_id, args) {
if (args) {
var matches = args.match(/^(\S+)$/);
if (matches) {
var room = MatrixClientPeg.get().getRoom(room_id);
if (!room) {
return reject("Bad room ID: " + room_id);
}
var powerLevelEvent = room.currentState.getStateEvents(
"m.room.power_levels", ""
);
return success(
MatrixClientPeg.get().setPowerLevel(
room_id, args, undefined, powerLevelEvent
)
);
}
}
return reject("Usage: /deop <userId>");
}
};
// helpful aliases
commands.j = commands.join;
module.exports = {
/**
* Process the given text for /commands and perform them.
* @param {string} roomId The room in which the command was performed.
* @param {string} input The raw text input by the user.
* @return {Object|null} An object with the property 'error' if there was an error
* processing the command, or 'promise' if a request was sent out.
* Returns null if the input didn't match a command.
*/
processInput: function(roomId, input) {
// trim any trailing whitespace, as it can confuse the parser for
// IRC-style commands
input = input.replace(/\s+$/, "");
if (input[0] === "/" && input[1] !== "/") {
var bits = input.match(/^(\S+?)( +(.*))?$/);
var cmd = bits[1].substring(1).toLowerCase();
var args = bits[3];
if (cmd === "me") return null;
if (commands[cmd]) {
return commands[cmd](roomId, args);
}
else {
return reject("Unrecognised command: " + input);
}
}
return null; // not a command
}
};

109
src/TextForEvent.js Normal file
View File

@@ -0,0 +1,109 @@
function textForMemberEvent(ev) {
// XXX: SYJS-16
var senderName = ev.sender ? ev.sender.name : ev.getSender();
var targetName = ev.target ? ev.target.name : ev.getStateKey();
var reason = ev.getContent().reason ? (
" Reason: " + ev.getContent().reason
) : "";
switch (ev.getContent().membership) {
case 'invite':
return senderName + " invited " + targetName + ".";
case 'ban':
return senderName + " banned " + targetName + "." + reason;
case 'join':
if (ev.getPrevContent() && ev.getPrevContent().membership == 'join') {
if (ev.getPrevContent().displayname && ev.getContent().displayname && ev.getPrevContent().displayname != ev.getContent().displayname) {
return ev.getSender() + " changed their display name from " +
ev.getPrevContent().displayname + " to " +
ev.getContent().displayname;
} else if (!ev.getPrevContent().displayname && ev.getContent().displayname) {
return ev.getSender() + " set their display name to " + ev.getContent().displayname;
} else if (ev.getPrevContent().displayname && !ev.getContent().displayname) {
return ev.getSender() + " removed their display name";
} else if (ev.getPrevContent().avatar_url && !ev.getContent().avatar_url) {
return ev.getSender() + " removed their profile picture";
} else if (ev.getPrevContent().avatar_url && ev.getContent().avatar_url && ev.getPrevContent().avatar_url != ev.getContent().avatar_url) {
return ev.getSender() + " changed their profile picture";
} else if (!ev.getPrevContent().avatar_url && ev.getContent().avatar_url) {
return ev.getSender() + " set a profile picture";
}
} else {
if (!ev.target) console.warn("Join message has no target! -- " + ev.getContent().state_key);
return targetName + " joined the room.";
}
return '';
case 'leave':
if (ev.getSender() === ev.getStateKey()) {
return targetName + " left the room.";
}
else if (ev.getPrevContent().membership === "ban") {
return senderName + " unbanned " + targetName + ".";
}
else if (ev.getPrevContent().membership === "join") {
return senderName + " kicked " + targetName + "." + reason;
}
else if (ev.getPrevContent().membership === "invite") {
return senderName + " withdrew " + targetName + "'s invitation." + reason;
}
else {
return targetName + " left the room.";
}
}
};
function textForTopicEvent(ev) {
var senderDisplayName = ev.sender && ev.sender.name ? ev.sender.name : ev.getSender();
return senderDisplayName + ' changed the topic to, "' + ev.getContent().topic + '"';
};
function textForMessageEvent(ev) {
var senderDisplayName = ev.sender && ev.sender.name ? ev.sender.name : ev.getSender();
var message = senderDisplayName + ': ' + ev.getContent().body;
if (ev.getContent().msgtype === "m.emote") {
message = "* " + senderDisplayName + " " + message;
} else if (ev.getContent().msgtype === "m.image") {
message = senderDisplayName + " sent an image.";
}
return message;
};
function textForCallAnswerEvent(event) {
var senderName = event.sender ? event.sender.name : "Someone";
return senderName + " answered the call.";
};
function textForCallHangupEvent(event) {
var senderName = event.sender ? event.sender.name : "Someone";
return senderName + " ended the call.";
};
function textForCallInviteEvent(event) {
var senderName = event.sender ? event.sender.name : "Someone";
// FIXME: Find a better way to determine this from the event?
var type = "voice";
if (event.getContent().offer && event.getContent().offer.sdp &&
event.getContent().offer.sdp.indexOf('m=video') !== -1) {
type = "video";
}
return senderName + " placed a " + type + " call.";
};
var handlers = {
'm.room.message': textForMessageEvent,
'm.room.topic': textForTopicEvent,
'm.room.member': textForMemberEvent,
'm.call.invite': textForCallInviteEvent,
'm.call.answer': textForCallAnswerEvent,
'm.call.hangup': textForCallHangupEvent,
};
module.exports = {
textForEvent: function(ev) {
var hdlr = handlers[ev.getType()];
if (!hdlr) return "";
return hdlr(ev);
}
}

View File

@@ -1,135 +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 Matrix = require("matrix-js-sdk");
var Room = Matrix.Room;
var CallHandler = require('matrix-react-sdk/lib/CallHandler');
// FIXME: This currently forces Vector to try to hit the matrix.org AS for conferencing.
// This is bad because it prevents people running their own ASes from being used.
// This isn't permanent and will be customisable in the future: see the proposal
// at docs/conferencing.md for more info.
var USER_PREFIX = "fs_";
var DOMAIN = "matrix.org";
function ConferenceCall(matrixClient, groupChatRoomId) {
this.client = matrixClient;
this.groupRoomId = groupChatRoomId;
this.confUserId = module.exports.getConferenceUserIdForRoom(this.groupRoomId);
}
ConferenceCall.prototype.setup = function() {
var self = this;
return this._joinConferenceUser().then(function() {
return self._getConferenceUserRoom();
}).then(function(room) {
// return a call for *this* room to be placed. We also tack on
// confUserId to speed up lookups (else we'd need to loop every room
// 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;
});
};
ConferenceCall.prototype._joinConferenceUser = function() {
// Make sure the conference user is in the group chat room
var groupRoom = this.client.getRoom(this.groupRoomId);
if (!groupRoom) {
return q.reject("Bad group room ID");
}
var member = groupRoom.getMember(this.confUserId);
if (member && member.membership === "join") {
return q();
}
return this.client.invite(this.groupRoomId, this.confUserId);
};
ConferenceCall.prototype._getConferenceUserRoom = function() {
// Use an existing 1:1 with the conference user; else make one
var rooms = this.client.getRooms();
var confRoom = null;
for (var i = 0; i < rooms.length; i++) {
var confUser = rooms[i].getMember(this.confUserId);
if (confUser && confUser.membership === "join" &&
rooms[i].getJoinedMembers().length === 2) {
confRoom = rooms[i];
break;
}
}
if (confRoom) {
return q(confRoom);
}
return this.client.createRoom({
preset: "private_chat",
invite: [this.confUserId]
}).then(function(res) {
return new Room(res.room_id);
});
};
/**
* Check if this user ID is in fact a conference bot.
* @param {string} userId The user ID to check.
* @return {boolean} True if it is a conference bot.
*/
module.exports.isConferenceUser = function(userId) {
if (userId.indexOf("@" + USER_PREFIX) !== 0) {
return false;
}
var base64part = userId.split(":")[0].substring(1 + USER_PREFIX.length);
if (base64part) {
var decoded = new Buffer(base64part, "base64").toString();
// ! $STUFF : $STUFF
return /^!.+:.+/.test(decoded);
}
return false;
};
module.exports.getConferenceUserIdForRoom = function(roomId) {
// abuse browserify's core node Buffer support (strip padding ='s)
var base64RoomId = new Buffer(roomId).toString("base64").replace(/=/g, "");
return "@" + USER_PREFIX + base64RoomId + ":" + DOMAIN;
};
module.exports.createNewMatrixCall = function(client, roomId) {
var confCall = new ConferenceCall(
client, roomId
);
return confCall.setup();
};
module.exports.getConferenceCallForRoom = function(roomId) {
// search for a conference 1:1 call for this group chat room ID
var activeCall = CallHandler.getAnyActiveCall();
if (activeCall && activeCall.confUserId) {
var thisRoomConfUserId = module.exports.getConferenceUserIdForRoom(
roomId
);
if (thisRoomConfUserId === activeCall.confUserId) {
return activeCall;
}
}
return null;
};
module.exports.ConferenceCall = ConferenceCall;
module.exports.slot = 'conference';

49
src/WhoIsTyping.js Normal file
View File

@@ -0,0 +1,49 @@
var MatrixClientPeg = require("./MatrixClientPeg");
module.exports = {
usersTypingApartFromMe: function(room) {
return this.usersTyping(
room, [MatrixClientPeg.get().credentials.userId]
);
},
/**
* Given a Room object and, optionally, a list of userID strings
* to exclude, return a list of user objects who are typing.
*/
usersTyping: function(room, exclude) {
var whoIsTyping = [];
if (exclude === undefined) {
exclude = [];
}
var memberKeys = Object.keys(room.currentState.members);
for (var i = 0; i < memberKeys.length; ++i) {
var userId = memberKeys[i];
if (room.currentState.members[userId].typing) {
if (exclude.indexOf(userId) == -1) {
whoIsTyping.push(room.currentState.members[userId]);
}
}
}
return whoIsTyping;
},
whoIsTypingString: function(room) {
var whoIsTyping = this.usersTypingApartFromMe(room);
if (whoIsTyping.length == 0) {
return null;
} else if (whoIsTyping.length == 1) {
return whoIsTyping[0].name + ' is typing';
} else {
var names = whoIsTyping.map(function(m) {
return m.name;
});
var lastPerson = names.shift();
return names.join(', ') + ' and ' + lastPerson + ' are typing';
}
}
}

View File

@@ -1,86 +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;
import structures$BottomLeftMenu from './components/structures/BottomLeftMenu';
module.exports.components['structures.BottomLeftMenu'] = structures$BottomLeftMenu;
import structures$CompatibilityPage from './components/structures/CompatibilityPage';
module.exports.components['structures.CompatibilityPage'] = structures$CompatibilityPage;
import structures$LeftPanel from './components/structures/LeftPanel';
module.exports.components['structures.LeftPanel'] = structures$LeftPanel;
import structures$RightPanel from './components/structures/RightPanel';
module.exports.components['structures.RightPanel'] = structures$RightPanel;
import structures$RoomDirectory from './components/structures/RoomDirectory';
module.exports.components['structures.RoomDirectory'] = structures$RoomDirectory;
import structures$RoomSubList from './components/structures/RoomSubList';
module.exports.components['structures.RoomSubList'] = structures$RoomSubList;
import structures$SearchBox from './components/structures/SearchBox';
module.exports.components['structures.SearchBox'] = structures$SearchBox;
import structures$ViewSource from './components/structures/ViewSource';
module.exports.components['structures.ViewSource'] = structures$ViewSource;
import views$context_menus$MessageContextMenu from './components/views/context_menus/MessageContextMenu';
module.exports.components['views.context_menus.MessageContextMenu'] = views$context_menus$MessageContextMenu;
import views$context_menus$NotificationStateContextMenu from './components/views/context_menus/NotificationStateContextMenu';
module.exports.components['views.context_menus.NotificationStateContextMenu'] = views$context_menus$NotificationStateContextMenu;
import views$context_menus$RoomTagContextMenu from './components/views/context_menus/RoomTagContextMenu';
module.exports.components['views.context_menus.RoomTagContextMenu'] = views$context_menus$RoomTagContextMenu;
import views$dialogs$ChangelogDialog from './components/views/dialogs/ChangelogDialog';
module.exports.components['views.dialogs.ChangelogDialog'] = views$dialogs$ChangelogDialog;
import views$directory$NetworkDropdown from './components/views/directory/NetworkDropdown';
module.exports.components['views.directory.NetworkDropdown'] = views$directory$NetworkDropdown;
import views$elements$ImageView from './components/views/elements/ImageView';
module.exports.components['views.elements.ImageView'] = views$elements$ImageView;
import views$elements$Spinner from './components/views/elements/Spinner';
module.exports.components['views.elements.Spinner'] = views$elements$Spinner;
import views$globals$GuestWarningBar from './components/views/globals/GuestWarningBar';
module.exports.components['views.globals.GuestWarningBar'] = views$globals$GuestWarningBar;
import views$globals$MatrixToolbar from './components/views/globals/MatrixToolbar';
module.exports.components['views.globals.MatrixToolbar'] = views$globals$MatrixToolbar;
import views$globals$NewVersionBar from './components/views/globals/NewVersionBar';
module.exports.components['views.globals.NewVersionBar'] = views$globals$NewVersionBar;
import views$login$VectorCustomServerDialog from './components/views/login/VectorCustomServerDialog';
module.exports.components['views.login.VectorCustomServerDialog'] = views$login$VectorCustomServerDialog;
import views$login$VectorLoginFooter from './components/views/login/VectorLoginFooter';
module.exports.components['views.login.VectorLoginFooter'] = views$login$VectorLoginFooter;
import views$login$VectorLoginHeader from './components/views/login/VectorLoginHeader';
module.exports.components['views.login.VectorLoginHeader'] = views$login$VectorLoginHeader;
import views$messages$DateSeparator from './components/views/messages/DateSeparator';
module.exports.components['views.messages.DateSeparator'] = views$messages$DateSeparator;
import views$messages$MessageTimestamp from './components/views/messages/MessageTimestamp';
module.exports.components['views.messages.MessageTimestamp'] = views$messages$MessageTimestamp;
import views$rooms$DNDRoomTile from './components/views/rooms/DNDRoomTile';
module.exports.components['views.rooms.DNDRoomTile'] = views$rooms$DNDRoomTile;
import views$rooms$RoomDropTarget from './components/views/rooms/RoomDropTarget';
module.exports.components['views.rooms.RoomDropTarget'] = views$rooms$RoomDropTarget;
import views$rooms$RoomTooltip from './components/views/rooms/RoomTooltip';
module.exports.components['views.rooms.RoomTooltip'] = views$rooms$RoomTooltip;
import views$rooms$SearchBar from './components/views/rooms/SearchBar';
module.exports.components['views.rooms.SearchBar'] = views$rooms$SearchBar;
import views$settings$IntegrationsManager from './components/views/settings/IntegrationsManager';
module.exports.components['views.settings.IntegrationsManager'] = views$settings$IntegrationsManager;
import views$settings$Notifications from './components/views/settings/Notifications';
module.exports.components['views.settings.Notifications'] = views$settings$Notifications;

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.
*/
'use strict';
var React = require('react');
var ReactDOM = require('react-dom');
var sdk = require('matrix-react-sdk')
var dis = require('matrix-react-sdk/lib/dispatcher');
module.exports = React.createClass({
displayName: 'BottomLeftMenu',
propTypes: {
collapsed: React.PropTypes.bool.isRequired,
},
getInitialState: function() {
return({
directoryHover : false,
roomsHover : false,
peopleHover : false,
settingsHover : false,
});
},
// Room events
onDirectoryClick: function() {
dis.dispatch({ action: 'view_room_directory' });
},
onDirectoryMouseEnter: function() {
this.setState({ directoryHover: true });
},
onDirectoryMouseLeave: function() {
this.setState({ directoryHover: false });
},
onRoomsClick: function() {
dis.dispatch({ action: 'view_create_room' });
},
onRoomsMouseEnter: function() {
this.setState({ roomsHover: true });
},
onRoomsMouseLeave: function() {
this.setState({ roomsHover: false });
},
// People events
onPeopleClick: function() {
dis.dispatch({ action: 'view_create_chat' });
},
onPeopleMouseEnter: function() {
this.setState({ peopleHover: true });
},
onPeopleMouseLeave: function() {
this.setState({ peopleHover: false });
},
// Settings events
onSettingsClick: function() {
dis.dispatch({ action: 'view_user_settings' });
},
onSettingsMouseEnter: function() {
this.setState({ settingsHover: true });
},
onSettingsMouseLeave: function() {
this.setState({ settingsHover: false });
},
// Get the label/tooltip to show
getLabel: function(label, show) {
if (show) {
var RoomTooltip = sdk.getComponent("rooms.RoomTooltip");
return <RoomTooltip className="mx_BottomLeftMenu_tooltip" label={label} />;
}
},
render: function() {
var TintableSvg = sdk.getComponent('elements.TintableSvg');
return (
<div className="mx_BottomLeftMenu">
<div className="mx_BottomLeftMenu_options">
<div className="mx_BottomLeftMenu_people" onClick={ this.onPeopleClick } onMouseEnter={ this.onPeopleMouseEnter } onMouseLeave={ this.onPeopleMouseLeave } >
<TintableSvg src="img/icons-people.svg" width="25" height="25" />
{ this.getLabel("Start chat", this.state.peopleHover) }
</div>
<div className="mx_BottomLeftMenu_directory" onClick={ this.onDirectoryClick } onMouseEnter={ this.onDirectoryMouseEnter } onMouseLeave={ this.onDirectoryMouseLeave } >
<TintableSvg src="img/icons-directory.svg" width="25" height="25"/>
{ this.getLabel("Room directory", this.state.directoryHover) }
</div>
<div className="mx_BottomLeftMenu_createRoom" onClick={ this.onRoomsClick } onMouseEnter={ this.onRoomsMouseEnter } onMouseLeave={ this.onRoomsMouseLeave } >
<TintableSvg src="img/icons-create-room.svg" width="25" height="25" />
{ this.getLabel("Create new room", this.state.roomsHover) }
</div>
<div className="mx_BottomLeftMenu_settings" onClick={ this.onSettingsClick } onMouseEnter={ this.onSettingsMouseEnter } onMouseLeave={ this.onSettingsMouseLeave } >
<TintableSvg src="img/icons-settings.svg" width="25" height="25" />
{ this.getLabel("Settings", this.state.settingsHover) }
</div>
</div>
</div>
);
}
});

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 Riot.</p>
<p>
Riot 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" showVoice={true} 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,279 +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');
var Modal = require('matrix-react-sdk/lib/Modal');
module.exports = React.createClass({
displayName: 'RightPanel',
propTypes: {
userId: React.PropTypes.string, // if showing an orphaned MemberInfo page, this is set
roomId: React.PropTypes.string, // if showing panels for a given room, this is set
collapsed: React.PropTypes.bool, // currently unused property to request for a minimized view of the panel
},
Phase : {
MemberList: 'MemberList',
FilePanel: 'FilePanel',
NotificationPanel: 'NotificationPanel',
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.state.phase !== this.Phase.MemberList) {
this.setState({ phase: this.Phase.MemberList });
dis.dispatch({
action: 'show_right_panel',
});
}
else {
dis.dispatch({
action: 'hide_right_panel',
});
}
},
onFileListButtonClick: function() {
if (this.props.collapsed || this.state.phase !== this.Phase.FilePanel) {
this.setState({ phase: this.Phase.FilePanel });
dis.dispatch({
action: 'show_right_panel',
});
}
else {
dis.dispatch({
action: 'hide_right_panel',
});
}
},
onNotificationListButtonClick: function() {
if (this.props.collapsed || this.state.phase !== this.Phase.NotificationPanel) {
this.setState({ phase: this.Phase.NotificationPanel });
dis.dispatch({
action: 'show_right_panel',
});
}
else {
dis.dispatch({
action: 'hide_right_panel',
});
}
},
onInviteButtonClick: function() {
if (MatrixClientPeg.get().isGuest()) {
var NeedToRegisterDialog = sdk.getComponent("dialogs.NeedToRegisterDialog");
Modal.createDialog(NeedToRegisterDialog, {
title: "Please Register",
description: "Guest users can't invite users. Please register to invite."
});
return;
}
// call ChatInviteDialog
dis.dispatch({
action: 'view_invite',
roomId: this.props.roomId,
});
},
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 NotificationPanel = sdk.getComponent('structures.NotificationPanel');
var FilePanel = sdk.getComponent('structures.FilePanel');
var TintableSvg = sdk.getComponent("elements.TintableSvg");
var buttonGroup;
var inviteGroup;
var panel;
var filesHighlight;
var membersHighlight;
var notificationsHighlight;
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.FilePanel) {
filesHighlight = <div className="mx_RightPanel_headerButton_highlight"></div>;
}
else if (this.state.phase == this.Phase.NotificationPanel) {
notificationsHighlight = <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);
var user_is_in_room;
if (room) {
membersBadge = room.getJoinedMembers().length;
user_is_in_room = room.hasMembershipState(
MatrixClientPeg.get().credentials.userId, 'join'
);
}
if (user_is_in_room) {
inviteGroup =
<div className="mx_RightPanel_invite" onClick={ this.onInviteButtonClick } >
<div className="mx_RightPanel_icon" >
<TintableSvg src="img/icon-invite-people.svg" width="35" height="35" />
</div>
<div className="mx_RightPanel_message">Invite to this room</div>
</div>;
}
}
if (this.props.roomId) {
buttonGroup =
<div className="mx_RightPanel_headerButtonGroup">
<div className="mx_RightPanel_headerButton" title="Members" onClick={ this.onMemberListButtonClick }>
<div className="mx_RightPanel_headerButton_badge">{ membersBadge ? membersBadge : <span>&nbsp;</span>}</div>
<TintableSvg src="img/icons-people.svg" width="25" height="25"/>
{ membersHighlight }
</div>
<div className="mx_RightPanel_headerButton mx_RightPanel_filebutton" title="Files" onClick={ this.onFileListButtonClick }>
<div className="mx_RightPanel_headerButton_badge">&nbsp;</div>
<TintableSvg src="img/icons-files.svg" width="25" height="25"/>
{ filesHighlight }
</div>
<div className="mx_RightPanel_headerButton mx_RightPanel_notificationbutton" title="Notifications" onClick={ this.onNotificationListButtonClick }>
<div className="mx_RightPanel_headerButton_badge">&nbsp;</div>
<TintableSvg src="img/icons-notifications.svg" width="25" height="25"/>
{ notificationsHighlight }
</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} />
}
else if (this.state.phase == this.Phase.NotificationPanel) {
panel = <NotificationPanel />
}
else if (this.state.phase == this.Phase.FilePanel) {
panel = <FilePanel roomId={this.props.roomId} />
}
}
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">
{ inviteGroup }
</div>
</aside>
);
}
});

View File

@@ -1,561 +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');
var q = require('q');
import {instanceForInstanceId, protocolNameForInstanceId} from '../../utils/DirectoryUtils';
linkifyMatrix(linkify);
module.exports = React.createClass({
displayName: 'RoomDirectory',
propTypes: {
config: React.PropTypes.object,
},
getDefaultProps: function() {
return {
config: {},
}
},
getInitialState: function() {
return {
publicRooms: [],
loading: true,
protocolsLoading: true,
instanceId: null,
includeAll: false,
roomServer: null,
filterString: null,
}
},
componentWillMount: function() {
this.nextBatch = null;
this.filterTimeout = null;
this.scrollPanel = null;
this.protocols = null;
this.setState({protocolsLoading: true});
MatrixClientPeg.get().getThirdpartyProtocols().done((response) => {
this.protocols = response;
this.setState({protocolsLoading: false});
}, (err) => {
this.setState({protocolsLoading: false});
if (MatrixClientPeg.get().isGuest()) {
// Guests currently aren't allowed to use this API, so
// ignore this as otherwise this error is literally the
// thing you see when loading the client!
return;
}
const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
Modal.createDialog(ErrorDialog, {
title: "Failed to get protocol list from Home Server",
description: "The Home Server may be too old to support third party networks",
});
});
// dis.dispatch({
// action: 'ui_opacity',
// sideOpacity: 0.3,
// middleOpacity: 0.3,
// });
},
componentWillUnmount: function() {
// dis.dispatch({
// action: 'ui_opacity',
// sideOpacity: 1.0,
// middleOpacity: 1.0,
// });
},
refreshRoomList: function() {
this.nextBatch = null;
this.setState({
publicRooms: [],
loading: true,
});
this.getMoreRooms().done();
},
getMoreRooms: function() {
if (!MatrixClientPeg.get()) return q();
const my_filter_string = this.state.filterString;
const my_server = this.state.roomServer;
// remember the next batch token when we sent the request
// too. If it's changed, appending to the list will corrupt it.
const my_next_batch = this.nextBatch;
const opts = {limit: 20};
if (my_server != MatrixClientPeg.getHomeServerName()) {
opts.server = my_server;
}
if (this.state.instanceId) {
opts.third_party_instance_id = this.state.instanceId;
} else if (this.state.includeAll) {
opts.include_all_networks = true;
}
if (this.nextBatch) opts.since = this.nextBatch;
if (my_filter_string) opts.filter = { generic_search_term: my_filter_string } ;
return MatrixClientPeg.get().publicRooms(opts).then((data) => {
if (
my_filter_string != this.state.filterString ||
my_server != this.state.roomServer ||
my_next_batch != this.nextBatch)
{
// if the filter or server has changed since this request was sent,
// throw away the result (don't even clear the busy flag
// since we must still have a request in flight)
return;
}
this.nextBatch = data.next_batch;
this.setState((s) => {
s.publicRooms.push(...data.chunk);
s.loading = false;
return s;
});
return Boolean(data.next_batch);
}, (err) => {
if (
my_filter_string != this.state.filterString ||
my_server != this.state.roomServer ||
my_next_batch != this.nextBatch)
{
// as above: we don't care about errors for old
// requests either
return;
}
this.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
});
});
},
/**
* 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.refreshRoomList();
}, function(err) {
modal.close();
this.refreshRoomList();
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);
}
},
onOptionChange: function(server, instanceId, includeAll) {
// clear next batch so we don't try to load more rooms
this.nextBatch = null;
this.setState({
// Clear the public rooms out here otherwise we needlessly
// spend time filtering lots of rooms when we're about to
// to clear the list anyway.
publicRooms: [],
roomServer: server,
instanceId: instanceId,
includeAll: includeAll,
}, this.refreshRoomList);
// We also refresh the room list each time even though this
// filtering is client-side. It hopefully won't be client side
// for very long, and we may have fetched a thousand rooms to
// find the five gitter ones, at which point we do not want
// to render all those rooms when switching back to 'all networks'.
// Easiest to just blow away the state & re-fetch.
},
onFillRequest: function(backwards) {
if (backwards || !this.nextBatch) return q(false);
return this.getMoreRooms();
},
onFilterChange: function(alias) {
this.setState({
filterString: alias || null,
});
// don't send the request for a little bit,
// no point hammering the server with a
// request for every keystroke, let the
// user finish typing.
if (this.filterTimeout) {
clearTimeout(this.filterTimeout);
}
this.filterTimeout = setTimeout(() => {
this.filterTimeout = null;
this.refreshRoomList();
}, 700);
},
onFilterClear: function() {
// update immediately
this.setState({
filterString: null,
}, this.refreshRoomList);
if (this.filterTimeout) {
clearTimeout(this.filterTimeout);
}
},
onJoinClick: function(alias) {
// If we don't have a particular instance id selected, just show that rooms alias
if (!this.state.instanceId) {
// If the user specified an alias without a domain, add on whichever server is selected
// in the dropdown
if (alias.indexOf(':') == -1) {
alias = alias + ':' + this.state.roomServer;
}
this.showRoomAlias(alias);
} else {
// This is a 3rd party protocol. Let's see if we can join it
const protocolName = protocolNameForInstanceId(this.protocols, this.state.instanceId);
const instance = instanceForInstanceId(this.protocols, this.state.instanceId);
const fields = protocolName ? this._getFieldsForThirdPartyLocation(alias, this.protocols[protocolName], instance) : null;
if (!fields) {
const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
Modal.createDialog(ErrorDialog, {
title: "Unable to join network",
description: "Riot does not know how to join a room on this network",
});
return;
}
MatrixClientPeg.get().getThirdpartyLocation(protocolName, fields).done((resp) => {
if (resp.length > 0 && resp[0].alias) {
this.showRoomAlias(resp[0].alias);
} else {
const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
Modal.createDialog(ErrorDialog, {
title: "Room not found",
description: "Couldn't find a matching Matrix room",
});
}
}, (e) => {
const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
Modal.createDialog(ErrorDialog, {
title: "Fetching third party location failed",
description: "Unable to look up room ID from server",
});
});
}
},
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() {
var BaseAvatar = sdk.getComponent('avatars.BaseAvatar');
if (!this.state.publicRooms) return [];
var rooms = this.state.publicRooms;
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.push(
<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;
},
collectScrollPanel: function(element) {
this.scrollPanel = element;
},
_stringLooksLikeId: function(s, field_type) {
let pat = /^#[^\s]+:[^\s]/;
if (field_type && field_type.regexp) {
pat = new RegExp(field_type.regexp);
}
return pat.test(s);
},
_getFieldsForThirdPartyLocation: function(userInput, protocol, instance) {
// make an object with the fields specified by that protocol. We
// require that the values of all but the last field come from the
// instance. The last is the user input.
const requiredFields = protocol.location_fields;
if (!requiredFields) return null;
const fields = {};
for (let i = 0; i < requiredFields.length - 1; ++i) {
const thisField = requiredFields[i];
if (instance.fields[thisField] === undefined) return null;
fields[thisField] = instance.fields[thisField];
}
fields[requiredFields[requiredFields.length - 1]] = userInput;
return fields;
},
render: function() {
const SimpleRoomHeader = sdk.getComponent('rooms.SimpleRoomHeader');
const Loader = sdk.getComponent("elements.Spinner");
if (this.state.protocolsLoading) {
return (
<div className="mx_RoomDirectory">
<SimpleRoomHeader title="Directory" />
<Loader />
</div>
);
}
let content;
if (this.state.loading) {
content = <div className="mx_RoomDirectory">
<Loader />
</div>;
} else {
const rows = this.getRows();
// we still show the scrollpanel, at least for now, because
// otherwise we don't fetch more because we don't get a fill
// request from the scrollpanel because there isn't one
let scrollpanel_content;
if (rows.length == 0) {
scrollpanel_content = <i>No rooms to show</i>;
} else {
scrollpanel_content = <table ref="directory_table" className="mx_RoomDirectory_table">
<tbody>
{ this.getRows() }
</tbody>
</table>;
}
const ScrollPanel = sdk.getComponent("structures.ScrollPanel");
content = <ScrollPanel ref={this.collectScrollPanel}
className="mx_RoomDirectory_tableWrapper"
onFillRequest={ this.onFillRequest }
stickyBottom={false}
startAtBottom={false}
onResize={function(){}}
>
{ scrollpanel_content }
</ScrollPanel>;
}
const protocolName = protocolNameForInstanceId(this.protocols, this.state.instanceId);
let instance_expected_field_type;
if (
protocolName &&
this.protocols &&
this.protocols[protocolName] &&
this.protocols[protocolName].location_fields.length > 0 &&
this.protocols[protocolName].field_types
) {
const last_field = this.protocols[protocolName].location_fields.slice(-1)[0];
instance_expected_field_type = this.protocols[protocolName].field_types[last_field];
}
let placeholder = 'Search for a room';
if (!this.state.instanceId) {
placeholder = '#example:' + this.state.roomServer;
} else if (instance_expected_field_type) {
placeholder = instance_expected_field_type.placeholder;
}
let showJoinButton = this._stringLooksLikeId(this.state.filterString, instance_expected_field_type);
if (protocolName) {
const instance = instanceForInstanceId(this.protocols, this.state.instanceId);
if (this._getFieldsForThirdPartyLocation(this.state.filterString, this.protocols[protocolName], instance) === null) {
showJoinButton = false;
}
}
const NetworkDropdown = sdk.getComponent('directory.NetworkDropdown');
const DirectorySearchBox = sdk.getComponent('elements.DirectorySearchBox');
return (
<div className="mx_RoomDirectory">
<SimpleRoomHeader title="Directory" />
<div className="mx_RoomDirectory_list">
<div className="mx_RoomDirectory_listheader">
<DirectorySearchBox
className="mx_RoomDirectory_searchbox"
onChange={this.onFilterChange} onClear={this.onFilterClear} onJoinClick={this.onJoinClick}
placeholder={placeholder} showJoinButton={showJoinButton}
/>
<NetworkDropdown config={this.props.config} protocols={this.protocols} onOptionChange={this.onOptionChange} />
</div>
{content}
</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] : "");
}

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