Compare commits
109 Commits
release-v0
...
v0.0.2
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
966f44baa1 | ||
|
|
184af9df76 | ||
|
|
2a1b9cd716 | ||
|
|
15af44f5fc | ||
|
|
e6f9c6e777 | ||
|
|
16ddb47466 | ||
|
|
66b577dc89 | ||
|
|
77d1b9af04 | ||
|
|
24ac801417 | ||
|
|
11ef1ac336 | ||
|
|
a1444d3214 | ||
|
|
a2b77ad5b5 | ||
|
|
5a760b71d0 | ||
|
|
03dfd57a79 | ||
|
|
7e93b75aa0 | ||
|
|
549d992293 | ||
|
|
ac5111c162 | ||
|
|
0488f03b5a | ||
|
|
d4a5ab11d4 | ||
|
|
d1af5a2232 | ||
|
|
feaf2a319b | ||
|
|
3b988b0eac | ||
|
|
98ea35253a | ||
|
|
8ff7d87b38 | ||
|
|
48f162b9df | ||
|
|
3d8d9bac8e | ||
|
|
1041ee654e | ||
|
|
78f2f7cfd0 | ||
|
|
6baf405a05 | ||
|
|
02a2e06d52 | ||
|
|
9e596ebb75 | ||
|
|
f7d3d4f9a9 | ||
|
|
d12ca92ea7 | ||
|
|
fc333067c2 | ||
|
|
030124a59a | ||
|
|
4e0d930014 | ||
|
|
f6d577d0c6 | ||
|
|
8228a7d485 | ||
|
|
c5e3891a5a | ||
|
|
3f67d8541f | ||
|
|
05d19121d8 | ||
|
|
e158eec94d | ||
|
|
53a7f4b3a8 | ||
|
|
0791cac572 | ||
|
|
05f7a3b4d1 | ||
|
|
79e468217a | ||
|
|
27ca7b48f7 | ||
|
|
b8dd2452db | ||
|
|
a1892ee963 | ||
|
|
ad313d7711 | ||
|
|
55f656f150 | ||
|
|
711272a7c9 | ||
|
|
2d3b87d56d | ||
|
|
2bce4e4d62 | ||
|
|
54048ee37c | ||
|
|
7de136a930 | ||
|
|
5004a3a5b3 | ||
|
|
cb89d3760a | ||
|
|
b68665ead5 | ||
|
|
9fb5702c2f | ||
|
|
8af6c2275b | ||
|
|
3792d5494a | ||
|
|
3be50e327d | ||
|
|
4472f63b5f | ||
|
|
6348c2cf99 | ||
|
|
bc2eca16f9 | ||
|
|
fe369858b7 | ||
|
|
56530d80d7 | ||
|
|
d172aaf41f | ||
|
|
5af43dc6a9 | ||
|
|
96627d4477 | ||
|
|
3838569625 | ||
|
|
b32658cfd0 | ||
|
|
980ce7fdae | ||
|
|
49c5f7cb95 | ||
|
|
1b82d92fa1 | ||
|
|
65498600de | ||
|
|
28c4a648be | ||
|
|
e2c9afb278 | ||
|
|
29d2ed7191 | ||
|
|
82aa603596 | ||
|
|
a8eb93bd6f | ||
|
|
31ee667102 | ||
|
|
b9538a077c | ||
|
|
343de6245f | ||
|
|
08b5888d03 | ||
|
|
abeed92501 | ||
|
|
6eb18f0268 | ||
|
|
d938ba70d3 | ||
|
|
88aaf82c88 | ||
|
|
f3b30477ce | ||
|
|
a4cbbf0d92 | ||
|
|
25ab56106a | ||
|
|
6cca5f4c05 | ||
|
|
aba4c1e9af | ||
|
|
2d0c8ac9ff | ||
|
|
f3b9f8c799 | ||
|
|
9b73d6ed6d | ||
|
|
a06e1f23ea | ||
|
|
635041470f | ||
|
|
a124e53a9a | ||
|
|
6cc88e4ef3 | ||
|
|
e1a6ede17b | ||
|
|
8fbce5fce8 | ||
|
|
2d9419c380 | ||
|
|
bd0db01515 | ||
|
|
517bb01f33 | ||
|
|
ec8a815688 | ||
|
|
5a87b9759f |
4
.babelrc
@@ -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
@@ -1,14 +1,2 @@
|
|||||||
/build
|
node_modules
|
||||||
/cert.pem
|
lib
|
||||||
/dist
|
|
||||||
/karma-reports
|
|
||||||
/key.pem
|
|
||||||
/lib
|
|
||||||
/node_modules
|
|
||||||
/packages/
|
|
||||||
/webapp
|
|
||||||
/.npmrc
|
|
||||||
.DS_Store
|
|
||||||
npm-debug.log
|
|
||||||
electron/dist
|
|
||||||
electron/pub
|
|
||||||
|
|||||||
@@ -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"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
example
|
||||||
|
examples
|
||||||
|
.module-cache
|
||||||
|
|||||||
@@ -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)
|
|
||||||
12
AUTHORS.rst
@@ -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
|
|
||||||
591
CHANGELOG.md
@@ -1,591 +0,0 @@
|
|||||||
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
|
|
||||||
@@ -1,4 +0,0 @@
|
|||||||
Contributing code to Vector
|
|
||||||
===========================
|
|
||||||
|
|
||||||
Vector follows the same pattern as https://github.com/matrix-org/synapse/blob/master/CONTRIBUTING.rst
|
|
||||||
375
README.md
@@ -1,291 +1,142 @@
|
|||||||
Riot
|
matrix-react-sdk
|
||||||
====
|
================
|
||||||
|
|
||||||
Riot (formerly known as Vector) is a Matrix web client built using the Matrix
|
This is a react-based SDK for inserting a Matrix chat/voip client into a web page.
|
||||||
React SDK (https://github.com/matrix-org/matrix-react-sdk).
|
|
||||||
|
|
||||||
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
|
Skins are modules are exported from such a package in the `lib` directory.
|
||||||
https://riot.im/app. The develop branch is continuously deployed by Jenkins at
|
`lib/skins` contains one directory per-skin, named after the skin, and the
|
||||||
https://riot.im/develop for those who like living dangerously.
|
`modules` directory contains modules as their javascript files.
|
||||||
|
|
||||||
To host your own copy of Riot, the quickest bet is to use a pre-built
|
A basic skin is provided in the matrix-react-skin package. This also contains
|
||||||
released version of Riot:
|
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
|
You can use matrix-react-sdk directly, but to do this you would have to provide
|
||||||
1. Untar the tarball on your web server
|
'views' for each UI component. To get started quickly, use matrix-react-skin.
|
||||||
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!
|
|
||||||
|
|
||||||
Note that Chrome does not allow microphone or webcam access for sites served
|
How to customise the SDK
|
||||||
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 encodes behaviour
|
|
||||||
on the room directory screen for filtering the list by server / network type and joining
|
|
||||||
third party networks. This config section will disappear once APIs are available to
|
|
||||||
get this information for home servers. This section is optional.
|
|
||||||
1. `roomDirectory.servers`: List of other Home Servers' directories to include in the drop
|
|
||||||
down list. Optional.
|
|
||||||
1. `roomDirectory.serverConfig`: Config for each server in `roomDirectory.servers`. Optional.
|
|
||||||
1. `roomDirectory.serverConfig.<server_name>.networks`: List of networks (named
|
|
||||||
in `roomDirectory.networks`) to include for this server. Optional.
|
|
||||||
1. `roomDirectory.networks`: config for each network type. Optional.
|
|
||||||
1. `roomDirectory.<network_type>.name`: Human-readable name for the network. Required.
|
|
||||||
1. `roomDirectory.<network_type>.protocol`: Protocol as given by the server in
|
|
||||||
`/_matrix/client/unstable/thirdparty/protocols` response. Required to be able to join
|
|
||||||
this type of third party network.
|
|
||||||
1. `roomDirectory.<network_type>.domain`: Domain as given by the server in
|
|
||||||
`/_matrix/client/unstable/thirdparty/protocols` response, if present. Required to be
|
|
||||||
able to join this type of third party network, if present in `thirdparty/protocols`.
|
|
||||||
1. `roomDirectory.<network_type>.portalRoomPattern`: Regular expression matching aliases
|
|
||||||
for portal rooms to locations on this network. Required.
|
|
||||||
1. `roomDirectory.<network_type>.icon`: URL to an icon to be displayed for this network. Required.
|
|
||||||
1. `roomDirectory.<network_type>.example`: Textual example of a location on this network,
|
|
||||||
eg. '#channel' for an IRC network. Optional.
|
|
||||||
1. `roomDirectory.<network_type>.nativePattern`: Regular expression that matches a
|
|
||||||
valid location on this network. This is used as a hint to the user to indicate
|
|
||||||
when a valid location has been entered so it's not necessary for this to be
|
|
||||||
exactly correct. Optional.
|
|
||||||
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 `install`
|
|
||||||
and `update`.
|
|
||||||
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
|
|
||||||
========================
|
========================
|
||||||
|
|
||||||
Riot can also be run as a desktop app, wrapped in electron. You can download a
|
The SDK uses the 'atomic' design pattern as seen at http://patternlab.io to
|
||||||
pre-built version from https://riot.im/download/desktop/ or, if you prefer,
|
encourage a very modular and reusable architecture, making it easy to
|
||||||
built it yourself.
|
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:
|
* The UI of the app is strictly split up into a hierarchy of components.
|
||||||
```
|
|
||||||
npm install
|
* Each component has its own:
|
||||||
npm install electron
|
* View object defined as a React javascript class containing embedded
|
||||||
node_modules/.bin/electron .
|
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:
|
Good separation between the components is maintained by adopting various best
|
||||||
* dmg + zip for macOS
|
practices that anyone working with the SDK needs to be be aware of and uphold:
|
||||||
* exe + nupkg for Windows
|
|
||||||
* deb for Linux
|
|
||||||
But this can be customised by editing the `build` section of package.json
|
|
||||||
as per https://github.com/electron-userland/electron-builder/wiki/Options
|
|
||||||
|
|
||||||
See https://github.com/electron-userland/electron-builder/wiki/Multi-Platform-Build
|
* Views are named with upper camel case (e.g. molecules/MessageTile.js)
|
||||||
for dependencies required for building packages for various platforms.
|
|
||||||
|
|
||||||
The only platform that can build packages for all three platforms is macOS:
|
* The view's CSS file MUST have the same name (e.g. molecules/MessageTile.css)
|
||||||
```
|
|
||||||
brew install wine --without-x11
|
|
||||||
brew install mono
|
|
||||||
npm install
|
|
||||||
npm run build:electron
|
|
||||||
```
|
|
||||||
|
|
||||||
For other packages, use electron-builder manually. For example, to build a package
|
* Per-view CSS is optional - it could choose to inherit all its styling from
|
||||||
for 64 bit Linux:
|
the context of the rest of the app, although this is unusual for any but
|
||||||
```
|
the simplest atoms and molecules.
|
||||||
npm install
|
|
||||||
npm run build
|
|
||||||
node_modules/.bin/build -l --x64
|
|
||||||
```
|
|
||||||
|
|
||||||
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:
|
* CSS classes use upper camel case when they describe React components - e.g.
|
||||||
* https://github.com/krisak/vector-electron-desktop
|
.mx_MessageTile is the selector for the CSS applied to a MessageTile view.
|
||||||
* @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/
|
|
||||||
```
|
|
||||||
|
|
||||||
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
|
* We deliberately use vanilla CSS 3.0 to avoid adding any more magic
|
||||||
for `matrix-react-sdk` at https://github.com/matrix-org/matrix-react-sdk, which
|
dependencies into the mix than we already have. App developers are welcome
|
||||||
also defines the design, architecture and style for Riot too.
|
to use whatever floats their boat however.
|
||||||
|
|
||||||
The idea of Riot is to be a relatively lightweight "skin" of customisations on
|
* The CSS for a component can however override the rules for child components.
|
||||||
top of the underlying `matrix-react-sdk`. `matrix-react-sdk` provides both the
|
For instance, .mx_RoomList .mx_RoomTile {} would be the selector to override
|
||||||
higher and lower level React components useful for building Matrix communication
|
styles of RoomTiles when viewed in the context of a RoomList view.
|
||||||
apps using React.
|
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
|
* Components should render only within the bounding box of their outermost DOM
|
||||||
the `component-index.js` for the app (used in future for skinning)
|
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
|
* We don't use the atomify library itself, as React already provides most
|
||||||
development on Riot forcing `matrix-react-sdk` to move fast at the expense of
|
of the modularity requirements it brings to the table.
|
||||||
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.
|
|
||||||
|
|
||||||
Please note that Riot is intended to run correctly without access to the public
|
With all this in mind, here's how you go about skinning the react SDK UI
|
||||||
internet. So please don't depend on resources (JS libs, CSS, images, fonts)
|
components to embed a Matrix client into your app:
|
||||||
hosted by external CDNs or servers but instead please package all dependencies
|
|
||||||
into Riot itself.
|
|
||||||
|
|
||||||
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
|
To Create Your Own Skin
|
||||||
`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
|
To actually change the look of a skin, you can create a base skin (which
|
||||||
having to manually rebuild each time.
|
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`
|
If you make a derived skin, you only need copy the files you wish to customise.
|
||||||
1. `pushd matrix-js-sdk`
|
|
||||||
1. `git checkout develop`
|
|
||||||
1. `npm install`
|
|
||||||
1. `npm install source-map-loader` # because webpack is made of fail (https://github.com/webpack/webpack/issues/1472)
|
|
||||||
1. `popd`
|
|
||||||
|
|
||||||
Then similarly with `matrix-react-sdk`:
|
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`
|
Now you have the basis of a skin, you need to generate a skindex.json file. The
|
||||||
1. `pushd matrix-react-sdk`
|
`reskindex.js` tool in matrix-react-sdk does this for you. It is suggested that
|
||||||
1. `git checkout develop`
|
you add an npm script to run this, as in matrix-react-skin.
|
||||||
1. `npm install`
|
|
||||||
1. `rm -r node_modules/matrix-js-sdk; ln -s ../../matrix-js-sdk node_modules/`
|
|
||||||
1. `popd`
|
|
||||||
|
|
||||||
Finally, build and start Riot itself:
|
For more specific detail on any of these steps, look at matrix-react-skin.
|
||||||
|
|
||||||
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)
|
|
||||||
|
|||||||
@@ -1,71 +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"
|
|
||||||
],
|
|
||||||
"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": "//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": "//matrix.org/_matrix/media/v1/download/matrix.org/DHLHpDDgWNNejFmrewvwEAHX",
|
|
||||||
"example": "#channel",
|
|
||||||
"nativePattern": "^#[^\\s]+$"
|
|
||||||
},
|
|
||||||
"irc:mozilla": {
|
|
||||||
"protocol": "irc",
|
|
||||||
"domain": "irc.mozilla.org",
|
|
||||||
"portalRoomPattern": "#mozilla_.*:matrix.org",
|
|
||||||
"name": "Mozilla",
|
|
||||||
"icon": "//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": "//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": "//matrix.org/_matrix/media/v1/download/matrix.org/DHLHpDDgWNNejFmrewvwEAHX",
|
|
||||||
"example": "#channel",
|
|
||||||
"nativePattern": "^#[^\\s]+$"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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.
|
|
||||||
|
Before Width: | Height: | Size: 102 KiB |
|
Before Width: | Height: | Size: 7.2 KiB |
|
Before Width: | Height: | Size: 673 B |
|
Before Width: | Height: | Size: 1.1 KiB |
|
Before Width: | Height: | Size: 14 KiB |
|
Before Width: | Height: | Size: 2.6 KiB |
|
Before Width: | Height: | Size: 30 KiB |
|
Before Width: | Height: | Size: 3.6 KiB |
|
Before Width: | Height: | Size: 5.5 KiB |
|
Before Width: | Height: | Size: 4.4 KiB |
|
Before Width: | Height: | Size: 102 KiB |
|
Before Width: | Height: | Size: 14 KiB |
@@ -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.
|
|
||||||
@@ -1,72 +0,0 @@
|
|||||||
{
|
|
||||||
"update_base_url": "https://riot.im/download/desktop/",
|
|
||||||
"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]+$"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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');
|
|
||||||
@@ -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;
|
|
||||||
@@ -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)
|
|
||||||
|
|
||||||
136
karma.conf.js
@@ -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',
|
|
||||||
},
|
|
||||||
});
|
|
||||||
};
|
|
||||||
163
package.json
@@ -1,154 +1,41 @@
|
|||||||
{
|
{
|
||||||
"name": "riot-web",
|
"name": "matrix-react-sdk",
|
||||||
"productName": "Riot",
|
"version": "0.0.2",
|
||||||
"main": "electron/src/electron-main.js",
|
"description": "SDK for matrix.org using React",
|
||||||
"version": "0.9.1-release",
|
"author": "matrix.org",
|
||||||
"description": "A feature-rich client for Matrix.org",
|
|
||||||
"author": "Vector Creations Ltd.",
|
|
||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "https://github.com/vector-im/riot-web"
|
"url": "https://github.com/matrix-org/matrix-react-sdk"
|
||||||
},
|
},
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
"files": [
|
"main": "lib/index.js",
|
||||||
"AUTHORS.rst",
|
"bin": {
|
||||||
"CONTRIBUTING.rst",
|
"reskindex": "./reskindex.js"
|
||||||
"deploy",
|
},
|
||||||
"docs",
|
|
||||||
"karma.conf.js",
|
|
||||||
"lib",
|
|
||||||
"release.sh",
|
|
||||||
"scripts",
|
|
||||||
"src",
|
|
||||||
"test",
|
|
||||||
"webpack.config.js"
|
|
||||||
],
|
|
||||||
"style": "bundle.css",
|
|
||||||
"matrix-react-parent": "matrix-react-sdk",
|
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"reskindex": "reskindex -h src/header",
|
"build": "babel src -d lib --source-maps",
|
||||||
"build:res": "cpx \"{src/skins/vector/fonts,src/skins/vector/img}/**\" webapp/ && cpx \"{res/media,res/vector-icons}/**\" webapp/",
|
"start": "babel src -w -d lib --source-maps",
|
||||||
"build:config": "cpx config.json webapp/",
|
"clean": "rimraf lib",
|
||||||
"build:emojione": "cpx \"node_modules/emojione/assets/svg/*\" webapp/emojione/svg/",
|
"prepublish": "npm run build"
|
||||||
"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"
|
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"babel-polyfill": "^6.5.0",
|
|
||||||
"babel-runtime": "^6.11.6",
|
|
||||||
"browser-request": "^0.3.3",
|
|
||||||
"classnames": "^2.1.2",
|
"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",
|
"filesize": "^3.1.2",
|
||||||
"flux": "~2.0.3",
|
"flux": "^2.0.3",
|
||||||
"gemini-scrollbar": "matrix-org/gemini-scrollbar#b302279",
|
"glob": "^5.0.14",
|
||||||
"gfm.css": "^1.1.1",
|
"linkifyjs": "^2.0.0-beta.4",
|
||||||
"highlight.js": "^9.0.0",
|
"matrix-js-sdk": "^0.3.0",
|
||||||
"linkifyjs": "^2.1.3",
|
"optimist": "^0.6.1",
|
||||||
"matrix-js-sdk": "0.7.1",
|
|
||||||
"matrix-react-sdk": "0.8.1",
|
|
||||||
"modernizr": "^3.1.0",
|
|
||||||
"q": "^1.4.1",
|
"q": "^1.4.1",
|
||||||
"react": "^15.4.0",
|
"react": "^0.13.3",
|
||||||
"react-dnd": "^2.1.4",
|
"react-loader": "^1.4.0"
|
||||||
"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"
|
|
||||||
},
|
},
|
||||||
|
"//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": {
|
"devDependencies": {
|
||||||
"babel-cli": "^6.5.2",
|
"babel": "^5.8.23",
|
||||||
"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",
|
|
||||||
"rimraf": "^2.4.3",
|
"rimraf": "^2.4.3",
|
||||||
"source-map-loader": "^0.1.5",
|
"json-loader": "^0.5.3",
|
||||||
"webpack": "^1.12.14",
|
"source-map-loader": "^0.1.5"
|
||||||
"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"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
12
release.sh
@@ -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 "$@"
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
signing_id: packages@riot.im
|
|
||||||
|
Before Width: | Height: | Size: 8.1 KiB |
|
Before Width: | Height: | Size: 11 KiB |
|
Before Width: | Height: | Size: 1.8 KiB |
|
Before Width: | Height: | Size: 2.6 KiB |
|
Before Width: | Height: | Size: 4.1 KiB |
|
Before Width: | Height: | Size: 5.5 KiB |
|
Before Width: | Height: | Size: 6.4 KiB |
|
Before Width: | Height: | Size: 6.7 KiB |
|
Before Width: | Height: | Size: 8.1 KiB |
|
Before Width: | Height: | Size: 8.5 KiB |
|
Before Width: | Height: | Size: 10 KiB |
|
Before Width: | Height: | Size: 3.2 KiB |
|
Before Width: | Height: | Size: 3.3 KiB |
|
Before Width: | Height: | Size: 4.1 KiB |
|
Before Width: | Height: | Size: 4.3 KiB |
|
Before Width: | Height: | Size: 10 KiB |
|
Before Width: | Height: | Size: 10 KiB |
@@ -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>
|
|
||||||
|
Before Width: | Height: | Size: 673 B |
|
Before Width: | Height: | Size: 1.5 KiB |
|
Before Width: | Height: | Size: 5.5 KiB |
|
Before Width: | Height: | Size: 88 KiB |
@@ -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"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
|
Before Width: | Height: | Size: 8.1 KiB |
|
Before Width: | Height: | Size: 8.4 KiB |
|
Before Width: | Height: | Size: 8.9 KiB |
|
Before Width: | Height: | Size: 18 KiB |
|
Before Width: | Height: | Size: 3.9 KiB |
83
reskindex.js
Executable 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();
|
||||||
|
|
||||||
@@ -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);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
@@ -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/install/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"
|
|
||||||
@@ -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";
|
|
||||||
}
|
|
||||||
|
|
||||||
@@ -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";
|
|
||||||
}
|
|
||||||
|
|
||||||
@@ -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
|
|
||||||
@@ -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" "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"
|
|
||||||
@@ -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"
|
|
||||||
@@ -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
@@ -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;
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
Copyright 2016 OpenMarket Ltd
|
Copyright 2015 OpenMarket Ltd
|
||||||
|
|
||||||
Licensed under the Apache License, Version 2.0 (the "License");
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
you may not use this file except in compliance with the License.
|
you may not use this file except in compliance with the License.
|
||||||
@@ -16,9 +16,13 @@ limitations under the License.
|
|||||||
|
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
|
var url = require ('url');
|
||||||
|
|
||||||
|
function getServiceUrl() {
|
||||||
|
var parsedUrl = url.parse(window.location.href);
|
||||||
|
return parsedUrl.protocol + "//" + parsedUrl.host + parsedUrl.pathname;
|
||||||
|
}
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
NotificationUtils: require('./NotificationUtils'),
|
getServiceUrl: getServiceUrl
|
||||||
PushRuleVectorState: require('./PushRuleVectorState'),
|
|
||||||
VectorPushRulesDefinitions: require('./VectorPushRulesDefinitions'),
|
|
||||||
ContentRules: require('./ContentRules'),
|
|
||||||
};
|
};
|
||||||
86
src/ContentMessages.js
Normal 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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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
@@ -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';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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;
|
|
||||||
@@ -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>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
@@ -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>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
@@ -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);
|
|
||||||
@@ -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> </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"> </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"> </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>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
@@ -1,631 +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');
|
|
||||||
|
|
||||||
linkifyMatrix(linkify);
|
|
||||||
|
|
||||||
module.exports = React.createClass({
|
|
||||||
displayName: 'RoomDirectory',
|
|
||||||
|
|
||||||
propTypes: {
|
|
||||||
config: React.PropTypes.object,
|
|
||||||
},
|
|
||||||
|
|
||||||
getDefaultProps: function() {
|
|
||||||
return {
|
|
||||||
config: {
|
|
||||||
networks: [],
|
|
||||||
},
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
getInitialState: function() {
|
|
||||||
return {
|
|
||||||
publicRooms: [],
|
|
||||||
loading: true,
|
|
||||||
network: null,
|
|
||||||
roomServer: null,
|
|
||||||
filterString: null,
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
componentWillMount: function() {
|
|
||||||
// precompile Regexps
|
|
||||||
this.portalRoomPatterns = {};
|
|
||||||
this.nativePatterns = {};
|
|
||||||
if (this.props.config.networks) {
|
|
||||||
for (const network of Object.keys(this.props.config.networks)) {
|
|
||||||
const network_info = this.props.config.networks[network];
|
|
||||||
if (network_info.portalRoomPattern) {
|
|
||||||
this.portalRoomPatterns[network] = new RegExp(network_info.portalRoomPattern);
|
|
||||||
}
|
|
||||||
if (network_info.nativePattern) {
|
|
||||||
this.nativePatterns[network] = new RegExp(network_info.nativePattern);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
this.nextBatch = null;
|
|
||||||
this.filterTimeout = null;
|
|
||||||
this.scrollPanel = null;
|
|
||||||
this.protocols = null;
|
|
||||||
|
|
||||||
MatrixClientPeg.get().getThirdpartyProtocols().done((response) => {
|
|
||||||
this.protocols = response;
|
|
||||||
}, (err) => {
|
|
||||||
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.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, network) {
|
|
||||||
// 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,
|
|
||||||
network: network,
|
|
||||||
}, this.refreshRoomList);
|
|
||||||
// We also refresh the room list each time even though this
|
|
||||||
// filtering is client-side. It hopefully won't be client side
|
|
||||||
// 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();
|
|
||||||
}, 300);
|
|
||||||
},
|
|
||||||
|
|
||||||
onFilterClear: function() {
|
|
||||||
// update immediately
|
|
||||||
this.setState({
|
|
||||||
filterString: null,
|
|
||||||
}, this.refreshRoomList);
|
|
||||||
|
|
||||||
if (this.filterTimeout) {
|
|
||||||
clearTimeout(this.filterTimeout);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
onJoinClick: function(alias) {
|
|
||||||
// If we're on the 'Matrix' network (or all networks),
|
|
||||||
// just show that rooms alias
|
|
||||||
if (this.state.network == null || this.state.network == '_matrix') {
|
|
||||||
this.showRoomAlias(alias);
|
|
||||||
} else {
|
|
||||||
// This is a 3rd party protocol. Let's see if we
|
|
||||||
// can join it
|
|
||||||
const fields = this._getFieldsForThirdPartyLocation(alias, this.state.network);
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
const protocol = this._protocolForThirdPartyNetwork(this.state.network);
|
|
||||||
MatrixClientPeg.get().getThirdpartyLocation(protocol, fields).done((resp) => {
|
|
||||||
if (resp.length > 0 && resp[0].alias) {
|
|
||||||
this.showRoomAlias(resp[0].alias);
|
|
||||||
} else {
|
|
||||||
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.filter((a) => {
|
|
||||||
if (this.state.network) {
|
|
||||||
if (!this._isRoomInNetwork(a, this.state.roomServer, this.state.network)) return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
});
|
|
||||||
var rows = [];
|
|
||||||
var self = this;
|
|
||||||
var guestRead, guestJoin, perms;
|
|
||||||
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>
|
|
||||||
{ 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;
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Terrible temporary function that guess what network a public room
|
|
||||||
* entry is in, until synapse is able to tell us
|
|
||||||
*/
|
|
||||||
_isRoomInNetwork: function(room, server, network) {
|
|
||||||
// We carve rooms into two categories here. 'portal' rooms are
|
|
||||||
// rooms created by a user joining a bridge 'portal' alias to
|
|
||||||
// participate in that room or a foreign network. A room is a
|
|
||||||
// portal room if it has exactly one alias and that alias matches
|
|
||||||
// a pattern defined in the config. Its network is the key
|
|
||||||
// of the pattern that it matches.
|
|
||||||
// All other rooms are considered 'native matrix' rooms, and
|
|
||||||
// go into the special '_matrix' network.
|
|
||||||
|
|
||||||
let roomNetwork = '_matrix';
|
|
||||||
if (room.aliases && room.aliases.length == 1) {
|
|
||||||
if (this.props.config.serverConfig && this.props.config.serverConfig[server] && this.props.config.serverConfig[server].networks) {
|
|
||||||
for (const n of this.props.config.serverConfig[server].networks) {
|
|
||||||
const pat = this.portalRoomPatterns[n];
|
|
||||||
if (pat && pat.test(room.aliases[0])) {
|
|
||||||
roomNetwork = n;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return roomNetwork == network;
|
|
||||||
},
|
|
||||||
|
|
||||||
_stringLooksLikeId: function(s, network) {
|
|
||||||
let pat = /^#[^\s]+:[^\s]/;
|
|
||||||
if (
|
|
||||||
network && network != '_matrix' &&
|
|
||||||
this.nativePatterns[network]
|
|
||||||
) {
|
|
||||||
pat = this.nativePatterns[network];
|
|
||||||
}
|
|
||||||
|
|
||||||
return pat.test(s);
|
|
||||||
},
|
|
||||||
|
|
||||||
_protocolForThirdPartyNetwork: function(network) {
|
|
||||||
if (
|
|
||||||
this.props.config.networks &&
|
|
||||||
this.props.config.networks[network] &&
|
|
||||||
this.props.config.networks[network].protocol
|
|
||||||
) {
|
|
||||||
return this.props.config.networks[network].protocol;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
_getFieldsForThirdPartyLocation: function(user_input, network) {
|
|
||||||
if (!this.props.config.networks || !this.props.config.networks[network]) return null;
|
|
||||||
|
|
||||||
const network_info = this.props.config.networks[network];
|
|
||||||
if (!network_info.protocol) return null;
|
|
||||||
|
|
||||||
if (!this.protocols) return null;
|
|
||||||
|
|
||||||
let matched_instance;
|
|
||||||
// Try to find which instance in the 'protocols' response
|
|
||||||
// matches this network. We look for a matching protocol
|
|
||||||
// and the existence of a 'domain' field and if present,
|
|
||||||
// its value.
|
|
||||||
if (
|
|
||||||
this.protocols[network_info.protocol] &&
|
|
||||||
this.protocols[network_info.protocol].instances &&
|
|
||||||
this.protocols[network_info.protocol].instances.length == 1
|
|
||||||
) {
|
|
||||||
const the_instance = this.protocols[network_info.protocol].instances[0];
|
|
||||||
// If there's only one instance in this protocol, use it
|
|
||||||
// as long as it has no domain (which we assume to mean it's
|
|
||||||
// there is only one possible instance).
|
|
||||||
if (
|
|
||||||
(
|
|
||||||
the_instance.fields.domain === undefined &&
|
|
||||||
network_info.domain === undefined
|
|
||||||
) ||
|
|
||||||
(
|
|
||||||
the_instance.fields.domain !== undefined &&
|
|
||||||
the_instance.fields.domain == network_info.domain
|
|
||||||
)
|
|
||||||
) {
|
|
||||||
matched_instance = the_instance;
|
|
||||||
}
|
|
||||||
} else if (network_info.domain) {
|
|
||||||
// otherwise, we look for one with a matching domain.
|
|
||||||
for (const this_instance of this.protocols[network_info.protocol].instances) {
|
|
||||||
if (this_instance.fields.domain == network_info.domain) {
|
|
||||||
matched_instance = this_instance;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (matched_instance === undefined) return null;
|
|
||||||
|
|
||||||
// now make an object with the fields specified by that protocol. We
|
|
||||||
// require that the values of all but the last field come from the
|
|
||||||
// instance. The last is the user input.
|
|
||||||
const required_fields = this.protocols[network_info.protocol].location_fields;
|
|
||||||
const fields = {};
|
|
||||||
for (let i = 0; i < required_fields.length - 1; ++i) {
|
|
||||||
const this_field = required_fields[i];
|
|
||||||
if (matched_instance.fields[this_field] === undefined) return null;
|
|
||||||
fields[this_field] = matched_instance.fields[this_field];
|
|
||||||
}
|
|
||||||
fields[required_fields[required_fields.length - 1]] = user_input;
|
|
||||||
return fields;
|
|
||||||
},
|
|
||||||
|
|
||||||
render: function() {
|
|
||||||
let content;
|
|
||||||
if (this.state.loading) {
|
|
||||||
const Loader = sdk.getComponent("elements.Spinner");
|
|
||||||
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>;
|
|
||||||
}
|
|
||||||
|
|
||||||
let placeholder = 'Search for a room';
|
|
||||||
if (this.state.network === null || this.state.network === '_matrix') {
|
|
||||||
placeholder = '#example:' + this.state.roomServer;
|
|
||||||
} else if (
|
|
||||||
this.props.config.networks &&
|
|
||||||
this.props.config.networks[this.state.network] &&
|
|
||||||
this.props.config.networks[this.state.network].example &&
|
|
||||||
this._getFieldsForThirdPartyLocation(this.state.filterString, this.state.network)
|
|
||||||
) {
|
|
||||||
placeholder = this.props.config.networks[this.state.network].example;
|
|
||||||
}
|
|
||||||
|
|
||||||
let showJoinButton = this._stringLooksLikeId(this.state.filterString, this.state.network);
|
|
||||||
if (this.state.network && this.state.network != '_matrix') {
|
|
||||||
if (this._getFieldsForThirdPartyLocation(this.state.filterString, this.state.network) === null) {
|
|
||||||
showJoinButton = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const SimpleRoomHeader = sdk.getComponent('rooms.SimpleRoomHeader');
|
|
||||||
const NetworkDropdown = sdk.getComponent('directory.NetworkDropdown');
|
|
||||||
const DirectorySearchBox = sdk.getComponent('elements.DirectorySearchBox');
|
|
||||||
return (
|
|
||||||
<div className="mx_RoomDirectory">
|
|
||||||
<SimpleRoomHeader title="Directory" />
|
|
||||||
<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} 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] : "");
|
|
||||||
}
|
|
||||||