Compare commits
275 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d8bce0f202 | ||
|
|
e6f600bb47 | ||
|
|
519af6a5d0 | ||
|
|
c6c4aff8ae | ||
|
|
6d4abac016 | ||
|
|
57641f7228 | ||
|
|
9ae86f2585 | ||
|
|
1e7e9d6101 | ||
|
|
8bbc16ed4b | ||
|
|
4745b3efeb | ||
|
|
09f79b94dd | ||
|
|
42357dee0b | ||
|
|
2e73cd6c4d | ||
|
|
161978ab05 | ||
|
|
994bc9279f | ||
|
|
fb08910db3 | ||
|
|
e607f49ae6 | ||
|
|
52cf1f6a4b | ||
|
|
61f9966fe0 | ||
|
|
5f7cdb8d42 | ||
|
|
901c0e7e41 | ||
|
|
98cad8779c | ||
|
|
fc597ba86e | ||
|
|
f5bd8abfda | ||
|
|
8be72e6c27 | ||
|
|
2740013625 | ||
|
|
119e03b64f | ||
|
|
c6dbeee245 | ||
|
|
feb3a13a71 | ||
|
|
c80d1c15d2 | ||
|
|
b9ba795cb0 | ||
|
|
598b841bf7 | ||
|
|
93b3857a9b | ||
|
|
5414b5d189 | ||
|
|
b4e362d700 | ||
|
|
eabb91da1e | ||
|
|
fe574b008b | ||
|
|
b8fe8a4363 | ||
|
|
6acbdc442d | ||
|
|
f7cd5cb76f | ||
|
|
e2195de719 | ||
|
|
3921aa4e54 | ||
|
|
48ca0254fe | ||
|
|
375a7605ae | ||
|
|
8097b150a0 | ||
|
|
67cf4230ac | ||
|
|
b06cff7928 | ||
|
|
d65374f7f9 | ||
|
|
f0b0999f65 | ||
|
|
e55845ac52 | ||
|
|
229693dd71 | ||
|
|
5e8e6f4c8b | ||
|
|
98f9e2f870 | ||
|
|
2b51cda6da | ||
|
|
65a31cc8a9 | ||
|
|
737f912abb | ||
|
|
f6957aa367 | ||
|
|
ad79f67ab6 | ||
|
|
a9fcf106ab | ||
|
|
0f47c9956c | ||
|
|
d4ac5f829f | ||
|
|
d31a675ca8 | ||
|
|
96f620829f | ||
|
|
5fa0f62ccc | ||
|
|
b825862f95 | ||
|
|
8d06b54091 | ||
|
|
df5143c818 | ||
|
|
ca6a3a2ad9 | ||
|
|
6934b7ed56 | ||
|
|
987822bd5a | ||
|
|
189c8aca79 | ||
|
|
b6aa7f430c | ||
|
|
d8e772cfc4 | ||
|
|
ec959f0052 | ||
|
|
3d813e68e9 | ||
|
|
37b4734bbe | ||
|
|
b613a742f5 | ||
|
|
72de35a2a1 | ||
|
|
970a029cea | ||
|
|
d5a5abe202 | ||
|
|
6ff253d0d8 | ||
|
|
749d26c8f4 | ||
|
|
f1998497f2 | ||
|
|
d0c2ab4cf1 | ||
|
|
7871a3edb5 | ||
|
|
9f51e2c407 | ||
|
|
2930a94c79 | ||
|
|
9026cc4977 | ||
|
|
e3290c1117 | ||
|
|
bacd467b4c | ||
|
|
4988f0603f | ||
|
|
6a944d3e7d | ||
|
|
0e18a12472 | ||
|
|
f202197545 | ||
|
|
496f507684 | ||
|
|
55062c7ec5 | ||
|
|
6c21391ff5 | ||
|
|
a1347e7bf1 | ||
|
|
c7635a362f | ||
|
|
97c0e2dfcd | ||
|
|
dfa7c3b72e | ||
|
|
4073688ba6 | ||
|
|
693867f074 | ||
|
|
f9afa79b01 | ||
|
|
a870ea2389 | ||
|
|
ac2911c222 | ||
|
|
c786980454 | ||
|
|
47534decb3 | ||
|
|
0df242e0e9 | ||
|
|
10650d2cd1 | ||
|
|
186b4abfd8 | ||
|
|
9ce2adceef | ||
|
|
5cd4f88b8c | ||
|
|
683ecb3326 | ||
|
|
67c882648f | ||
|
|
8b0390f354 | ||
|
|
6d954ad7e0 | ||
|
|
646608bf8b | ||
|
|
be90badc8d | ||
|
|
dace762a6c | ||
|
|
7577d2eb05 | ||
|
|
90019023c7 | ||
|
|
5d02c72687 | ||
|
|
81d70a921d | ||
|
|
363453fd06 | ||
|
|
70d383fb1b | ||
|
|
b0c1097f86 | ||
|
|
68a3505091 | ||
|
|
6e47d2f1b5 | ||
|
|
2bcb27b24f | ||
|
|
6ac5fe60d9 | ||
|
|
8356ad7bfa | ||
|
|
69eee7ca0a | ||
|
|
4f944cf01c | ||
|
|
8676481aab | ||
|
|
e66e8b11cb | ||
|
|
1401de2e7d | ||
|
|
15c730a22a | ||
|
|
3b06563960 | ||
|
|
cf33ed58c1 | ||
|
|
c8a6be1454 | ||
|
|
5b7f629996 | ||
|
|
a4c16b896b | ||
|
|
d150ee0d05 | ||
|
|
7bf69d067a | ||
|
|
ccc43cc0ed | ||
|
|
f7091d2fdd | ||
|
|
1e2cd9c2d1 | ||
|
|
a2a3d7a0f9 | ||
|
|
abc306be6c | ||
|
|
ccea483f4f | ||
|
|
84aac88677 | ||
|
|
b30df2115c | ||
|
|
8d46077ac2 | ||
|
|
0441fcf3df | ||
|
|
b223d3b385 | ||
|
|
e033ce6c43 | ||
|
|
6681205337 | ||
|
|
6da1a1077d | ||
|
|
80bae0563d | ||
|
|
9a00ec128c | ||
|
|
bc092d5c77 | ||
|
|
1c9a78a2f1 | ||
|
|
11eb0a89eb | ||
|
|
d7d6b3b4a2 | ||
|
|
f5d95f7314 | ||
|
|
a6d029c556 | ||
|
|
ed9c29d365 | ||
|
|
527c390152 | ||
|
|
a6c794cb11 | ||
|
|
ff462580b0 | ||
|
|
6869c679ef | ||
|
|
4dab36d3b2 | ||
|
|
cfe63fa274 | ||
|
|
c1469f4e39 | ||
|
|
3601b44429 | ||
|
|
b3510d6973 | ||
|
|
d9780239b6 | ||
|
|
fb3ca441ba | ||
|
|
efd0dab316 | ||
|
|
f1b72dfa09 | ||
|
|
4d0b492ba0 | ||
|
|
3bcb447e03 | ||
|
|
108af83ae8 | ||
|
|
275a55266b | ||
|
|
18504ca14e | ||
|
|
6aba9f8eda | ||
|
|
e8494c3dc7 | ||
|
|
caa3cb7d89 | ||
|
|
67dbd9fba9 | ||
|
|
19238b9326 | ||
|
|
8c3fed7559 | ||
|
|
17bb47676e | ||
|
|
757604fd60 | ||
|
|
c75118caa2 | ||
|
|
0b5085ecbb | ||
|
|
64fdb290eb | ||
|
|
a714edbf2b | ||
|
|
735c298de3 | ||
|
|
db07ef7899 | ||
|
|
03caaeef07 | ||
|
|
91793a7a8e | ||
|
|
7323ae5bf5 | ||
|
|
d04cc03c1c | ||
|
|
4fb273284d | ||
|
|
3bfdbad5ba | ||
|
|
890751951c | ||
|
|
ba7e02c3b1 | ||
|
|
622ab27254 | ||
|
|
f17e851435 | ||
|
|
5e84a6c39e | ||
|
|
a86f2720bc | ||
|
|
b174d49f9d | ||
|
|
31ed719df1 | ||
|
|
c19538d6ce | ||
|
|
3adf5fe3fd | ||
|
|
bd0e121f0e | ||
|
|
de1f7861d6 | ||
|
|
5a58c8bda4 | ||
|
|
6396c60645 | ||
|
|
f700bb4efa | ||
|
|
53e5894759 | ||
|
|
8fe05fdff3 | ||
|
|
e002575eae | ||
|
|
e1919c5ea3 | ||
|
|
fcf683c8a7 | ||
|
|
e564d34ca8 | ||
|
|
61d7db6eb6 | ||
|
|
5768f73bbd | ||
|
|
df94dd7487 | ||
|
|
4221781d9b | ||
|
|
e0556789d2 | ||
|
|
9310d92605 | ||
|
|
8a2f57d234 | ||
|
|
d72323bfc5 | ||
|
|
dfd0356609 | ||
|
|
20a7aa03ed | ||
|
|
e02e0219a7 | ||
|
|
d04201d069 | ||
|
|
82de2ca4ec | ||
|
|
18450058d7 | ||
|
|
f20b2593bc | ||
|
|
4d51a5de7e | ||
|
|
8e0b61ca35 | ||
|
|
61a543a694 | ||
|
|
5608e97719 | ||
|
|
d6f27add66 | ||
|
|
740ab7e479 | ||
|
|
ea09a25563 | ||
|
|
5a08c62f1b | ||
|
|
be1d7781c5 | ||
|
|
40ac80a3e9 | ||
|
|
5f747e1a90 | ||
|
|
0c3537ac1a | ||
|
|
e6caf9ece4 | ||
|
|
34c60c69d8 | ||
|
|
5ff915476a | ||
|
|
e5d2ebc57c | ||
|
|
64815b1b0b | ||
|
|
59120d3019 | ||
|
|
9245ab01f1 | ||
|
|
59fe9279d1 | ||
|
|
046c9ef920 | ||
|
|
bca22f26c5 | ||
|
|
2b68b88b14 | ||
|
|
97cedfc712 | ||
|
|
86cb3e9376 | ||
|
|
42993a78ed | ||
|
|
2a5ca9d3df | ||
|
|
323bd79d0e | ||
|
|
60adbffacf | ||
|
|
f827a2963e | ||
|
|
5e293c0f45 | ||
|
|
cd7adfed0a | ||
|
|
9174b32ef1 |
4
.babelrc
Normal file
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"presets": ["react", "es2015", "es2016"],
|
||||
"plugins": ["transform-class-properties", "transform-object-rest-spread", "transform-async-to-generator", "transform-runtime", "add-module-exports"]
|
||||
}
|
||||
12
.gitignore
vendored
@@ -1,12 +1,14 @@
|
||||
/build
|
||||
/cert.pem
|
||||
/dist
|
||||
/karma-reports
|
||||
/key.pem
|
||||
/lib
|
||||
/node_modules
|
||||
/packages/
|
||||
/vector/bundle.*
|
||||
/vector/components.css
|
||||
/vector/emojione/
|
||||
/vector/config.json
|
||||
/vector/olm.js
|
||||
/webapp
|
||||
/.npmrc
|
||||
.DS_Store
|
||||
npm-debug.log
|
||||
electron/dist
|
||||
electron/pub
|
||||
|
||||
@@ -1,3 +0,0 @@
|
||||
example
|
||||
examples
|
||||
build/.module-cache
|
||||
|
||||
187
CHANGELOG.md
@@ -1,3 +1,190 @@
|
||||
Changes in [0.9.3](https://github.com/vector-im/riot-web/releases/tag/v0.9.3) (2016-12-22)
|
||||
==========================================================================================
|
||||
[Full Changelog](https://github.com/vector-im/riot-web/compare/v0.9.2...v0.9.3)
|
||||
|
||||
* (from matrix-react-sdk) Fix regression where the date separator would be displayed
|
||||
at the wrong time of day.
|
||||
* README.md: fix GFMD for nativefier
|
||||
[\#2755](https://github.com/vector-im/riot-web/pull/2755)
|
||||
|
||||
Changes in [0.9.2](https://github.com/vector-im/riot-web/releases/tag/v0.9.2) (2016-12-16)
|
||||
==========================================================================================
|
||||
[Full Changelog](https://github.com/vector-im/riot-web/compare/v0.9.1...v0.9.2)
|
||||
|
||||
* Remove the client side filtering from the room dir
|
||||
[\#2750](https://github.com/vector-im/riot-web/pull/2750)
|
||||
* Configure olm memory size
|
||||
[\#2745](https://github.com/vector-im/riot-web/pull/2745)
|
||||
* Support room dir 3rd party network filtering
|
||||
[\#2747](https://github.com/vector-im/riot-web/pull/2747)
|
||||
|
||||
Changes in [0.9.1](https://github.com/vector-im/riot-web/releases/tag/v0.9.1) (2016-12-09)
|
||||
==========================================================================================
|
||||
[Full Changelog](https://github.com/vector-im/riot-web/compare/v0.9.1-rc.2...v0.9.1)
|
||||
|
||||
* Update README to say how to build the desktop app
|
||||
[\#2732](https://github.com/vector-im/riot-web/pull/2732)
|
||||
* Add signing ID in release_config.yaml
|
||||
[\#2731](https://github.com/vector-im/riot-web/pull/2731)
|
||||
* Makeover!
|
||||
[\#2722](https://github.com/vector-im/riot-web/pull/2722)
|
||||
* Fix broken tests
|
||||
[\#2730](https://github.com/vector-im/riot-web/pull/2730)
|
||||
* Make the 'loading' tests work in isolation
|
||||
[\#2727](https://github.com/vector-im/riot-web/pull/2727)
|
||||
* Use a PNG for the icon on non-Windows
|
||||
[\#2708](https://github.com/vector-im/riot-web/pull/2708)
|
||||
* Add missing brackets to call to toUpperCase
|
||||
[\#2703](https://github.com/vector-im/riot-web/pull/2703)
|
||||
|
||||
Changes in [0.9.1-rc.2](https://github.com/vector-im/riot-web/releases/tag/v0.9.1-rc.2) (2016-12-06)
|
||||
====================================================================================================
|
||||
[Full Changelog](https://github.com/vector-im/riot-web/compare/v0.9.1-rc.1...v0.9.1-rc.2)
|
||||
|
||||
* Fix clicking on notifications
|
||||
[\#2700](https://github.com/vector-im/riot-web/pull/2700)
|
||||
* Desktop app: Only show window when ready
|
||||
[\#2697](https://github.com/vector-im/riot-web/pull/2697)
|
||||
|
||||
Changes in [0.9.1-rc.1](https://github.com/vector-im/riot-web/releases/tag/v0.9.1-rc.1) (2016-12-05)
|
||||
====================================================================================================
|
||||
[Full Changelog](https://github.com/vector-im/riot-web/compare/v0.9.0...v0.9.1-rc.1)
|
||||
|
||||
* Final bits to prepare electron distribtion:
|
||||
[\#2653](https://github.com/vector-im/riot-web/pull/2653)
|
||||
* Update name & repo to reflect renamed repository
|
||||
[\#2692](https://github.com/vector-im/riot-web/pull/2692)
|
||||
* Document cross_origin_renderer_url
|
||||
[\#2680](https://github.com/vector-im/riot-web/pull/2680)
|
||||
* Add css for the iframes for e2e attachments
|
||||
[\#2659](https://github.com/vector-im/riot-web/pull/2659)
|
||||
* Fix config location in some more places
|
||||
[\#2670](https://github.com/vector-im/riot-web/pull/2670)
|
||||
* CSS updates for s/block/blacklist for e2e
|
||||
[\#2662](https://github.com/vector-im/riot-web/pull/2662)
|
||||
* Update to electron 1.4.8
|
||||
[\#2660](https://github.com/vector-im/riot-web/pull/2660)
|
||||
* Add electron config
|
||||
[\#2644](https://github.com/vector-im/riot-web/pull/2644)
|
||||
* Move getDefaultDeviceName into the Platforms
|
||||
[\#2643](https://github.com/vector-im/riot-web/pull/2643)
|
||||
* Add Freenode & Mozilla domains
|
||||
[\#2641](https://github.com/vector-im/riot-web/pull/2641)
|
||||
* Include config.sample.json in dist tarball
|
||||
[\#2614](https://github.com/vector-im/riot-web/pull/2614)
|
||||
|
||||
Changes in [0.9.0](https://github.com/vector-im/vector-web/releases/tag/v0.9.0) (2016-11-19)
|
||||
============================================================================================
|
||||
[Full Changelog](https://github.com/vector-im/vector-web/compare/v0.8.4...v0.9.0)
|
||||
|
||||
* Add a cachebuster to /version
|
||||
[\#2596](https://github.com/vector-im/vector-web/pull/2596)
|
||||
* Add a 'View decrypted source' button
|
||||
[\#2587](https://github.com/vector-im/vector-web/pull/2587)
|
||||
* Fix changelog dialog to read new version format
|
||||
[\#2577](https://github.com/vector-im/vector-web/pull/2577)
|
||||
* Build all of the vector dir in the build process
|
||||
[\#2558](https://github.com/vector-im/vector-web/pull/2558)
|
||||
* Support for get_app_version
|
||||
[\#2553](https://github.com/vector-im/vector-web/pull/2553)
|
||||
* Add CSS for mlist truncation
|
||||
[\#2565](https://github.com/vector-im/vector-web/pull/2565)
|
||||
* Add menu option for `external_url` if present
|
||||
[\#2560](https://github.com/vector-im/vector-web/pull/2560)
|
||||
* Make auto-update configureable
|
||||
[\#2555](https://github.com/vector-im/vector-web/pull/2555)
|
||||
* Missed files electron windows fixes
|
||||
[\#2556](https://github.com/vector-im/vector-web/pull/2556)
|
||||
* Add some CSS for scalar error popup
|
||||
[\#2554](https://github.com/vector-im/vector-web/pull/2554)
|
||||
* Catch unhandled errors in the electron process
|
||||
[\#2552](https://github.com/vector-im/vector-web/pull/2552)
|
||||
* Slight grab-bag of fixes for electron on Windows
|
||||
[\#2551](https://github.com/vector-im/vector-web/pull/2551)
|
||||
* Electron app (take 3)
|
||||
[\#2535](https://github.com/vector-im/vector-web/pull/2535)
|
||||
* Use webpack-dev-server instead of http-server
|
||||
[\#2542](https://github.com/vector-im/vector-web/pull/2542)
|
||||
* Better support no-config when loading from file
|
||||
[\#2541](https://github.com/vector-im/vector-web/pull/2541)
|
||||
* Fix loading with no config from HTTP
|
||||
[\#2540](https://github.com/vector-im/vector-web/pull/2540)
|
||||
* Move 'new version' support into Platform
|
||||
[\#2532](https://github.com/vector-im/vector-web/pull/2532)
|
||||
* Add Notification support to the Web Platform
|
||||
[\#2533](https://github.com/vector-im/vector-web/pull/2533)
|
||||
* Use the defaults if given a blank config file
|
||||
[\#2534](https://github.com/vector-im/vector-web/pull/2534)
|
||||
* Implement Platforms
|
||||
[\#2531](https://github.com/vector-im/vector-web/pull/2531)
|
||||
|
||||
Changes in [0.8.4](https://github.com/vector-im/vector-web/releases/tag/v0.8.4) (2016-11-04)
|
||||
============================================================================================
|
||||
[Full Changelog](https://github.com/vector-im/vector-web/compare/v0.8.4-rc.2...v0.8.4)
|
||||
|
||||
* No changes
|
||||
|
||||
Changes in [0.8.4-rc.2](https://github.com/vector-im/vector-web/releases/tag/v0.8.4-rc.2) (2016-11-02)
|
||||
======================================================================================================
|
||||
[Full Changelog](https://github.com/vector-im/vector-web/compare/v0.8.4-rc.1...v0.8.4-rc.2)
|
||||
|
||||
* Fix the version in the generated distribution package
|
||||
|
||||
Changes in [0.8.4-rc.1](https://github.com/vector-im/vector-web/releases/tag/v0.8.4-rc.1) (2016-11-02)
|
||||
======================================================================================================
|
||||
[Full Changelog](https://github.com/vector-im/vector-web/compare/v0.8.3...v0.8.4-rc.1)
|
||||
|
||||
Breaking Changes
|
||||
----------------
|
||||
* End-to-end encryption now requires one-time keys to be
|
||||
signed, so end-to-end encryption will not interoperate
|
||||
with previous releases of vector-web. End-to-end encryption
|
||||
remains in beta.
|
||||
|
||||
Other Changes
|
||||
-------------
|
||||
* Rename the package script/output dir to 'dist'
|
||||
[\#2528](https://github.com/vector-im/vector-web/pull/2528)
|
||||
* Avoid errors if olm is missing
|
||||
[\#2518](https://github.com/vector-im/vector-web/pull/2518)
|
||||
* Put a cachebuster in the names of CSS and JS files
|
||||
[\#2515](https://github.com/vector-im/vector-web/pull/2515)
|
||||
* Bump to olm 2.0.0
|
||||
[\#2517](https://github.com/vector-im/vector-web/pull/2517)
|
||||
* Don't include the world in the published packages
|
||||
[\#2516](https://github.com/vector-im/vector-web/pull/2516)
|
||||
* Use webpack to copy olm.js
|
||||
[\#2514](https://github.com/vector-im/vector-web/pull/2514)
|
||||
* Don't include two copies of the CSS in the tarball
|
||||
[\#2513](https://github.com/vector-im/vector-web/pull/2513)
|
||||
* Correct text alignment on room directory search
|
||||
[\#2512](https://github.com/vector-im/vector-web/pull/2512)
|
||||
* Correct spelling of 'rel'
|
||||
[\#2510](https://github.com/vector-im/vector-web/pull/2510)
|
||||
* readme tweaks
|
||||
[\#2507](https://github.com/vector-im/vector-web/pull/2507)
|
||||
* s/vector/riot/ in the readme
|
||||
[\#2491](https://github.com/vector-im/vector-web/pull/2491)
|
||||
* Switch to babel 6, again
|
||||
[\#2480](https://github.com/vector-im/vector-web/pull/2480)
|
||||
* Revert "Switch to babel 6"
|
||||
[\#2472](https://github.com/vector-im/vector-web/pull/2472)
|
||||
* Switch to babel 6
|
||||
[\#2461](https://github.com/vector-im/vector-web/pull/2461)
|
||||
|
||||
Changes in [0.8.3](https://github.com/vector-im/vector-web/releases/tag/v0.8.3) (2016-10-12)
|
||||
============================================================================================
|
||||
[Full Changelog](https://github.com/vector-im/vector-web/compare/v0.8.2...v0.8.3)
|
||||
|
||||
* 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)
|
||||
|
||||
218
README.md
@@ -1,67 +1,74 @@
|
||||
Vector/Web
|
||||
==========
|
||||
Riot
|
||||
====
|
||||
|
||||
Vector is a Matrix web client built using the Matrix React SDK (https://github.com/matrix-org/matrix-react-sdk).
|
||||
Riot (formerly known as Vector) is a Matrix web client built using the Matrix
|
||||
React SDK (https://github.com/matrix-org/matrix-react-sdk).
|
||||
|
||||
Getting Started
|
||||
===============
|
||||
|
||||
The easiest way to test Vector is to just use the hosted copy at https://vector.im/beta.
|
||||
The develop branch is continuously deployed by Jenkins at https://vector.im/develop for
|
||||
those who like living dangerously.
|
||||
The easiest way to test Riot is to just use the hosted copy at
|
||||
https://riot.im/app. The develop branch is continuously deployed by Jenkins at
|
||||
https://riot.im/develop for those who like living dangerously.
|
||||
|
||||
To host your own copy of Vector, the quickest bet is to use a pre-built released version
|
||||
of Vector:
|
||||
To host your own copy of Riot, the quickest bet is to use a pre-built
|
||||
released version of Riot:
|
||||
|
||||
1. Download the latest version from https://vector.im/packages/
|
||||
1. Download the latest version from https://github.com/vector-im/vector-web/releases
|
||||
1. Untar the tarball on your web server
|
||||
1. Move (or symlink) the vector-x.x.x directory to an appropriate name
|
||||
1. If desired, copy `config.sample.json` to `config.json` and edit it
|
||||
as desired. See below for details.
|
||||
1. Enter the URL into your browser and log into vector!
|
||||
1. Enter the URL into your browser and log into Riot!
|
||||
|
||||
Note that Chrome does not allow microphone or webcam access for sites served
|
||||
over http (except localhost), so for working VoIP you will need to serve Riot
|
||||
over https.
|
||||
|
||||
Important Security Note
|
||||
=======================
|
||||
|
||||
We do not recommend running Vector from the same domain name as your Matrix
|
||||
homeserver. The reason is the risk of XSS (cross-site-scripting) vulnerabilities
|
||||
that could occur if someone caused Vector to load and render malicious user generated
|
||||
content from a Matrix API which then had trusted access to Vector (or other apps) due
|
||||
to sharing the same domain.
|
||||
We 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.
|
||||
We have put some coarse mitigations into place to try to protect against this
|
||||
situation, but it's still not good practice to do it in the first place. See
|
||||
https://github.com/vector-im/vector-web/issues/1977 for more details.
|
||||
|
||||
Building From Source
|
||||
====================
|
||||
|
||||
Vector is a modular webapp built with modern ES6 and requires a npm build system to build.
|
||||
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 directory: `cd vector-web`
|
||||
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, you will probably need to
|
||||
rebuild one of the dependencies, due to https://github.com/npm/npm/issues/3055:
|
||||
`(cd node_modules/matrix-react-sdk && npm install)`
|
||||
1. Configure the app by copying `config.sample.json` to `config.json` and modifying
|
||||
it (see below for details)
|
||||
1. `npm run package` to build a tarball to deploy. Untaring this file will give
|
||||
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 package` is not supported on Windows, so Windows users can run `npm
|
||||
run build`, which will build all the necessary files into the `vector`
|
||||
directory. The version of Vector will not appear in Settings without
|
||||
using the package script. You can then mount the vector directory on your
|
||||
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 `vector/config.sample.json` to
|
||||
`vector/config.json` and customising it:
|
||||
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
|
||||
@@ -72,62 +79,81 @@ You can configure the app by copying `vector/config.sample.json` to
|
||||
addresses) to matrix IDs: see http://matrix.org/docs/spec/identity_service/unstable.html
|
||||
for more details. Currently the only public matrix identity servers are https://matrix.org
|
||||
and https://vector.im. In future identity servers will be decentralised.
|
||||
1. `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. `integrations_ui_url`: URL to the web interface for the integrations server.
|
||||
1. `integrations_rest_url`: URL to the REST interface for the integrations server.
|
||||
1. `roomDirectory`: config for the public room directory. This section is optional.
|
||||
1. `roomDirectory.servers`: List of other Home Servers' directories to include in the drop
|
||||
down list. Optional.
|
||||
1. `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 `macos`
|
||||
and `win32` (for update packages, not installer packages).
|
||||
1. `cross_origin_renderer_url`: URL to a static HTML page hosting code to help display
|
||||
encrypted file attachments. This MUST be hosted on a completely separate domain to
|
||||
anything else since it is used to isolate the privileges of file attachments to this
|
||||
domain. Default: `usercontent.riot.im`. This needs to contain v1.html from
|
||||
https://github.com/matrix-org/usercontent/blob/master/v1.html
|
||||
|
||||
Running as a Desktop app
|
||||
========================
|
||||
|
||||
In future we'll do an official distribution of Vector as an desktop app. Meanwhile,
|
||||
there are a few options:
|
||||
Riot can also be run as a desktop app, wrapped in electron. You can download a
|
||||
pre-built version from https://riot.im/download/desktop/ or, if you prefer,
|
||||
built it yourself.
|
||||
|
||||
@asdf:matrix.org points out that you can use nativefier and it just works(tm):
|
||||
To run as a desktop app:
|
||||
```
|
||||
npm install
|
||||
npm install electron
|
||||
node_modules/.bin/electron .
|
||||
```
|
||||
|
||||
To build packages, use electron-builder. This is configured to output:
|
||||
* dmg + zip for macOS
|
||||
* exe + nupkg for Windows
|
||||
* deb for Linux
|
||||
But this can be customised by editing the `build` section of package.json
|
||||
as per https://github.com/electron-userland/electron-builder/wiki/Options
|
||||
|
||||
See https://github.com/electron-userland/electron-builder/wiki/Multi-Platform-Build
|
||||
for dependencies required for building packages for various platforms.
|
||||
|
||||
The only platform that can build packages for all three platforms is macOS:
|
||||
```
|
||||
brew install wine --without-x11
|
||||
brew install mono
|
||||
npm install
|
||||
npm run build:electron
|
||||
```
|
||||
|
||||
For other packages, use electron-builder manually. For example, to build a package
|
||||
for 64 bit Linux:
|
||||
```
|
||||
npm install
|
||||
npm run build
|
||||
node_modules/.bin/build -l --x64
|
||||
```
|
||||
|
||||
All electron packages go into `electron/dist/`
|
||||
|
||||
Many thanks to @aviraldg for the initial work on the electron integration.
|
||||
|
||||
Other options for running as a desktop app:
|
||||
* https://github.com/krisak/vector-electron-desktop
|
||||
* @asdf:matrix.org points out that you can use nativefier and it just works(tm)
|
||||
|
||||
```
|
||||
sudo npm install nativefier -g
|
||||
nativefier https://vector.im/beta/
|
||||
nativefier https://riot.im/app/
|
||||
```
|
||||
|
||||
krisa has a dedicated electron project at https://github.com/krisak/vector-electron-desktop
|
||||
(although you should swap out the 'vector' folder for the latest vector tarball you want to run.
|
||||
Get a tarball from https://vector.im/packages or build your own - see Building From Source
|
||||
above).
|
||||
|
||||
There's also a (much) older electron distribution at https://github.com/stevenhammerton/vector-desktop
|
||||
|
||||
|
||||
Development
|
||||
===========
|
||||
|
||||
Before attempting to develop on Vector you **must** read the developer guide
|
||||
Before attempting to develop on Riot you **must** read the developer guide
|
||||
for `matrix-react-sdk` at https://github.com/matrix-org/matrix-react-sdk, which
|
||||
also defines the design, architecture and style for Vector too.
|
||||
also defines the design, architecture and style for Riot too.
|
||||
|
||||
The idea of Vector is to be a relatively lightweight "skin" of customisations on
|
||||
The idea of Riot is to be a relatively lightweight "skin" of customisations on
|
||||
top of the underlying `matrix-react-sdk`. `matrix-react-sdk` provides both the
|
||||
higher and lower level React components useful for building Matrix communication
|
||||
apps using React.
|
||||
@@ -136,26 +162,26 @@ After creating a new component you must run `npm run reskindex` to regenerate
|
||||
the `component-index.js` for the app (used in future for skinning)
|
||||
|
||||
**However, as of July 2016 this layering abstraction is broken due to rapid
|
||||
development on Vector forcing `matrix-react-sdk` to move fast at the expense of
|
||||
maintaining a clear abstraction between the two.** Hacking on Vector inevitably
|
||||
development on Riot forcing `matrix-react-sdk` to move fast at the expense of
|
||||
maintaining a clear abstraction between the two.** Hacking on Riot inevitably
|
||||
means hacking equally on `matrix-react-sdk`, and there are bits of
|
||||
`matrix-react-sdk` behaviour incorrectly residing in the `vector-web` project
|
||||
(e.g. matrix-react-sdk specific CSS), and a bunch of Vector specific behaviour
|
||||
in the `matrix-react-sdk` (grep for Vector). This separation problem will be
|
||||
solved asap once development on Vector (and thus matrix-react-sdk) has
|
||||
(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 Vector is intended to run correctly without access to the public
|
||||
Please note that Riot is intended to run correctly without access to the public
|
||||
internet. So please don't depend on resources (JS libs, CSS, images, fonts)
|
||||
hosted by external CDNs or servers but instead please package all dependencies
|
||||
into Vector itself.
|
||||
into Riot itself.
|
||||
|
||||
Setting up a dev environment
|
||||
============================
|
||||
|
||||
Much of the functionality in Vector is actually in the `matrix-react-sdk` and
|
||||
Much of the functionality in Riot is actually in the `matrix-react-sdk` and
|
||||
`matrix-js-sdk` modules. It is possible to set these up in a way that makes it
|
||||
easy to track the `develop` branches in git and to make local changes without
|
||||
having to manually rebuild each time.
|
||||
@@ -178,7 +204,7 @@ Then similarly with `matrix-react-sdk`:
|
||||
1. `rm -r node_modules/matrix-js-sdk; ln -s ../../matrix-js-sdk node_modules/`
|
||||
1. `popd`
|
||||
|
||||
Finally, build and start vector itself:
|
||||
Finally, build and start Riot itself:
|
||||
|
||||
1. `git clone git@github.com:vector-im/vector-web.git`
|
||||
1. `cd vector-web`
|
||||
@@ -203,25 +229,19 @@ Finally, build and start vector itself:
|
||||
Remember, the command will not terminate since it runs the web server
|
||||
and rebuilds source files when they change. This development server also
|
||||
disables caching, so do NOT use it in production.
|
||||
1. Open http://127.0.0.1:8080/ in your browser to see your newly built Vector.
|
||||
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 Vector skin, you will need to rebuild
|
||||
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 vector.
|
||||
|
||||
Filing issues
|
||||
=============
|
||||
|
||||
All issues for Vector-web and Matrix-react-sdk should be filed at
|
||||
https://github.com/matrix-org/matrix-react-sdk/issues
|
||||
You'll need to do this in each new terminal you open before building Riot.
|
||||
|
||||
Triaging issues
|
||||
===============
|
||||
@@ -247,23 +267,3 @@ bug or feature:
|
||||
|
||||
* network (specific to network conditions)
|
||||
* platform (platform specific)
|
||||
|
||||
Enabling encryption
|
||||
===================
|
||||
|
||||
End-to-end encryption in Vector and Matrix is not yet considered ready for
|
||||
day-to-day use; it is experimental and should be considered only as a
|
||||
proof-of-concept. See https://matrix.org/jira/browse/SPEC-162 for an overview
|
||||
of the current progress.
|
||||
|
||||
To enable the (very experimental) support, check the 'End-to-End Encryption'
|
||||
box in the 'Labs' section of the user settings (note that the labs are disabled
|
||||
on http://vector.im/beta: you will need to use http://vector.im/develop or your
|
||||
own deployment of vector). The Room Settings dialog will then show an
|
||||
'Encryption' setting; rooms for which you are an administrator will offer you
|
||||
the option of enabling encryption. Any messages sent in that room will then be
|
||||
encrypted.
|
||||
|
||||
Note that historical encrypted messages cannot currently be decoded - history
|
||||
is therefore lost when the page is reloaded.
|
||||
|
||||
|
||||
13
config.sample.json
Normal file
@@ -0,0 +1,13 @@
|
||||
{
|
||||
"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"
|
||||
]
|
||||
}
|
||||
}
|
||||
BIN
electron/build/icon.icns
Normal file
BIN
electron/build/icon.ico
Normal file
|
After Width: | Height: | Size: 102 KiB |
BIN
electron/build/icons/128x128.png
Normal file
|
After Width: | Height: | Size: 7.2 KiB |
BIN
electron/build/icons/16x16.png
Normal file
|
After Width: | Height: | Size: 673 B |
BIN
electron/build/icons/24x24.png
Normal file
|
After Width: | Height: | Size: 1.1 KiB |
BIN
electron/build/icons/256x256.png
Normal file
|
After Width: | Height: | Size: 14 KiB |
BIN
electron/build/icons/48x48.png
Normal file
|
After Width: | Height: | Size: 2.6 KiB |
BIN
electron/build/icons/512x512.png
Normal file
|
After Width: | Height: | Size: 30 KiB |
BIN
electron/build/icons/64x64.png
Normal file
|
After Width: | Height: | Size: 3.6 KiB |
BIN
electron/build/icons/96x96.png
Normal file
|
After Width: | Height: | Size: 5.5 KiB |
BIN
electron/build/install-spinner.gif
Normal file
|
After Width: | Height: | Size: 4.4 KiB |
BIN
electron/img/riot.ico
Normal file
|
After Width: | Height: | Size: 21 KiB |
|
Before Width: | Height: | Size: 14 KiB After Width: | Height: | Size: 14 KiB |
4
electron/riot.im/README
Normal file
@@ -0,0 +1,4 @@
|
||||
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,9 +1,10 @@
|
||||
{
|
||||
"update_base_url": "https://riot.im/download/desktop/update/",
|
||||
"default_hs_url": "https://matrix.org",
|
||||
"default_is_url": "https://vector.im",
|
||||
"brand": "Riot",
|
||||
"integrations_ui_url": "http://localhost:8081/",
|
||||
"integrations_rest_url": "http://localhost:5050",
|
||||
"integrations_ui_url": "https://scalar.vector.im/",
|
||||
"integrations_rest_url": "https://scalar.vector.im/api",
|
||||
"enableLabs": true,
|
||||
"roomDirectory": {
|
||||
"servers": [
|
||||
@@ -26,21 +27,25 @@
|
||||
"protocol": "gitter",
|
||||
"portalRoomPattern": "#gitter_.*:matrix.org",
|
||||
"name": "Gitter",
|
||||
"icon": "//gitter.im/favicon.ico",
|
||||
"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": "//matrix.org/_matrix/media/v1/download/matrix.org/DHLHpDDgWNNejFmrewvwEAHX",
|
||||
"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": "//matrix.org/_matrix/media/v1/download/matrix.org/DHLHpDDgWNNejFmrewvwEAHX",
|
||||
"icon": "https://matrix.org/_matrix/media/v1/download/matrix.org/DHLHpDDgWNNejFmrewvwEAHX",
|
||||
"example": "#channel",
|
||||
"nativePattern": "^#[^\\s]+$"
|
||||
},
|
||||
@@ -49,7 +54,7 @@
|
||||
"domain": "ipv6-irc.snoonet.org",
|
||||
"portalRoomPattern": "#_snoonet_.*:matrix.org",
|
||||
"name": "Snoonet",
|
||||
"icon": "//matrix.org/_matrix/media/v1/download/matrix.org/DHLHpDDgWNNejFmrewvwEAHX",
|
||||
"icon": "https://matrix.org/_matrix/media/v1/download/matrix.org/DHLHpDDgWNNejFmrewvwEAHX",
|
||||
"example": "#channel",
|
||||
"nativePattern": "^#[^\\s]+$"
|
||||
},
|
||||
@@ -58,7 +63,7 @@
|
||||
"domain": "irc.oftc.net",
|
||||
"portalRoomPattern": "#_oftc_.*:matrix.org",
|
||||
"name": "OFTC",
|
||||
"icon": "//matrix.org/_matrix/media/v1/download/matrix.org/DHLHpDDgWNNejFmrewvwEAHX",
|
||||
"icon": "https://matrix.org/_matrix/media/v1/download/matrix.org/DHLHpDDgWNNejFmrewvwEAHX",
|
||||
"example": "#channel",
|
||||
"nativePattern": "^#[^\\s]+$"
|
||||
}
|
||||
214
electron/src/electron-main.js
Normal file
@@ -0,0 +1,214 @@
|
||||
// @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');
|
||||
30
electron/src/squirrelhooks.js
Normal file
@@ -0,0 +1,30 @@
|
||||
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;
|
||||
201
electron/src/vectormenu.js
Normal file
@@ -0,0 +1,201 @@
|
||||
/*
|
||||
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)
|
||||
|
||||
@@ -92,13 +92,7 @@ module.exports = function (config) {
|
||||
test: /\.js$/, loader: "babel",
|
||||
include: [path.resolve('./src'),
|
||||
path.resolve('./test'),
|
||||
],
|
||||
query: {
|
||||
// we're using babel 5, for consistency with
|
||||
// the release build, which doesn't use the
|
||||
// presets.
|
||||
// presets: ['react', 'es2015'],
|
||||
},
|
||||
]
|
||||
},
|
||||
],
|
||||
noParse: [
|
||||
|
||||
129
package.json
@@ -1,76 +1,107 @@
|
||||
{
|
||||
"name": "vector-web",
|
||||
"version": "0.8.2",
|
||||
"description": "Vector webapp",
|
||||
"author": "matrix.org",
|
||||
"name": "riot-web",
|
||||
"productName": "Riot",
|
||||
"main": "electron/src/electron-main.js",
|
||||
"version": "0.9.3",
|
||||
"description": "A feature-rich client for Matrix.org",
|
||||
"author": "Vector Creations Ltd.",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/vector-im/vector-web"
|
||||
"url": "https://github.com/vector-im/riot-web"
|
||||
},
|
||||
"license": "Apache-2.0",
|
||||
"files": [
|
||||
"AUTHORS.rst",
|
||||
"CONTRIBUTING.rst",
|
||||
"deploy",
|
||||
"docs",
|
||||
"karma.conf.js",
|
||||
"lib",
|
||||
"release.sh",
|
||||
"scripts",
|
||||
"src",
|
||||
"test",
|
||||
"webpack.config.js"
|
||||
],
|
||||
"style": "bundle.css",
|
||||
"matrix-react-parent": "matrix-react-sdk",
|
||||
"scripts": {
|
||||
"reskindex": "reskindex -h src/header",
|
||||
"build:emojione": "cpx \"node_modules/emojione/assets/svg/*\" vector/emojione/svg/",
|
||||
"build:res": "cpx \"{src/skins/vector/fonts,src/skins/vector/img}/**\" webapp/ && cpx \"{res/media,res/vector-icons}/**\" webapp/",
|
||||
"build:config": "cpx config.json webapp/",
|
||||
"build:emojione": "cpx \"node_modules/emojione/assets/svg/*\" webapp/emojione/svg/",
|
||||
"build:modernizr": "modernizr -c .modernizr.json -d src/vector/modernizr.js",
|
||||
"build:css": "catw \"src/skins/vector/css/**/*.css\" -o vector/components.css --no-watch",
|
||||
"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 lib/vector/index.js vector/bundle.js",
|
||||
"build:bundle:dev": "webpack --optimize-occurence-order lib/vector/index.js vector/bundle.js",
|
||||
"build:staticfiles": "cpx -v node_modules/olm/olm.js vector/",
|
||||
"build": "npm run build:staticfiles && npm run build:emojione && npm run build:css && npm run build:compile && npm run build:bundle",
|
||||
"build:dev": "npm run build:staticfiles && npm run build:emojione && npm run build:css && npm run build:compile && npm run build:bundle:dev",
|
||||
"package": "scripts/package.sh",
|
||||
"start:emojione": "cpx \"node_modules/emojione/assets/svg/*\" vector/emojione/svg/ -w",
|
||||
"start:js": "webpack -w src/vector/index.js vector/bundle.js",
|
||||
"start:js:prod": "NODE_ENV=production webpack -w src/vector/index.js vector/bundle.js",
|
||||
"start:skins:css": "catw \"src/skins/vector/css/**/*.css\" -o vector/components.css",
|
||||
"start:staticfiles": "cpx -Lwv node_modules/olm/olm.js vector/",
|
||||
"//cache": "Note the -c 1 below due to https://code.google.com/p/chromium/issues/detail?id=508270",
|
||||
"start": "parallelshell \"npm run start:staticfiles\" \"npm run start:emojione\" \"npm run start:js\" \"npm run start:skins:css\" \"http-server -c 1 vector\"",
|
||||
"start:prod": "parallelshell \"npm run start:staticfiles\" \"npm run start:emojione\" \"npm run start:js:prod\" \"npm run start:skins:css\" \"http-server -c 1 vector\"",
|
||||
"clean": "rimraf lib vector/olm.js vector/bundle.css vector/bundle.js vector/bundle.js.map vector/webpack.css* vector/emojione",
|
||||
"prepublish": "npm run build:css && npm run build:compile",
|
||||
"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": {
|
||||
"babel-polyfill": "^6.5.0",
|
||||
"babel-runtime": "^6.11.6",
|
||||
"browser-request": "^0.3.3",
|
||||
"classnames": "^2.1.2",
|
||||
"draft-js": "^0.8.1",
|
||||
"extract-text-webpack-plugin": "^0.9.1",
|
||||
"favico.js": "^0.3.10",
|
||||
"filesize": "^3.1.2",
|
||||
"flux": "~2.0.3",
|
||||
"gemini-scrollbar": "matrix-org/gemini-scrollbar#b302279",
|
||||
"gfm.css": "^1.1.1",
|
||||
"highlight.js": "^9.0.0",
|
||||
"linkifyjs": "^2.1.3",
|
||||
"matrix-js-sdk": "0.6.2",
|
||||
"matrix-react-sdk": "0.7.3",
|
||||
"matrix-js-sdk": "0.7.2",
|
||||
"matrix-react-sdk": "0.8.3",
|
||||
"modernizr": "^3.1.0",
|
||||
"q": "^1.4.1",
|
||||
"react": "^15.2.1",
|
||||
"react": "^15.4.0",
|
||||
"react-dnd": "^2.1.4",
|
||||
"react-dnd-html5-backend": "^2.1.2",
|
||||
"react-dom": "^15.2.1",
|
||||
"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"
|
||||
},
|
||||
"devDependencies": {
|
||||
"babel": "^5.8.23",
|
||||
"babel-core": "^5.8.25",
|
||||
"babel-loader": "^5.3.2",
|
||||
"babel-cli": "^6.5.2",
|
||||
"babel-core": "^6.14.0",
|
||||
"babel-eslint": "^6.1.0",
|
||||
"babel-loader": "^6.2.5",
|
||||
"babel-plugin-add-module-exports": "^0.2.1",
|
||||
"babel-plugin-transform-async-to-generator": "^6.16.0",
|
||||
"babel-plugin-transform-class-properties": "^6.16.0",
|
||||
"babel-plugin-transform-object-rest-spread": "^6.16.0",
|
||||
"babel-plugin-transform-runtime": "^6.15.0",
|
||||
"babel-preset-es2015": "^6.16.0",
|
||||
"babel-preset-es2016": "^6.16.0",
|
||||
"babel-preset-es2017": "^6.16.0",
|
||||
"babel-preset-react": "^6.16.0",
|
||||
"babel-preset-stage-2": "^6.17.0",
|
||||
"catw": "^1.0.1",
|
||||
"cpx": "^1.3.2",
|
||||
"css-raw-loader": "^0.1.1",
|
||||
"electron-builder": "^10.4.1",
|
||||
"emojione": "^2.2.3",
|
||||
"expect": "^1.16.0",
|
||||
"fs-extra": "^0.30.0",
|
||||
"http-server": "^0.8.4",
|
||||
"html-webpack-plugin": "^2.24.0",
|
||||
"json-loader": "^0.5.3",
|
||||
"karma": "^0.13.22",
|
||||
"karma-chrome-launcher": "^0.2.3",
|
||||
@@ -80,16 +111,44 @@
|
||||
"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.0",
|
||||
"react-addons-test-utils": "^15.0.1",
|
||||
"react-addons-perf": "^15.4.0",
|
||||
"react-addons-test-utils": "^15.4.0",
|
||||
"rimraf": "^2.4.3",
|
||||
"source-map-loader": "^0.1.5",
|
||||
"webpack": "^1.12.14"
|
||||
"webpack": "^1.12.14",
|
||||
"webpack-dev-server": "^1.16.2"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"olm": "https://matrix.org/packages/npm/olm/olm-1.3.0.tgz"
|
||||
"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"
|
||||
}
|
||||
}
|
||||
|
||||
1
release_config.yaml
Normal file
@@ -0,0 +1 @@
|
||||
signing_id: packages@riot.im
|
||||
BIN
res/vector-icons/android-chrome-144x144.png
Normal file
|
After Width: | Height: | Size: 8.1 KiB |
BIN
res/vector-icons/android-chrome-192x192.png
Normal file
|
After Width: | Height: | Size: 11 KiB |
BIN
res/vector-icons/android-chrome-36x36.png
Normal file
|
After Width: | Height: | Size: 1.8 KiB |
BIN
res/vector-icons/android-chrome-48x48.png
Normal file
|
After Width: | Height: | Size: 2.6 KiB |
BIN
res/vector-icons/android-chrome-72x72.png
Normal file
|
After Width: | Height: | Size: 4.1 KiB |
BIN
res/vector-icons/android-chrome-96x96.png
Normal file
|
After Width: | Height: | Size: 5.5 KiB |
BIN
res/vector-icons/apple-touch-icon-114x114.png
Normal file
|
After Width: | Height: | Size: 6.4 KiB |
BIN
res/vector-icons/apple-touch-icon-120x120.png
Normal file
|
After Width: | Height: | Size: 6.7 KiB |
BIN
res/vector-icons/apple-touch-icon-144x144.png
Normal file
|
After Width: | Height: | Size: 8.1 KiB |
BIN
res/vector-icons/apple-touch-icon-152x152.png
Normal file
|
After Width: | Height: | Size: 8.5 KiB |
BIN
res/vector-icons/apple-touch-icon-180x180.png
Normal file
|
After Width: | Height: | Size: 10 KiB |
BIN
res/vector-icons/apple-touch-icon-57x57.png
Normal file
|
After Width: | Height: | Size: 3.2 KiB |
BIN
res/vector-icons/apple-touch-icon-60x60.png
Normal file
|
After Width: | Height: | Size: 3.3 KiB |
BIN
res/vector-icons/apple-touch-icon-72x72.png
Normal file
|
After Width: | Height: | Size: 4.1 KiB |
BIN
res/vector-icons/apple-touch-icon-76x76.png
Normal file
|
After Width: | Height: | Size: 4.3 KiB |
BIN
res/vector-icons/apple-touch-icon-precomposed.png
Normal file
|
After Width: | Height: | Size: 10 KiB |
BIN
res/vector-icons/apple-touch-icon.png
Normal file
|
After Width: | Height: | Size: 10 KiB |
BIN
res/vector-icons/favicon-16x16.png
Normal file
|
After Width: | Height: | Size: 673 B |
BIN
res/vector-icons/favicon-32x32.png
Normal file
|
After Width: | Height: | Size: 1.5 KiB |
BIN
res/vector-icons/favicon-96x96.png
Normal file
|
After Width: | Height: | Size: 5.5 KiB |
BIN
res/vector-icons/favicon.ico
Normal file
|
After Width: | Height: | Size: 102 KiB |
BIN
res/vector-icons/mstile-144x144.png
Normal file
|
After Width: | Height: | Size: 8.1 KiB |
BIN
res/vector-icons/mstile-150x150.png
Normal file
|
After Width: | Height: | Size: 8.4 KiB |
BIN
res/vector-icons/mstile-310x150.png
Normal file
|
After Width: | Height: | Size: 8.9 KiB |
BIN
res/vector-icons/mstile-310x310.png
Normal file
|
After Width: | Height: | Size: 18 KiB |
BIN
res/vector-icons/mstile-70x70.png
Normal file
|
After Width: | Height: | Size: 3.9 KiB |
26
scripts/babelcheck.js
Normal file
@@ -0,0 +1,26 @@
|
||||
#!/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);
|
||||
}
|
||||
});
|
||||
130
scripts/electron-package.sh
Executable file
@@ -0,0 +1,130 @@
|
||||
#!/bin/bash
|
||||
|
||||
set -e
|
||||
|
||||
usage() {
|
||||
echo "Usage: $0 -v <version> -c <config file> [-n]"
|
||||
echo
|
||||
echo "version: commit-ish to check out and build"
|
||||
echo "config file: a path to a json config file to"
|
||||
echo "ship with the build. In addition, update_base_url:"
|
||||
echo "from this file is used to set up auto-update."
|
||||
echo "-n: build with no config file."
|
||||
echo
|
||||
echo "Values may also be passed as environment variables"
|
||||
}
|
||||
|
||||
conffile=
|
||||
version=
|
||||
skipcfg=0
|
||||
while getopts "c:v:n" opt; do
|
||||
case $opt in
|
||||
c)
|
||||
conffile=$OPTARG
|
||||
;;
|
||||
v)
|
||||
version=$OPTARG
|
||||
;;
|
||||
n)
|
||||
skipcfg=1
|
||||
;;
|
||||
\?)
|
||||
echo "Invalid option: -$OPTARG" >&2
|
||||
usage
|
||||
exit
|
||||
;;
|
||||
esac
|
||||
done
|
||||
|
||||
if [ -z "$version" ]; then
|
||||
echo "No version supplied"
|
||||
usage
|
||||
exit
|
||||
fi
|
||||
|
||||
if [ -z "$conffile" ] && [ "$skipcfg" = 0 ]; then
|
||||
echo "No config file given. Use -c to supply a config file or"
|
||||
echo "-n to build with no config file (and no auto update)."
|
||||
exit
|
||||
fi
|
||||
|
||||
if [ -n "$conffile" ]; then
|
||||
update_base_url=`jq -r .update_base_url $conffile`
|
||||
|
||||
if [ -z "$update_base_url" ]; then
|
||||
echo "No update URL supplied. Use update_base_url: null if you really"
|
||||
echo "want a build with no auto-update."
|
||||
usage
|
||||
exit
|
||||
fi
|
||||
# Make sure the base URL ends in a slash if it doesn't already
|
||||
update_base_url=`echo $update_base_url | sed -e 's#\([^\/]\)$#\1\/#'`
|
||||
fi
|
||||
|
||||
if [ ! -f package.json ]; then
|
||||
echo "No package.json found. This script must be run from"
|
||||
echo "the vector-web directory."
|
||||
exit
|
||||
fi
|
||||
|
||||
echo "Building $version using Update base URL $update_base_url"
|
||||
|
||||
projdir=`pwd`
|
||||
builddir=`mktemp -d 2>/dev/null || mktemp -d -t 'buildtmp'`
|
||||
pushd "$builddir"
|
||||
|
||||
git clone "$projdir" .
|
||||
git checkout "$version"
|
||||
|
||||
# Figure out what version we're building
|
||||
vername=`jq -r .version package.json`
|
||||
|
||||
if [ -n "$conffile" ]; then
|
||||
popd
|
||||
cp "$conffile" "$builddir/"
|
||||
pushd "$builddir"
|
||||
fi
|
||||
|
||||
npm install
|
||||
npm run build:electron
|
||||
|
||||
popd
|
||||
|
||||
distdir="$builddir/electron/dist"
|
||||
pubdir="$projdir/electron/pub"
|
||||
rm -r "$pubdir" || true
|
||||
mkdir -p "$pubdir"
|
||||
|
||||
# Install packages: what the user downloads the first time,
|
||||
# (DMGs for mac, exe installer for windows)
|
||||
mkdir -p "$pubdir/install/macos"
|
||||
cp $distdir/mac/*.dmg "$pubdir/install/macos/"
|
||||
|
||||
mkdir -p "$pubdir/install/win32/ia32/"
|
||||
cp $distdir/win-ia32/*.exe "$pubdir/install/win32/ia32/"
|
||||
|
||||
mkdir -p "$pubdir/install/win32/x64/"
|
||||
cp $distdir/win/*.exe "$pubdir/install/win32/x64/"
|
||||
|
||||
# Packages for auto-update
|
||||
mkdir -p "$pubdir/update/macos"
|
||||
cp $distdir/mac/*.zip "$pubdir/update/macos/"
|
||||
echo "$vername" > "$pubdir/update/macos/latest"
|
||||
|
||||
mkdir -p "$pubdir/update/win32/ia32/"
|
||||
cp $distdir/win-ia32/*.nupkg "$pubdir/update/win32/ia32/"
|
||||
cp $distdir/win-ia32/RELEASES "$pubdir/update/win32/ia32/"
|
||||
|
||||
mkdir -p "$pubdir/update/win32/x64/"
|
||||
cp $distdir/win/*.nupkg "$pubdir/update/win32/x64/"
|
||||
cp $distdir/win/RELEASES "$pubdir/update/win32/x64/"
|
||||
|
||||
# Move the debs to the main project dir's dist folder
|
||||
rm -r "$projdir/electron/dist" || true
|
||||
mkdir -p "$projdir/electron/dist"
|
||||
cp $distdir/*.deb "$projdir/electron/dist/"
|
||||
|
||||
rm -rf "$builddir"
|
||||
|
||||
echo "Riot Desktop is ready to go in $pubdir: this directory can be hosted on your web server."
|
||||
echo "deb archives are in electron/dist/ - these should be added into your debian repository"
|
||||
@@ -25,11 +25,7 @@ cp -r olm/package node_modules/olm
|
||||
# run the mocha tests
|
||||
npm run test
|
||||
|
||||
# build our artifacts; dumps them in ./vector
|
||||
npm run build:dev
|
||||
|
||||
# gzip up ./vector
|
||||
rm vector-*.tar.gz || true # rm previous artifacts without failing if it doesn't exist
|
||||
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
|
||||
@@ -39,4 +35,4 @@ JSSDK_SHA=$(grep 'gitHead' node_modules/matrix-js-sdk/package.json | cut -d \" -
|
||||
|
||||
VECTOR_SHA=$(git rev-parse --short=12 HEAD) # use the ACTUAL SHA rather than assume develop
|
||||
|
||||
tar -zcvhf vector-$VECTOR_SHA-react-$REACT_SHA-js-$JSSDK_SHA.tar.gz vector #g[z]ip, [c]reate archive, [v]erbose, [f]ilename, [h]ard-dereference (do not archive symlinks)
|
||||
DIST_VERSION=$VECTOR_SHA-react-$REACT_SHA-js-$JSSDK_SHA scripts/package.sh -d
|
||||
93
scripts/make-icons.sh
Executable file
@@ -0,0 +1,93 @@
|
||||
#!/bin/bash
|
||||
|
||||
if [ $# != 1 ]
|
||||
then
|
||||
echo "Usage: $0 <svg file>"
|
||||
exit
|
||||
fi
|
||||
|
||||
set -e
|
||||
set -x
|
||||
|
||||
tmpdir=`mktemp -d 2>/dev/null || mktemp -d -t 'icontmp'`
|
||||
|
||||
for i in 1024 512 310 256 192 180 152 150 144 128 120 114 96 76 72 70 64 60 57 48 36 32 24 16
|
||||
do
|
||||
#convert -background none -density 1000 -resize $i -extent $i -gravity center "$1" "$tmpdir/$i.png"
|
||||
|
||||
# Above is the imagemagick command to render an svg to png. Unfortunately, its support for SVGs
|
||||
# with CSS isn't very good (with rsvg and even moreso the built in renderer) so we use cairosvg.
|
||||
# This can be installed with:
|
||||
# pip install cairosvg==1.0.22 # Version 2 doesn't support python 2
|
||||
# pip install tinycss
|
||||
# pip install cssselect # These are necessary for CSS support
|
||||
# You'll also need xmlstarlet from your favourite package manager
|
||||
#
|
||||
# Cairosvg doesn't suport rendering at a specific size (https://github.com/Kozea/CairoSVG/issues/83#issuecomment-215720176)
|
||||
# so we have to 'resize the svg' first (add width and height attributes to the svg element) to make it render at the
|
||||
# size we need.
|
||||
# XXX: This will break if the svg already has width and height attributes
|
||||
cp "$1" "$tmpdir/tmp.svg"
|
||||
xmlstarlet ed -N x="http://www.w3.org/2000/svg" --insert "/x:svg" --type attr -n width -v $i "$tmpdir/tmp.svg" > "$tmpdir/tmp2.svg"
|
||||
xmlstarlet ed -N x="http://www.w3.org/2000/svg" --insert "/x:svg" --type attr -n height -v $i "$tmpdir/tmp2.svg" > "$tmpdir/tmp3.svg"
|
||||
cairosvg -f png -o "$tmpdir/$i.png" "$tmpdir/tmp3.svg"
|
||||
rm "$tmpdir/tmp.svg" "$tmpdir/tmp2.svg" "$tmpdir/tmp3.svg"
|
||||
done
|
||||
|
||||
# one more for the non-square mstile
|
||||
cp "$1" "$tmpdir/tmp.svg"
|
||||
xmlstarlet ed -N x="http://www.w3.org/2000/svg" --insert "/x:svg" --type attr -n width -v 310 "$tmpdir/tmp.svg" > "$tmpdir/tmp2.svg"
|
||||
xmlstarlet ed -N x="http://www.w3.org/2000/svg" --insert "/x:svg" --type attr -n height -v 150 "$tmpdir/tmp2.svg" > "$tmpdir/tmp3.svg"
|
||||
cairosvg -f png -o "$tmpdir/310x150.png" "$tmpdir/tmp3.svg"
|
||||
rm "$tmpdir/tmp.svg" "$tmpdir/tmp2.svg" "$tmpdir/tmp3.svg"
|
||||
|
||||
mkdir "$tmpdir/Riot.iconset"
|
||||
cp "$tmpdir/16.png" "$tmpdir/Riot.iconset/icon_16x16.png"
|
||||
cp "$tmpdir/32.png" "$tmpdir/Riot.iconset/icon_16x16@2x.png"
|
||||
cp "$tmpdir/32.png" "$tmpdir/Riot.iconset/icon_32x32.png"
|
||||
cp "$tmpdir/64.png" "$tmpdir/Riot.iconset/icon_32x32@2x.png"
|
||||
cp "$tmpdir/128.png" "$tmpdir/Riot.iconset/icon_128x128.png"
|
||||
cp "$tmpdir/256.png" "$tmpdir/Riot.iconset/icon_128x128@2x.png"
|
||||
cp "$tmpdir/256.png" "$tmpdir/Riot.iconset/icon_256x256.png"
|
||||
cp "$tmpdir/512.png" "$tmpdir/Riot.iconset/icon_256x256@2x.png"
|
||||
cp "$tmpdir/512.png" "$tmpdir/Riot.iconset/icon_512x512.png"
|
||||
cp "$tmpdir/1024.png" "$tmpdir/Riot.iconset/icon_512x512@2x.png"
|
||||
iconutil -c icns -o electron/build/icon.icns "$tmpdir/Riot.iconset"
|
||||
|
||||
cp "$tmpdir/36.png" "res/vector-icons/android-chrome-36x36.png"
|
||||
cp "$tmpdir/48.png" "res/vector-icons/android-chrome-48x48.png"
|
||||
cp "$tmpdir/72.png" "res/vector-icons/android-chrome-72x72.png"
|
||||
cp "$tmpdir/96.png" "res/vector-icons/android-chrome-96x96.png"
|
||||
cp "$tmpdir/144.png" "res/vector-icons/android-chrome-144x144.png"
|
||||
cp "$tmpdir/192.png" "res/vector-icons/android-chrome-192x192.png"
|
||||
cp "$tmpdir/180.png" "res/vector-icons/apple-touch-icon.png"
|
||||
cp "$tmpdir/180.png" "res/vector-icons/apple-touch-icon-precomposed.png"
|
||||
cp "$tmpdir/57.png" "res/vector-icons/apple-touch-icon-57x57.png"
|
||||
cp "$tmpdir/60.png" "res/vector-icons/apple-touch-icon-60x60.png"
|
||||
cp "$tmpdir/72.png" "res/vector-icons/apple-touch-icon-72x72.png"
|
||||
cp "$tmpdir/76.png" "res/vector-icons/apple-touch-icon-76x76.png"
|
||||
cp "$tmpdir/114.png" "res/vector-icons/apple-touch-icon-114x114.png"
|
||||
cp "$tmpdir/120.png" "res/vector-icons/apple-touch-icon-120x120.png"
|
||||
cp "$tmpdir/144.png" "res/vector-icons/apple-touch-icon-144x144.png"
|
||||
cp "$tmpdir/152.png" "res/vector-icons/apple-touch-icon-152x152.png"
|
||||
cp "$tmpdir/180.png" "res/vector-icons/apple-touch-icon-180x180.png"
|
||||
cp "$tmpdir/16.png" "res/vector-icons/favicon-16x16.png"
|
||||
cp "$tmpdir/32.png" "res/vector-icons/favicon-32x32.png"
|
||||
cp "$tmpdir/96.png" "res/vector-icons/favicon-96x96.png"
|
||||
cp "$tmpdir/70.png" "res/vector-icons/mstile-70x70.png"
|
||||
cp "$tmpdir/144.png" "res/vector-icons/mstile-144x144.png"
|
||||
cp "$tmpdir/150.png" "res/vector-icons/mstile-150x150.png"
|
||||
cp "$tmpdir/310.png" "res/vector-icons/mstile-310x310.png"
|
||||
cp "$tmpdir/310x150.png" "res/vector-icons/mstile-310x150.png"
|
||||
|
||||
convert "$tmpdir/16.png" "$tmpdir/32.png" "$tmpdir/64.png" "$tmpdir/128.png" "$tmpdir/256.png" "res/vector-icons/favicon.ico"
|
||||
|
||||
cp "res/vector-icons/favicon.ico" "electron/build/icon.ico"
|
||||
|
||||
# https://github.com/electron-userland/electron-builder/blob/3f97b86993d4ea5172e562b182230a194de0f621/src/targets/LinuxTargetHelper.ts#L127
|
||||
for i in 24 96 16 48 64 128 256 512
|
||||
do
|
||||
cp "$tmpdir/$i.png" "electron/build/icons/${i}x${i}.png"
|
||||
done
|
||||
|
||||
rm -r "$tmpdir"
|
||||
@@ -2,14 +2,29 @@
|
||||
|
||||
set -e
|
||||
|
||||
version=`git describe --dirty --tags || echo unknown`
|
||||
dev=""
|
||||
if [ "$1" = '-d' ]; then
|
||||
dev=":dev"
|
||||
fi
|
||||
|
||||
npm run build
|
||||
mkdir -p packages
|
||||
cp -r vector vector-$version
|
||||
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 packages/vector-$version.tar.gz vector-$version
|
||||
tar chvzf dist/vector-$version.tar.gz vector-$version
|
||||
rm -r vector-$version
|
||||
|
||||
echo
|
||||
echo "Packaged packages/vector-$version.tar.gz"
|
||||
echo "Packaged dist/vector-$version.tar.gz"
|
||||
|
||||
@@ -13,7 +13,7 @@ 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):
|
||||
for chunk in r.iter_content(chunk_size=1024):
|
||||
if chunk: # filter out keep-alive new chunks
|
||||
f.write(chunk)
|
||||
return local_filename
|
||||
@@ -107,30 +107,46 @@ def on_receive_jenkins_poke():
|
||||
)
|
||||
|
||||
print("Retrieving .tar.gz file: %s" % tar_gz_url)
|
||||
|
||||
# we rely on the fact that flask only serves one request at a time to
|
||||
# ensure that we do not overwrite a tarball from a concurrent request.
|
||||
filename = download_file(tar_gz_url)
|
||||
print("Downloaded file: %s" % filename)
|
||||
|
||||
try:
|
||||
# we extract into a directory based on the build number. This avoids the
|
||||
# problem of multiple builds building the same git version and thus having
|
||||
# the same tarball name. That would lead to two potential problems:
|
||||
# (a) sometimes jenkins serves corrupted artifacts; we would replace
|
||||
# a good deploy with a bad one
|
||||
# (b) we'll be overwriting the live deployment, which means people might
|
||||
# see half-written files.
|
||||
build_dir = os.path.join(arg_extract_path, "%s-#%s" % (job_name, build_num))
|
||||
if os.path.exists(build_dir):
|
||||
abort(400, "Not deploying. We have previously deployed this build.")
|
||||
return
|
||||
os.mkdir(build_dir)
|
||||
|
||||
untar_to(filename, build_dir)
|
||||
print("Extracted to: %s" % build_dir)
|
||||
finally:
|
||||
if arg_should_clean:
|
||||
os.remove(filename)
|
||||
|
||||
name_str = filename.replace(".tar.gz", "")
|
||||
untar_location = os.path.join(arg_extract_path, name_str)
|
||||
untar_to(filename, untar_location)
|
||||
|
||||
if arg_should_clean:
|
||||
os.remove(filename)
|
||||
|
||||
# stamp the version somewhere JS can get to it
|
||||
with open(os.path.join(untar_location, "vector/version"), "w") as stamp_file:
|
||||
stamp_file.write(name_str)
|
||||
|
||||
create_symlink(source=os.path.join(untar_location, "vector"), linkname=arg_symlink)
|
||||
extracted_dir = os.path.join(build_dir, name_str)
|
||||
|
||||
if arg_config_location:
|
||||
create_symlink(source=arg_config_location, linkname=os.path.join(untar_location, "vector", 'config.json'))
|
||||
create_symlink(source=arg_config_location, linkname=os.path.join(extracted_dir, 'config.json'))
|
||||
|
||||
create_symlink(source=extracted_dir, linkname=arg_symlink)
|
||||
|
||||
return jsonify({})
|
||||
|
||||
if __name__ == "__main__":
|
||||
parser = argparse.ArgumentParser("Runs a Vector redeployment server.")
|
||||
parser.add_argument(
|
||||
"-j", "--jenkins", dest="jenkins", default="http://matrix.org/jenkins/", help=(
|
||||
"-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."
|
||||
)
|
||||
@@ -26,32 +26,61 @@ limitations under the License.
|
||||
|
||||
module.exports.components = require('matrix-react-sdk/lib/component-index').components;
|
||||
|
||||
module.exports.components['structures.BottomLeftMenu'] = require('./components/structures/BottomLeftMenu');
|
||||
module.exports.components['structures.CompatibilityPage'] = require('./components/structures/CompatibilityPage');
|
||||
module.exports.components['structures.LeftPanel'] = require('./components/structures/LeftPanel');
|
||||
module.exports.components['structures.RightPanel'] = require('./components/structures/RightPanel');
|
||||
module.exports.components['structures.RoomDirectory'] = require('./components/structures/RoomDirectory');
|
||||
module.exports.components['structures.RoomSubList'] = require('./components/structures/RoomSubList');
|
||||
module.exports.components['structures.SearchBox'] = require('./components/structures/SearchBox');
|
||||
module.exports.components['structures.ViewSource'] = require('./components/structures/ViewSource');
|
||||
module.exports.components['views.context_menus.MessageContextMenu'] = require('./components/views/context_menus/MessageContextMenu');
|
||||
module.exports.components['views.context_menus.NotificationStateContextMenu'] = require('./components/views/context_menus/NotificationStateContextMenu');
|
||||
module.exports.components['views.context_menus.RoomTagContextMenu'] = require('./components/views/context_menus/RoomTagContextMenu');
|
||||
module.exports.components['views.dialogs.ChangelogDialog'] = require('./components/views/dialogs/ChangelogDialog');
|
||||
module.exports.components['views.directory.NetworkDropdown'] = require('./components/views/directory/NetworkDropdown');
|
||||
module.exports.components['views.elements.ImageView'] = require('./components/views/elements/ImageView');
|
||||
module.exports.components['views.elements.Spinner'] = require('./components/views/elements/Spinner');
|
||||
module.exports.components['views.globals.GuestWarningBar'] = require('./components/views/globals/GuestWarningBar');
|
||||
module.exports.components['views.globals.MatrixToolbar'] = require('./components/views/globals/MatrixToolbar');
|
||||
module.exports.components['views.globals.NewVersionBar'] = require('./components/views/globals/NewVersionBar');
|
||||
module.exports.components['views.login.VectorCustomServerDialog'] = require('./components/views/login/VectorCustomServerDialog');
|
||||
module.exports.components['views.login.VectorLoginFooter'] = require('./components/views/login/VectorLoginFooter');
|
||||
module.exports.components['views.login.VectorLoginHeader'] = require('./components/views/login/VectorLoginHeader');
|
||||
module.exports.components['views.messages.DateSeparator'] = require('./components/views/messages/DateSeparator');
|
||||
module.exports.components['views.messages.MessageTimestamp'] = require('./components/views/messages/MessageTimestamp');
|
||||
module.exports.components['views.rooms.DNDRoomTile'] = require('./components/views/rooms/DNDRoomTile');
|
||||
module.exports.components['views.rooms.RoomDropTarget'] = require('./components/views/rooms/RoomDropTarget');
|
||||
module.exports.components['views.rooms.RoomTooltip'] = require('./components/views/rooms/RoomTooltip');
|
||||
module.exports.components['views.rooms.SearchBar'] = require('./components/views/rooms/SearchBar');
|
||||
module.exports.components['views.settings.IntegrationsManager'] = require('./components/views/settings/IntegrationsManager');
|
||||
module.exports.components['views.settings.Notifications'] = require('./components/views/settings/Notifications');
|
||||
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;
|
||||
|
||||
@@ -31,6 +31,8 @@ var linkifyMatrix = require('matrix-react-sdk/lib/linkify-matrix');
|
||||
var sanitizeHtml = require('sanitize-html');
|
||||
var q = require('q');
|
||||
|
||||
import {instanceForInstanceId, protocolNameForInstanceId} from '../../utils/DirectoryUtils';
|
||||
|
||||
linkifyMatrix(linkify);
|
||||
|
||||
module.exports = React.createClass({
|
||||
@@ -42,9 +44,7 @@ module.exports = React.createClass({
|
||||
|
||||
getDefaultProps: function() {
|
||||
return {
|
||||
config: {
|
||||
networks: [],
|
||||
},
|
||||
config: {},
|
||||
}
|
||||
},
|
||||
|
||||
@@ -52,36 +52,26 @@ module.exports = React.createClass({
|
||||
return {
|
||||
publicRooms: [],
|
||||
loading: true,
|
||||
network: null,
|
||||
protocolsLoading: true,
|
||||
instanceId: null,
|
||||
includeAll: false,
|
||||
roomServer: null,
|
||||
filterString: null,
|
||||
}
|
||||
},
|
||||
|
||||
componentWillMount: function() {
|
||||
// precompile Regexps
|
||||
this.portalRoomPatterns = {};
|
||||
this.nativePatterns = {};
|
||||
if (this.props.config.networks) {
|
||||
for (const network of Object.keys(this.props.config.networks)) {
|
||||
const network_info = this.props.config.networks[network];
|
||||
if (network_info.portalRoomPattern) {
|
||||
this.portalRoomPatterns[network] = new RegExp(network_info.portalRoomPattern);
|
||||
}
|
||||
if (network_info.nativePattern) {
|
||||
this.nativePatterns[network] = new RegExp(network_info.nativePattern);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
this.nextBatch = null;
|
||||
this.filterTimeout = null;
|
||||
this.scrollPanel = null;
|
||||
this.protocols = null;
|
||||
|
||||
this.setState({protocolsLoading: true});
|
||||
MatrixClientPeg.get().getThirdpartyProtocols().done((response) => {
|
||||
this.protocols = response;
|
||||
this.setState({protocolsLoading: false});
|
||||
}, (err) => {
|
||||
this.setState({protocolsLoading: false});
|
||||
if (MatrixClientPeg.get().isGuest()) {
|
||||
// Guests currently aren't allowed to use this API, so
|
||||
// ignore this as otherwise this error is literally the
|
||||
@@ -131,6 +121,11 @@ module.exports = React.createClass({
|
||||
if (my_server != MatrixClientPeg.getHomeServerName()) {
|
||||
opts.server = my_server;
|
||||
}
|
||||
if (this.state.instanceId) {
|
||||
opts.third_party_instance_id = this.state.instanceId;
|
||||
} else if (this.state.includeAll) {
|
||||
opts.include_all_networks = true;
|
||||
}
|
||||
if (this.nextBatch) opts.since = this.nextBatch;
|
||||
if (my_filter_string) opts.filter = { generic_search_term: my_filter_string } ;
|
||||
return MatrixClientPeg.get().publicRooms(opts).then((data) => {
|
||||
@@ -231,7 +226,7 @@ module.exports = React.createClass({
|
||||
}
|
||||
},
|
||||
|
||||
onOptionChange: function(server, network) {
|
||||
onOptionChange: function(server, instanceId, includeAll) {
|
||||
// clear next batch so we don't try to load more rooms
|
||||
this.nextBatch = null;
|
||||
this.setState({
|
||||
@@ -240,7 +235,8 @@ module.exports = React.createClass({
|
||||
// to clear the list anyway.
|
||||
publicRooms: [],
|
||||
roomServer: server,
|
||||
network: network,
|
||||
instanceId: instanceId,
|
||||
includeAll: includeAll,
|
||||
}, this.refreshRoomList);
|
||||
// We also refresh the room list each time even though this
|
||||
// filtering is client-side. It hopefully won't be client side
|
||||
@@ -271,7 +267,7 @@ module.exports = React.createClass({
|
||||
this.filterTimeout = setTimeout(() => {
|
||||
this.filterTimeout = null;
|
||||
this.refreshRoomList();
|
||||
}, 300);
|
||||
}, 700);
|
||||
},
|
||||
|
||||
onFilterClear: function() {
|
||||
@@ -286,14 +282,19 @@ module.exports = React.createClass({
|
||||
},
|
||||
|
||||
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') {
|
||||
// If we don't have a particular instance id selected, just show that rooms alias
|
||||
if (!this.state.instanceId) {
|
||||
// If the user specified an alias without a domain, add on whichever server is selected
|
||||
// in the dropdown
|
||||
if (alias.indexOf(':') == -1) {
|
||||
alias = alias + ':' + this.state.roomServer;
|
||||
}
|
||||
this.showRoomAlias(alias);
|
||||
} else {
|
||||
// This is a 3rd party protocol. Let's see if we
|
||||
// can join it
|
||||
const fields = this._getFieldsForThirdPartyLocation(alias, this.state.network);
|
||||
// This is a 3rd party protocol. Let's see if we can join it
|
||||
const protocolName = protocolNameForInstanceId(this.protocols, this.state.instanceId);
|
||||
const instance = instanceForInstanceId(this.protocols, this.state.instanceId);
|
||||
const fields = protocolName ? this._getFieldsForThirdPartyLocation(alias, this.protocols[protocolName], instance) : null;
|
||||
if (!fields) {
|
||||
const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
|
||||
Modal.createDialog(ErrorDialog, {
|
||||
@@ -302,8 +303,7 @@ module.exports = React.createClass({
|
||||
});
|
||||
return;
|
||||
}
|
||||
const protocol = this._protocolForThirdPartyNetwork(this.state.network);
|
||||
MatrixClientPeg.get().getThirdpartyLocation(protocol, fields).done((resp) => {
|
||||
MatrixClientPeg.get().getThirdpartyLocation(protocolName, fields).done((resp) => {
|
||||
if (resp.length > 0 && resp[0].alias) {
|
||||
this.showRoomAlias(resp[0].alias);
|
||||
} else {
|
||||
@@ -372,13 +372,7 @@ module.exports = React.createClass({
|
||||
|
||||
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 rooms = this.state.publicRooms;
|
||||
var rows = [];
|
||||
var self = this;
|
||||
var guestRead, guestJoin, perms;
|
||||
@@ -440,158 +434,105 @@ module.exports = React.createClass({
|
||||
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) {
|
||||
_stringLooksLikeId: function(s, field_type) {
|
||||
let pat = /^#[^\s]+:[^\s]/;
|
||||
if (
|
||||
network && network != '_matrix' &&
|
||||
this.nativePatterns[network]
|
||||
) {
|
||||
pat = this.nativePatterns[network];
|
||||
if (field_type && field_type.regexp) {
|
||||
pat = new RegExp(field_type.regexp);
|
||||
}
|
||||
|
||||
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].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
|
||||
_getFieldsForThirdPartyLocation: function(userInput, protocol, instance) {
|
||||
// make an object with the fields specified by that protocol. We
|
||||
// require that the values of all but the last field come from the
|
||||
// instance. The last is the user input.
|
||||
const required_fields = this.protocols[network_info.protocol].location_fields;
|
||||
const requiredFields = protocol.location_fields;
|
||||
if (!requiredFields) return null;
|
||||
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];
|
||||
for (let i = 0; i < requiredFields.length - 1; ++i) {
|
||||
const thisField = requiredFields[i];
|
||||
if (instance.fields[thisField] === undefined) return null;
|
||||
fields[thisField] = instance.fields[thisField];
|
||||
}
|
||||
fields[required_fields[required_fields.length - 1]] = user_input;
|
||||
fields[requiredFields[requiredFields.length - 1]] = userInput;
|
||||
return fields;
|
||||
},
|
||||
|
||||
render: function() {
|
||||
const SimpleRoomHeader = sdk.getComponent('rooms.SimpleRoomHeader');
|
||||
const Loader = sdk.getComponent("elements.Spinner");
|
||||
|
||||
if (this.state.protocolsLoading) {
|
||||
return (
|
||||
<div className="mx_RoomDirectory">
|
||||
<SimpleRoomHeader title="Directory" />
|
||||
<Loader />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
let content;
|
||||
if (this.state.loading) {
|
||||
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) {
|
||||
content = <i>No rooms to show</i>;
|
||||
scrollpanel_content = <i>No rooms to show</i>;
|
||||
} else {
|
||||
const ScrollPanel = sdk.getComponent("structures.ScrollPanel");
|
||||
content = <ScrollPanel ref={this.collectScrollPanel}
|
||||
className="mx_RoomDirectory_tableWrapper"
|
||||
onFillRequest={ this.onFillRequest }
|
||||
stickyBottom={false}
|
||||
startAtBottom={false}
|
||||
onResize={function(){}}
|
||||
>
|
||||
<table ref="directory_table" className="mx_RoomDirectory_table">
|
||||
<tbody>
|
||||
{ this.getRows() }
|
||||
</tbody>
|
||||
</table>
|
||||
</ScrollPanel>;
|
||||
scrollpanel_content = <table ref="directory_table" className="mx_RoomDirectory_table">
|
||||
<tbody>
|
||||
{ this.getRows() }
|
||||
</tbody>
|
||||
</table>;
|
||||
}
|
||||
const ScrollPanel = sdk.getComponent("structures.ScrollPanel");
|
||||
content = <ScrollPanel ref={this.collectScrollPanel}
|
||||
className="mx_RoomDirectory_tableWrapper"
|
||||
onFillRequest={ this.onFillRequest }
|
||||
stickyBottom={false}
|
||||
startAtBottom={false}
|
||||
onResize={function(){}}
|
||||
>
|
||||
{ scrollpanel_content }
|
||||
</ScrollPanel>;
|
||||
}
|
||||
|
||||
const protocolName = protocolNameForInstanceId(this.protocols, this.state.instanceId);
|
||||
let instance_expected_field_type;
|
||||
if (
|
||||
protocolName &&
|
||||
this.protocols &&
|
||||
this.protocols[protocolName] &&
|
||||
this.protocols[protocolName].location_fields.length > 0 &&
|
||||
this.protocols[protocolName].field_types
|
||||
) {
|
||||
const last_field = this.protocols[protocolName].location_fields.slice(-1)[0];
|
||||
instance_expected_field_type = this.protocols[protocolName].field_types[last_field];
|
||||
}
|
||||
|
||||
|
||||
let placeholder = 'Search for a room';
|
||||
if (!this.state.instanceId) {
|
||||
placeholder = '#example:' + this.state.roomServer;
|
||||
} else if (instance_expected_field_type) {
|
||||
placeholder = instance_expected_field_type.placeholder;
|
||||
}
|
||||
|
||||
let showJoinButton = this._stringLooksLikeId(this.state.filterString, instance_expected_field_type);
|
||||
if (protocolName) {
|
||||
const instance = instanceForInstanceId(this.protocols, this.state.instanceId);
|
||||
if (this._getFieldsForThirdPartyLocation(this.state.filterString, this.protocols[protocolName], instance) === null) {
|
||||
showJoinButton = false;
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
const showJoinButton = (
|
||||
this._stringLooksLikeId(this.state.filterString, this.state.network) &&
|
||||
this._getFieldsForThirdPartyLocation(this.state.filterString, this.state.network)
|
||||
);
|
||||
|
||||
const SimpleRoomHeader = sdk.getComponent('rooms.SimpleRoomHeader');
|
||||
const NetworkDropdown = sdk.getComponent('directory.NetworkDropdown');
|
||||
const DirectorySearchBox = sdk.getComponent('elements.DirectorySearchBox');
|
||||
return (
|
||||
@@ -604,7 +545,7 @@ module.exports = React.createClass({
|
||||
onChange={this.onFilterChange} onClear={this.onFilterClear} onJoinClick={this.onJoinClick}
|
||||
placeholder={placeholder} showJoinButton={showJoinButton}
|
||||
/>
|
||||
<NetworkDropdown config={this.props.config} onOptionChange={this.onOptionChange} />
|
||||
<NetworkDropdown config={this.props.config} protocols={this.protocols} onOptionChange={this.onOptionChange} />
|
||||
</div>
|
||||
{content}
|
||||
</div>
|
||||
|
||||
@@ -22,7 +22,8 @@ module.exports = React.createClass({
|
||||
displayName: 'ViewSource',
|
||||
|
||||
propTypes: {
|
||||
onFinished: React.PropTypes.func.isRequired
|
||||
content: React.PropTypes.object.isRequired,
|
||||
onFinished: React.PropTypes.func.isRequired,
|
||||
},
|
||||
|
||||
componentDidMount: function() {
|
||||
@@ -45,10 +46,9 @@ module.exports = React.createClass({
|
||||
return (
|
||||
<div className="mx_ViewSource">
|
||||
<pre>
|
||||
{JSON.stringify(this.props.mxEvent.event, null, 2)}
|
||||
{JSON.stringify(this.props.content, null, 2)}
|
||||
</pre>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
@@ -20,9 +20,10 @@ var React = require('react');
|
||||
|
||||
var MatrixClientPeg = require('matrix-react-sdk/lib/MatrixClientPeg');
|
||||
var dis = require('matrix-react-sdk/lib/dispatcher');
|
||||
var sdk = require('matrix-react-sdk')
|
||||
var sdk = require('matrix-react-sdk');
|
||||
var Modal = require('matrix-react-sdk/lib/Modal');
|
||||
var Resend = require("matrix-react-sdk/lib/Resend");
|
||||
import * as UserSettingsStore from 'matrix-react-sdk/lib/UserSettingsStore';
|
||||
|
||||
module.exports = React.createClass({
|
||||
displayName: 'MessageContextMenu',
|
||||
@@ -46,7 +47,16 @@ module.exports = React.createClass({
|
||||
onViewSourceClick: function() {
|
||||
var ViewSource = sdk.getComponent('structures.ViewSource');
|
||||
Modal.createDialog(ViewSource, {
|
||||
mxEvent: this.props.mxEvent
|
||||
content: this.props.mxEvent.event,
|
||||
}, 'mx_Dialog_viewsource');
|
||||
if (this.props.onFinished) this.props.onFinished();
|
||||
},
|
||||
|
||||
onViewClearSourceClick: function() {
|
||||
const ViewSource = sdk.getComponent('structures.ViewSource');
|
||||
Modal.createDialog(ViewSource, {
|
||||
// FIXME: _clearEvent is private
|
||||
content: this.props.mxEvent._clearEvent,
|
||||
}, 'mx_Dialog_viewsource');
|
||||
if (this.props.onFinished) this.props.onFinished();
|
||||
},
|
||||
@@ -73,7 +83,7 @@ module.exports = React.createClass({
|
||||
if (this.props.onFinished) this.props.onFinished();
|
||||
},
|
||||
|
||||
onPermalinkClick: function() {
|
||||
closeMenu: function() {
|
||||
if (this.props.onFinished) this.props.onFinished();
|
||||
},
|
||||
|
||||
@@ -96,10 +106,12 @@ module.exports = React.createClass({
|
||||
var eventStatus = this.props.mxEvent.status;
|
||||
var resendButton;
|
||||
var viewSourceButton;
|
||||
var viewClearSourceButton;
|
||||
var redactButton;
|
||||
var cancelButton;
|
||||
var permalinkButton;
|
||||
var unhidePreviewButton;
|
||||
var externalURLButton;
|
||||
|
||||
if (eventStatus === 'not_sent') {
|
||||
resendButton = (
|
||||
@@ -131,6 +143,14 @@ module.exports = React.createClass({
|
||||
</div>
|
||||
);
|
||||
|
||||
if (this.props.mxEvent.getType() !== this.props.mxEvent.getWireType()) {
|
||||
viewClearSourceButton = (
|
||||
<div className="mx_MessageContextMenu_field" onClick={this.onViewClearSourceClick}>
|
||||
View Decrypted Source
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
if (this.props.eventTileOps) {
|
||||
if (this.props.eventTileOps.isWidgetHidden()) {
|
||||
unhidePreviewButton = (
|
||||
@@ -145,7 +165,7 @@ module.exports = React.createClass({
|
||||
permalinkButton = (
|
||||
<div className="mx_MessageContextMenu_field">
|
||||
<a href={ "https://matrix.to/#/" + this.props.mxEvent.getRoomId() +"/"+ this.props.mxEvent.getId() }
|
||||
target="_blank" onClick={ this.onPermalinkClick }>Permalink</a>
|
||||
target="_blank" rel="noopener" onClick={ this.closeMenu }>Permalink</a>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -155,15 +175,28 @@ module.exports = React.createClass({
|
||||
</div>
|
||||
);
|
||||
|
||||
// Bridges can provide a 'external_url' to link back to the source.
|
||||
if( typeof(this.props.mxEvent.event.content.external_url) === "string") {
|
||||
externalURLButton = (
|
||||
<div className="mx_MessageContextMenu_field">
|
||||
<a href={ this.props.mxEvent.event.content.external_url }
|
||||
rel="noopener" target="_blank" onClick={ this.closeMenu }>Source URL</a>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
return (
|
||||
<div>
|
||||
{resendButton}
|
||||
{redactButton}
|
||||
{cancelButton}
|
||||
{viewSourceButton}
|
||||
{viewClearSourceButton}
|
||||
{unhidePreviewButton}
|
||||
{permalinkButton}
|
||||
{quoteButton}
|
||||
{UserSettingsStore.isFeatureEnabled('rich_text_editor') ? quoteButton : null}
|
||||
{externalURLButton}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -31,9 +31,10 @@ export default class ChangelogDialog extends React.Component {
|
||||
const version = this.props.newVersion.split('-');
|
||||
const version2 = this.props.version.split('-');
|
||||
if(version == null || version2 == null) return;
|
||||
// parse versions of form: [vectorversion]-react-[react-sdk-version]-js-[js-sdk-version]
|
||||
for(let i=0; i<REPOS.length; i++) {
|
||||
const oldVersion = version2[2*i+1];
|
||||
const newVersion = version[2*i+1];
|
||||
const oldVersion = version2[2*i];
|
||||
const newVersion = version[2*i];
|
||||
request(`https://api.github.com/repos/${REPOS[i]}/compare/${oldVersion}...${newVersion}`, (a, b, body) => {
|
||||
if(body == null) return;
|
||||
this.setState({[REPOS[i]]: JSON.parse(body).commits});
|
||||
@@ -44,7 +45,7 @@ export default class ChangelogDialog extends React.Component {
|
||||
_elementsForCommit(commit) {
|
||||
return (
|
||||
<li key={commit.sha} className="mx_ChangelogDialog_li">
|
||||
<a href={commit.html_url} target="_blank" ref="noopener">
|
||||
<a href={commit.html_url} target="_blank" rel="noopener">
|
||||
{commit.commit.message}
|
||||
</a>
|
||||
</li>
|
||||
|
||||
@@ -16,6 +16,9 @@ limitations under the License.
|
||||
|
||||
import React from 'react';
|
||||
import MatrixClientPeg from 'matrix-react-sdk/lib/MatrixClientPeg';
|
||||
import {instanceForInstanceId} from '../../../utils/DirectoryUtils';
|
||||
|
||||
const DEFAULT_ICON_URL = "img/network-matrix.svg";
|
||||
|
||||
export default class NetworkDropdown extends React.Component {
|
||||
constructor(props) {
|
||||
@@ -35,20 +38,11 @@ export default class NetworkDropdown extends React.Component {
|
||||
this.inputTextBox = null;
|
||||
|
||||
const server = MatrixClientPeg.getHomeServerName();
|
||||
let defaultNetwork = null;
|
||||
if (
|
||||
this.props.config.serverConfig &&
|
||||
this.props.config.serverConfig[server] &&
|
||||
this.props.config.serverConfig[server].networks &&
|
||||
this.props.config.serverConfig[server].networks.indexOf('_matrix') > -1
|
||||
) {
|
||||
defaultNetwork = '_matrix';
|
||||
}
|
||||
|
||||
this.state = {
|
||||
expanded: false,
|
||||
selectedServer: server,
|
||||
selectedNetwork: defaultNetwork,
|
||||
selectedInstance: null,
|
||||
includeAllNetworks: false,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -58,7 +52,7 @@ export default class NetworkDropdown extends React.Component {
|
||||
document.addEventListener('click', this.onDocumentClick, false);
|
||||
|
||||
// fire this now so the defaults can be set up
|
||||
this.props.onOptionChange(this.state.selectedServer, this.state.selectedNetwork);
|
||||
this.props.onOptionChange(this.state.selectedServer, this.state.selectedInstance, this.state.includeAllNetworks);
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
@@ -98,13 +92,14 @@ export default class NetworkDropdown extends React.Component {
|
||||
ev.preventDefault();
|
||||
}
|
||||
|
||||
onMenuOptionClick(server, network, ev) {
|
||||
onMenuOptionClick(server, instance, includeAll) {
|
||||
this.setState({
|
||||
expanded: false,
|
||||
selectedServer: server,
|
||||
selectedNetwork: network,
|
||||
selectedInstanceId: instance ? instance.instance_id : null,
|
||||
includeAll: includeAll,
|
||||
});
|
||||
this.props.onOptionChange(server, network);
|
||||
this.props.onOptionChange(server, instance ? instance.instance_id : null, includeAll);
|
||||
}
|
||||
|
||||
onInputKeyUp(e) {
|
||||
@@ -144,11 +139,22 @@ export default class NetworkDropdown extends React.Component {
|
||||
servers.unshift(MatrixClientPeg.getHomeServerName());
|
||||
}
|
||||
|
||||
// For our own HS, we can use the instance_ids given in the third party protocols
|
||||
// response to get the server to filter the room list by network for us.
|
||||
// We can't get thirdparty protocols for remote server yet though, so for those
|
||||
// we can only show the default room list.
|
||||
for (const server of servers) {
|
||||
options.push(this._makeMenuOption(server, null));
|
||||
if (this.props.config.serverConfig && this.props.config.serverConfig[server] && this.props.config.serverConfig[server].networks) {
|
||||
for (const network of this.props.config.serverConfig[server].networks) {
|
||||
options.push(this._makeMenuOption(server, network));
|
||||
options.push(this._makeMenuOption(server, null, true));
|
||||
if (server == MatrixClientPeg.getHomeServerName()) {
|
||||
options.push(this._makeMenuOption(server, null, false));
|
||||
if (this.props.protocols) {
|
||||
for (const proto of Object.keys(this.props.protocols)) {
|
||||
if (!this.props.protocols[proto].instances) continue;
|
||||
for (const instance of this.props.protocols[proto].instances) {
|
||||
if (!instance.instance_id) continue;
|
||||
options.push(this._makeMenuOption(server, instance, false));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -156,50 +162,36 @@ export default class NetworkDropdown extends React.Component {
|
||||
return options;
|
||||
}
|
||||
|
||||
_makeMenuOption(server, network, wire_onclick) {
|
||||
if (wire_onclick === undefined) wire_onclick = true;
|
||||
_makeMenuOption(server, instance, includeAll, handleClicks) {
|
||||
if (handleClicks === undefined) handleClicks = true;
|
||||
|
||||
let icon;
|
||||
let name;
|
||||
let span_class;
|
||||
let key;
|
||||
|
||||
if (network === null) {
|
||||
if (!instance && includeAll) {
|
||||
key = server;
|
||||
name = server;
|
||||
span_class = 'mx_NetworkDropdown_menu_all';
|
||||
} else if (network == '_matrix') {
|
||||
} else if (!instance) {
|
||||
key = server + '_all';
|
||||
name = 'Matrix';
|
||||
icon = <img src="img/network-matrix.svg" width="16" height="16" />;
|
||||
icon = <img src="img/network-matrix.svg" />;
|
||||
span_class = 'mx_NetworkDropdown_menu_network';
|
||||
} else {
|
||||
if (this.props.config.networks[network] === undefined) {
|
||||
throw new Error(network + ' network missing from config');
|
||||
}
|
||||
if (this.props.config.networks[network].name) {
|
||||
name = this.props.config.networks[network].name;
|
||||
} else {
|
||||
name = network;
|
||||
}
|
||||
if (this.props.config.networks[network].icon) {
|
||||
// omit height here so if people define a non-square logo in the config, it
|
||||
// will keep the aspect when it scales
|
||||
icon = <img src={this.props.config.networks[network].icon} width="16" />;
|
||||
} else {
|
||||
icon = <img src={iconPath} width="16" height="16" />;
|
||||
}
|
||||
|
||||
key = server + '_inst_' + instance.instance_id;
|
||||
icon = <img src={instance.icon || DEFAULT_ICON_URL} />;
|
||||
name = instance.desc;
|
||||
span_class = 'mx_NetworkDropdown_menu_network';
|
||||
}
|
||||
|
||||
const click_handler = wire_onclick ? this.onMenuOptionClick.bind(this, server, network) : null;
|
||||
|
||||
let key = server;
|
||||
if (network !== null) {
|
||||
key += '_' + network;
|
||||
}
|
||||
const click_handler = handleClicks ? this.onMenuOptionClick.bind(this, server, instance, includeAll) : null;
|
||||
|
||||
return <div key={key} className="mx_NetworkDropdown_networkoption" onClick={click_handler}>
|
||||
{icon}
|
||||
<span className={span_class}>{name}</span>
|
||||
</div>;
|
||||
<span className="mx_NetworkDropdown_menu_network">{name}</span>
|
||||
</div>
|
||||
}
|
||||
|
||||
render() {
|
||||
@@ -216,8 +208,9 @@ export default class NetworkDropdown extends React.Component {
|
||||
placeholder="matrix.org" // 'matrix.org' as an example of an HS name
|
||||
/>
|
||||
} else {
|
||||
const instance = instanceForInstanceId(this.props.protocols, this.state.selectedInstanceId);
|
||||
current_value = this._makeMenuOption(
|
||||
this.state.selectedServer, this.state.selectedNetwork, false
|
||||
this.state.selectedServer, instance, this.state.includeAll, false
|
||||
);
|
||||
}
|
||||
|
||||
@@ -233,12 +226,12 @@ export default class NetworkDropdown extends React.Component {
|
||||
|
||||
NetworkDropdown.propTypes = {
|
||||
onOptionChange: React.PropTypes.func.isRequired,
|
||||
protocols: React.PropTypes.object,
|
||||
// The room directory config. May have a 'servers' key that is a list of server names to include in the dropdown
|
||||
config: React.PropTypes.object,
|
||||
};
|
||||
|
||||
NetworkDropdown.defaultProps = {
|
||||
config: {
|
||||
networks: [],
|
||||
}
|
||||
protocols: {},
|
||||
config: {},
|
||||
};
|
||||
|
||||
|
||||
@@ -16,50 +16,79 @@ limitations under the License.
|
||||
|
||||
'use strict';
|
||||
|
||||
var React = require('react');
|
||||
var sdk = require('matrix-react-sdk');
|
||||
import React from 'react';
|
||||
import sdk from 'matrix-react-sdk';
|
||||
import Modal from 'matrix-react-sdk/lib/Modal';
|
||||
import PlatformPeg from 'matrix-react-sdk/lib/PlatformPeg';
|
||||
|
||||
/**
|
||||
* Check a version string is compatible with the Changelog
|
||||
* dialog
|
||||
* dialog ([vectorversion]-react-[react-sdk-version]-js-[js-sdk-version])
|
||||
*/
|
||||
function checkVersion(ver) {
|
||||
const parts = ver.split('-');
|
||||
return parts[0] == 'vector' && parts[2] == 'react' && parts[4] == 'js';
|
||||
return parts.length == 5 && parts[1] == 'react' && parts[3] == 'js';
|
||||
}
|
||||
|
||||
export default function NewVersionBar(props) {
|
||||
const onChangelogClicked = () => {
|
||||
const ChangelogDialog = sdk.getComponent('dialogs.ChangelogDialog');
|
||||
export default React.createClass({
|
||||
propTypes: {
|
||||
version: React.PropTypes.string.isRequired,
|
||||
newVersion: React.PropTypes.string.isRequired,
|
||||
releaseNotes: React.PropTypes.string,
|
||||
},
|
||||
|
||||
Modal.createDialog(ChangelogDialog, {
|
||||
version: props.version,
|
||||
newVersion: props.newVersion,
|
||||
displayReleaseNotes: function(releaseNotes) {
|
||||
const QuestionDialog = sdk.getComponent('dialogs.QuestionDialog');
|
||||
Modal.createDialog(QuestionDialog, {
|
||||
title: "What's New",
|
||||
description: <pre className="changelog_text">{releaseNotes}</pre>,
|
||||
button: "Update",
|
||||
onFinished: (update) => {
|
||||
if(update) {
|
||||
window.location.reload();
|
||||
if(update && PlatformPeg.get()) {
|
||||
PlatformPeg.get().installUpdate();
|
||||
}
|
||||
}
|
||||
});
|
||||
};
|
||||
},
|
||||
|
||||
let changelog_button;
|
||||
if (checkVersion(props.version) && checkVersion(props.newVersion)) {
|
||||
changelog_button = <button className="mx_MatrixToolbar_action" onClick={onChangelogClicked}>Changelog</button>;
|
||||
}
|
||||
return (
|
||||
<div className="mx_MatrixToolbar">
|
||||
<img className="mx_MatrixToolbar_warning" src="img/warning.svg" width="24" height="23" alt="/!\"/>
|
||||
<div className="mx_MatrixToolbar_content">
|
||||
A new version of Riot is available. Refresh your browser.
|
||||
displayChangelog: function() {
|
||||
const ChangelogDialog = sdk.getComponent('dialogs.ChangelogDialog');
|
||||
Modal.createDialog(ChangelogDialog, {
|
||||
version: this.props.version,
|
||||
newVersion: this.props.newVersion,
|
||||
onFinished: (update) => {
|
||||
if(update && PlatformPeg.get()) {
|
||||
PlatformPeg.get().installUpdate();
|
||||
}
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
onUpdateClicked: function() {
|
||||
PlatformPeg.get().installUpdate();
|
||||
},
|
||||
|
||||
render: function() {
|
||||
let action_button;
|
||||
// If we have release notes to display, we display them. Otherwise,
|
||||
// we display the Changelog Dialog which takes two versions and
|
||||
// automatically tells you what's changed (provided the versions
|
||||
// are in the right format)
|
||||
if (this.props.releaseNotes) {
|
||||
action_button = <button className="mx_MatrixToolbar_action" onClick={this.displayReleaseNotes}>What's new?</button>;
|
||||
} else if (checkVersion(this.props.version) && checkVersion(this.props.newVersion)) {
|
||||
action_button = <button className="mx_MatrixToolbar_action" onClick={this.displayChangelog}>What's new?</button>;
|
||||
} else if (PlatformPeg.get()) {
|
||||
action_button = <button className="mx_MatrixToolbar_action" onClick={this.onUpdateClicked}>Update</button>;
|
||||
}
|
||||
return (
|
||||
<div className="mx_MatrixToolbar">
|
||||
<img className="mx_MatrixToolbar_warning" src="img/warning.svg" width="24" height="23" alt="/!\"/>
|
||||
<div className="mx_MatrixToolbar_content">
|
||||
A new version of Riot is available.
|
||||
</div>
|
||||
{action_button}
|
||||
</div>
|
||||
{changelog_button}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
NewVersionBar.propTypes = {
|
||||
version: React.PropTypes.string.isRequired,
|
||||
newVersion: React.PropTypes.string.isRequired,
|
||||
};
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
@@ -214,6 +214,9 @@ textarea {
|
||||
cursor: pointer;
|
||||
color: #76cfa6;
|
||||
background-color: #fff;
|
||||
|
||||
/* align images in buttons (eg spinners) */
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
.mx_Dialog button.mx_Dialog_primary, .mx_Dialog input[type="submit"].mx_Dialog_primary {
|
||||
@@ -285,3 +288,7 @@ textarea {
|
||||
cursor: pointer;
|
||||
display: inline;
|
||||
}
|
||||
|
||||
.changelog_text {
|
||||
font-family: 'Open Sans', Arial, Helvetica, Sans-Serif;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,36 @@
|
||||
/*
|
||||
Copyright 2016 OpenMarket Ltd
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
.mx_TextualEvent.mx_MemberEventListSummary_summary {
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.mx_MemberEventListSummary_avatars {
|
||||
display: inline-block;
|
||||
margin-right: 8px;
|
||||
padding-top: 8px;
|
||||
line-height: 12px;
|
||||
}
|
||||
|
||||
.mx_MemberEventListSummary_avatars .mx_BaseAvatar {
|
||||
margin-right: -4px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.mx_MemberEventListSummary_toggle {
|
||||
color:#76cfa6;
|
||||
cursor:pointer;
|
||||
}
|
||||
@@ -44,3 +44,17 @@ limitations under the License.
|
||||
vertical-align: middle;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
/* Remove the border and padding for iframes for download links. */
|
||||
.mx_MImageBody_download iframe {
|
||||
margin: 0px;
|
||||
padding: 0px;
|
||||
border: none;
|
||||
width: 100%;
|
||||
/* Set the height of the iframe to be 1 line of text.
|
||||
* Iframes don't automatically size themselves to fit their content.
|
||||
* So either we have to fix the height of the iframe using CSS or
|
||||
* use javascript's cross-origin postMessage API to communicate how
|
||||
* big the content of the iframe is. */
|
||||
height: 1.5em;
|
||||
}
|
||||
|
||||
@@ -44,14 +44,23 @@ limitations under the License.
|
||||
border-bottom: 1px solid rgba(0,0,0,0.1);
|
||||
}
|
||||
|
||||
.mx_MemberDeviceInfo_block,
|
||||
.mx_MemberDeviceInfo_unblock {
|
||||
.mx_MemberDeviceInfo_blacklist,
|
||||
.mx_MemberDeviceInfo_unblacklist {
|
||||
float: right;
|
||||
}
|
||||
|
||||
/* "Unblacklist" is too long for a regular button: make it wider and
|
||||
reduce the padding. */
|
||||
.mx_EncryptedEventDialog .mx_MemberDeviceInfo_blacklist,
|
||||
.mx_EncryptedEventDialog .mx_MemberDeviceInfo_unblacklist {
|
||||
width: 8em;
|
||||
padding-left: 1em;
|
||||
padding-right: 1em;
|
||||
}
|
||||
|
||||
.mx_MemberDeviceInfo div.mx_MemberDeviceInfo_verified,
|
||||
.mx_MemberDeviceInfo div.mx_MemberDeviceInfo_unverified,
|
||||
.mx_MemberDeviceInfo div.mx_MemberDeviceInfo_blocked {
|
||||
.mx_MemberDeviceInfo div.mx_MemberDeviceInfo_blacklisted {
|
||||
float: right;
|
||||
padding-left: 1em;
|
||||
}
|
||||
@@ -64,6 +73,6 @@ limitations under the License.
|
||||
color: #e8bf37;
|
||||
}
|
||||
|
||||
.mx_MemberDeviceInfo div.mx_MemberDeviceInfo_blocked {
|
||||
.mx_MemberDeviceInfo div.mx_MemberDeviceInfo_blacklisted {
|
||||
color: #ba6363;
|
||||
}
|
||||
|
||||
@@ -92,7 +92,7 @@ limitations under the License.
|
||||
}
|
||||
|
||||
.mx_MemberInfo_createRoom_label {
|
||||
width: 150px ! important;
|
||||
width: initial ! important;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
|
||||
@@ -20,7 +20,9 @@ limitations under the License.
|
||||
}
|
||||
|
||||
.mx_RoomSettings_leaveButton,
|
||||
.mx_RoomSettings_integrationsButton {
|
||||
.mx_RoomSettings_integrationsButton,
|
||||
.mx_RoomSettings_integrationsButton_error {
|
||||
position: relative;
|
||||
height: 36px;
|
||||
background-color: #76cfa6;
|
||||
border-radius: 36px;
|
||||
@@ -33,6 +35,21 @@ limitations under the License.
|
||||
padding-left: 12px;
|
||||
padding-right: 12px;
|
||||
}
|
||||
.mx_RoomSettings_integrationsButton_error {
|
||||
pointer: not-allowed;
|
||||
}
|
||||
.mx_RoomSettings_integrationsButton_errorPopup {
|
||||
position: absolute;
|
||||
top: 110%;
|
||||
left: -26%;
|
||||
width: 150%;
|
||||
padding: 2%;
|
||||
font-size: 10pt;
|
||||
line-height: 1.5em;
|
||||
border-radius: 5px;
|
||||
background-color: #000;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.mx_RoomSettings_e2eIcon {
|
||||
padding-left: 4px;
|
||||
|
||||
@@ -62,6 +62,7 @@ limitations under the License.
|
||||
|
||||
.mx_RoomDirectory_searchbox {
|
||||
display: table-cell;
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
.mx_RoomDirectory_listheader .mx_NetworkDropdown {
|
||||
|
||||
23
src/utils/DirectoryUtils.js
Normal file
@@ -0,0 +1,23 @@
|
||||
// Find a protocol 'instance' with a given instance_id
|
||||
// in the supplied protocols dict
|
||||
export function instanceForInstanceId(protocols, instance_id) {
|
||||
if (!instance_id) return null;
|
||||
for (const proto of Object.keys(protocols)) {
|
||||
if (!protocols[proto].instances && protocols[proto].instances instanceof Array) continue;
|
||||
for (const instance of protocols[proto].instances) {
|
||||
if (instance.instance_id == instance_id) return instance;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// given an instance_id, return the name of the protocol for
|
||||
// that instance ID in the supplied protocols dict
|
||||
export function protocolNameForInstanceId(protocols, instance_id) {
|
||||
if (!instance_id) return null;
|
||||
for (const proto of Object.keys(protocols)) {
|
||||
if (!protocols[proto].instances && protocols[proto].instances instanceof Array) continue;
|
||||
for (const instance of protocols[proto].instances) {
|
||||
if (instance.instance_id == instance_id) return proto;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -20,14 +20,16 @@
|
||||
<meta name="msapplication-TileImage" content="vector-icons/mstile-144x144.png">
|
||||
<meta name="msapplication-config" content="vector-icons/browserconfig.xml">
|
||||
<meta name="theme-color" content="#ffffff">
|
||||
<% for(var i=0; i<htmlWebpackPlugin.files.css.length; i++) {%>
|
||||
<link href="<%= htmlWebpackPlugin.files.css[i] %>" rel="stylesheet">
|
||||
<% } %>
|
||||
</head>
|
||||
<body style="height: 100%;">
|
||||
<section id="matrixchat" style="height: 100%;"></section>
|
||||
<!-- load olm, if possible. -->
|
||||
<script src="olm.js"></script>
|
||||
<script src="bundle.js"></script>
|
||||
<noscript>Sorry, Riot requires JavaScript to be enabled.</noscript>
|
||||
<link rel="stylesheet" href="bundle.css">
|
||||
<% for(var i=0; i<htmlWebpackPlugin.files.js.length; i++) {%>
|
||||
<script src="<%= htmlWebpackPlugin.files.js[i] %>"></script>
|
||||
<% } %>
|
||||
<img src="img/warning.svg" width="24" height="23" style="visibility: hidden; position: absolute; top: 0px; left: 0px;"/>
|
||||
<audio id="messageAudio">
|
||||
<source src="media/message.ogg" type="audio/ogg" />
|
||||
@@ -18,11 +18,19 @@ limitations under the License.
|
||||
|
||||
// for ES6 stuff like startsWith() that Safari doesn't handle
|
||||
// and babel doesn't do by default
|
||||
// Note we use this, as well as the babel transform-runtime plugin
|
||||
// since transform-runtime does not cover instance methods
|
||||
// such as "foobar".includes("foo") which bits of our library
|
||||
// code use, but the babel transform-runtime plugin allows the
|
||||
// regenerator runtime to be injected early enough in the process
|
||||
// (it can't be here as it's too late: the alternative is to put
|
||||
// the babel-polyfill as the first 'entry' in the webpack config).
|
||||
// https://babeljs.io/docs/plugins/transform-runtime/
|
||||
require('babel-polyfill');
|
||||
|
||||
// CSS requires: just putting them here for now as CSS is going to be
|
||||
// refactored soon anyway
|
||||
require('../../vector/components.css');
|
||||
// refactored "soon" anyway
|
||||
require('../../build/components.css');
|
||||
require('gemini-scrollbar/gemini-scrollbar.css');
|
||||
require('gfm.css/gfm.css');
|
||||
require('highlight.js/styles/github.css');
|
||||
@@ -39,16 +47,17 @@ if (process.env.NODE_ENV !== 'production') {
|
||||
var RunModernizrTests = require("./modernizr"); // this side-effects a global
|
||||
var ReactDOM = require("react-dom");
|
||||
var sdk = require("matrix-react-sdk");
|
||||
var PlatformPeg = require("matrix-react-sdk/lib/PlatformPeg");
|
||||
sdk.loadSkin(require('../component-index'));
|
||||
var VectorConferenceHandler = require('../VectorConferenceHandler');
|
||||
var UpdateChecker = require("./updater");
|
||||
var q = require('q');
|
||||
var request = require('browser-request');
|
||||
|
||||
import UAParser from 'ua-parser-js';
|
||||
import url from 'url';
|
||||
|
||||
import {parseQs, parseQsFromFragment} from './url_utils';
|
||||
import Platform from './platform';
|
||||
|
||||
var lastLocationHashSet = null;
|
||||
|
||||
@@ -103,10 +112,6 @@ function onHashChange(ev) {
|
||||
routeUrl(window.location);
|
||||
}
|
||||
|
||||
function onVersion(current, latest) {
|
||||
window.matrixChat.onVersion(current, latest);
|
||||
}
|
||||
|
||||
var loaded = false;
|
||||
var lastLoadedScreen = null;
|
||||
|
||||
@@ -114,6 +119,8 @@ var lastLoadedScreen = null;
|
||||
// so a web page can update the URL bar appropriately.
|
||||
var onNewScreen = function(screen) {
|
||||
console.log("newscreen "+screen);
|
||||
// just remember the most recent screen while we are loading, so that the
|
||||
// user doesn't see the URL bar doing a dance
|
||||
if (!loaded) {
|
||||
lastLoadedScreen = screen;
|
||||
} else {
|
||||
@@ -136,47 +143,36 @@ var makeRegistrationUrl = function() {
|
||||
'#/register';
|
||||
}
|
||||
|
||||
|
||||
function getDefaultDeviceDisplayName() {
|
||||
// strip query-string and fragment from uri
|
||||
let u = url.parse(window.location.href);
|
||||
u.search = "";
|
||||
u.hash = "";
|
||||
let app_name = u.format();
|
||||
|
||||
let ua = new UAParser();
|
||||
return app_name + " via " + ua.getBrowser().name +
|
||||
" on " + ua.getOS().name;
|
||||
}
|
||||
|
||||
window.addEventListener('hashchange', onHashChange);
|
||||
window.onload = function() {
|
||||
console.log("window.onload");
|
||||
if (!validBrowser) {
|
||||
return;
|
||||
}
|
||||
UpdateChecker.setVersionListener(onVersion);
|
||||
UpdateChecker.run();
|
||||
routeUrl(window.location);
|
||||
loaded = true;
|
||||
if (lastLoadedScreen) {
|
||||
onNewScreen(lastLoadedScreen);
|
||||
lastLoadedScreen = null;
|
||||
}
|
||||
}
|
||||
|
||||
function getConfig() {
|
||||
let deferred = q.defer();
|
||||
|
||||
request(
|
||||
{ method: "GET", url: "config.json", json: true },
|
||||
{ method: "GET", url: "config.json" },
|
||||
(err, response, body) => {
|
||||
if (err || response.status < 200 || response.status >= 300) {
|
||||
// Lack of a config isn't an error, we should
|
||||
// just use the defaults.
|
||||
// Also treat a blank config as no config, assuming
|
||||
// the status code is 0, because we don't get 404s
|
||||
// from file: URIs so this is the only way we can
|
||||
// not fail if the file doesn't exist when loading
|
||||
// from a file:// URI.
|
||||
if (response) {
|
||||
if (response.status == 404 || (response.status == 0 && body == '')) {
|
||||
deferred.resolve({});
|
||||
}
|
||||
}
|
||||
deferred.reject({err: err, response: response});
|
||||
return;
|
||||
}
|
||||
|
||||
deferred.resolve(body);
|
||||
// We parse the JSON ourselves rather than use the JSON
|
||||
// parameter, since this throws a parse error on empty
|
||||
// which breaks if there's no config.json and we're
|
||||
// loading from the filesystem (see above).
|
||||
deferred.resolve(JSON.parse(body));
|
||||
}
|
||||
);
|
||||
|
||||
@@ -202,6 +198,9 @@ async function loadApp() {
|
||||
const fragparts = parseQsFromFragment(window.location);
|
||||
const params = parseQs(window.location);
|
||||
|
||||
// set the platform for react sdk (our Platform object automatically picks the right one)
|
||||
PlatformPeg.set(new Platform());
|
||||
|
||||
// don't try to redirect to the native apps if we're
|
||||
// verifying a 3pid
|
||||
const preventRedirect = Boolean(fragparts.params.client_secret);
|
||||
@@ -226,13 +225,7 @@ async function loadApp() {
|
||||
try {
|
||||
configJson = await getConfig();
|
||||
} catch (e) {
|
||||
// On 404 errors, carry on without a config,
|
||||
// but on other errors, fail, otherwise it will
|
||||
// lead to subtle errors where the app runs with
|
||||
// the default config if it fails to fetch config.json.
|
||||
if (e.response.status != 404) {
|
||||
configError = e;
|
||||
}
|
||||
configError = e;
|
||||
}
|
||||
|
||||
console.log("Vector starting at "+window.location);
|
||||
@@ -241,6 +234,8 @@ async function loadApp() {
|
||||
Unable to load config file: please refresh the page to try again.
|
||||
</div>, document.getElementById('matrixchat'));
|
||||
} else if (validBrowser) {
|
||||
UpdateChecker.start();
|
||||
|
||||
var MatrixChat = sdk.getComponent('structures.MatrixChat');
|
||||
|
||||
window.matrixChat = ReactDOM.render(
|
||||
@@ -253,10 +248,19 @@ async function loadApp() {
|
||||
startingFragmentQueryParams={fragparts.params}
|
||||
enableGuest={true}
|
||||
onLoadCompleted={onLoadCompleted}
|
||||
defaultDeviceDisplayName={getDefaultDeviceDisplayName()}
|
||||
defaultDeviceDisplayName={PlatformPeg.get().getDefaultDeviceDisplayName()}
|
||||
/>,
|
||||
document.getElementById('matrixchat')
|
||||
);
|
||||
|
||||
routeUrl(window.location);
|
||||
|
||||
// we didn't propagate screen changes to the URL bar while we were loading; do it now.
|
||||
loaded = true;
|
||||
if (lastLoadedScreen) {
|
||||
onNewScreen(lastLoadedScreen);
|
||||
lastLoadedScreen = null;
|
||||
}
|
||||
}
|
||||
else {
|
||||
console.error("Browser is missing required features.");
|
||||
@@ -267,7 +271,6 @@ async function loadApp() {
|
||||
validBrowser = true;
|
||||
console.log("User accepts the compatibility risks.");
|
||||
loadApp();
|
||||
window.onload(); // still do the same code paths for compatible clients
|
||||
}} />,
|
||||
document.getElementById('matrixchat')
|
||||
);
|
||||
|
||||
41
src/vector/olm-loader.js
Normal file
@@ -0,0 +1,41 @@
|
||||
/*
|
||||
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.
|
||||
*/
|
||||
|
||||
/* a very thin shim for loading olm.js: just sets the global OLM_OPTIONS and
|
||||
* requires the actual olm.js library.
|
||||
*
|
||||
* olm.js reads global.OLM_OPTIONS and defines global.Olm. The latter is fine for us,
|
||||
* but we need to prepare the former.
|
||||
*
|
||||
* We can't use webpack's definePlugin to do this, because we tell webpack not
|
||||
* to parse olm.js. We also can't put this code in index.js, because olm and
|
||||
* index.js are loaded in parallel, and we need to make sure OLM_OPTIONS is set
|
||||
* before olm.js is loaded.
|
||||
*/
|
||||
|
||||
/* total_memory must be a power of two, and at least twice the stack.
|
||||
*
|
||||
* We don't need a lot of stack, but we do need about 128K of heap to encrypt a
|
||||
* 64K event (enough to store the ciphertext and the plaintext, bearing in mind
|
||||
* that the plaintext can only be 48K because base64). We also have about 36K
|
||||
* of statics. So let's have 256K of memory.
|
||||
*/
|
||||
global.OLM_OPTIONS = {
|
||||
TOTAL_STACK: 64*1024,
|
||||
TOTAL_MEMORY: 256*1024,
|
||||
};
|
||||
|
||||
require('olm/olm.js');
|
||||
130
src/vector/platform/ElectronPlatform.js
Normal file
@@ -0,0 +1,130 @@
|
||||
// @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.
|
||||
*/
|
||||
|
||||
import VectorBasePlatform from './VectorBasePlatform';
|
||||
import dis from 'matrix-react-sdk/lib/dispatcher';
|
||||
import q from 'q';
|
||||
|
||||
const electron = require('electron');
|
||||
const remote = electron.remote;
|
||||
|
||||
electron.remote.autoUpdater.on('update-downloaded', onUpdateDownloaded);
|
||||
|
||||
function onUpdateDownloaded(ev, releaseNotes, ver, date, updateURL) {
|
||||
dis.dispatch({
|
||||
action: 'new_version',
|
||||
currentVersion: electron.remote.app.getVersion(),
|
||||
newVersion: ver,
|
||||
releaseNotes: releaseNotes,
|
||||
});
|
||||
}
|
||||
|
||||
function platformFriendlyName() {
|
||||
console.log(window.process);
|
||||
switch (window.process.platform) {
|
||||
case 'darwin':
|
||||
return 'macOS';
|
||||
case 'freebsd':
|
||||
return 'FreeBSD';
|
||||
case 'openbsd':
|
||||
return 'OpenBSD';
|
||||
case 'sunos':
|
||||
return 'SunOS';
|
||||
case 'win32':
|
||||
return 'Windows';
|
||||
default:
|
||||
// Sorry, Linux users: you get lumped into here,
|
||||
// but only because Linux's capitalisation is
|
||||
// normal. We do care about you.
|
||||
return window.process.platform[0].toUpperCase() + window.process.platform.slice(1);
|
||||
}
|
||||
}
|
||||
|
||||
export default class ElectronPlatform extends VectorBasePlatform {
|
||||
setNotificationCount(count: number) {
|
||||
super.setNotificationCount(count);
|
||||
// this sometimes throws because electron is made of fail:
|
||||
// https://github.com/electron/electron/issues/7351
|
||||
// For now, let's catch the error, but I suspect it may
|
||||
// continue to fail and we might just have to accept that
|
||||
// electron's remote RPC is a non-starter for now and use IPC
|
||||
try {
|
||||
remote.app.setBadgeCount(count);
|
||||
} catch (e) {
|
||||
console.error("Failed to set notification count", e);
|
||||
}
|
||||
}
|
||||
|
||||
supportsNotifications() : boolean {
|
||||
return true;
|
||||
}
|
||||
|
||||
maySendNotifications() : boolean {
|
||||
return true;
|
||||
}
|
||||
|
||||
displayNotification(title: string, msg: string, avatarUrl: string, room: Object): Notification {
|
||||
// Notifications in Electron use the HTML5 notification API
|
||||
const notification = new global.Notification(
|
||||
title,
|
||||
{
|
||||
body: msg,
|
||||
icon: avatarUrl,
|
||||
tag: "vector",
|
||||
silent: true, // we play our own sounds
|
||||
}
|
||||
);
|
||||
|
||||
notification.onclick = function() {
|
||||
dis.dispatch({
|
||||
action: 'view_room',
|
||||
room_id: room.roomId
|
||||
});
|
||||
global.focus();
|
||||
electron.remote.getCurrentWindow().restore();
|
||||
};
|
||||
|
||||
return notification;
|
||||
}
|
||||
|
||||
clearNotification(notif: Notification) {
|
||||
notif.close();
|
||||
}
|
||||
|
||||
getAppVersion() {
|
||||
return q(electron.remote.app.getVersion());
|
||||
}
|
||||
|
||||
pollForUpdate() {
|
||||
// In electron we control the update process ourselves, since
|
||||
// it needs to run in the main process, so we just run the timer
|
||||
// loop in the main electron process instead.
|
||||
}
|
||||
|
||||
installUpdate() {
|
||||
// IPC to the main process to install the update, since quitAndInstall
|
||||
// doesn't fire the before-quit event so the main process needs to know
|
||||
// it should exit.
|
||||
electron.ipcRenderer.send('install_update');
|
||||
}
|
||||
|
||||
getDefaultDeviceDisplayName() {
|
||||
return "Riot Desktop on " + platformFriendlyName();
|
||||
}
|
||||
}
|
||||
50
src/vector/platform/VectorBasePlatform.js
Normal file
@@ -0,0 +1,50 @@
|
||||
// @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.
|
||||
*/
|
||||
|
||||
import BasePlatform from 'matrix-react-sdk/lib/BasePlatform'
|
||||
|
||||
/**
|
||||
* Vector-specific extensions to the BasePlatform template
|
||||
*/
|
||||
export default class VectorBasePlatform extends BasePlatform {
|
||||
/**
|
||||
* Check for the availability of an update to the version of the
|
||||
* app that's currently running.
|
||||
* If an update is available, this function should dispatch the
|
||||
* 'new_version' action.
|
||||
*/
|
||||
pollForUpdate() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the currently running app to the latest available
|
||||
* version and replace this instance of the app with the
|
||||
* new version.
|
||||
*/
|
||||
installUpdate() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a sensible default display name for the
|
||||
* device Vector is running on
|
||||
*/
|
||||
getDefaultDeviceDisplayName() {
|
||||
return "Unknown device";
|
||||
}
|
||||
}
|
||||
199
src/vector/platform/WebPlatform.js
Normal file
@@ -0,0 +1,199 @@
|
||||
// @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.
|
||||
*/
|
||||
|
||||
import VectorBasePlatform from './VectorBasePlatform';
|
||||
import Favico from 'favico.js';
|
||||
import request from 'browser-request';
|
||||
import dis from 'matrix-react-sdk/lib/dispatcher.js';
|
||||
import q from 'q';
|
||||
|
||||
import url from 'url';
|
||||
import UAParser from 'ua-parser-js';
|
||||
|
||||
export default class WebPlatform extends VectorBasePlatform {
|
||||
constructor() {
|
||||
super();
|
||||
this.runningVersion = null;
|
||||
// The 'animations' are really low framerate and look terrible.
|
||||
// Also it re-starts the animationb every time you set the badge,
|
||||
// and we set the state each time, even if the value hasn't changed,
|
||||
// so we'd need to fix that if enabling the animation.
|
||||
this.favicon = new Favico({animation: 'none'});
|
||||
this._updateFavicon();
|
||||
}
|
||||
|
||||
_updateFavicon() {
|
||||
try {
|
||||
// This needs to be in in a try block as it will throw
|
||||
// if there are more than 100 badge count changes in
|
||||
// its internal queue
|
||||
let bgColor = "#d00",
|
||||
notif = this.notificationCount;
|
||||
|
||||
if (this.errorDidOccur) {
|
||||
notif = notif || "×";
|
||||
bgColor = "#f00";
|
||||
}
|
||||
|
||||
this.favicon.badge(notif, {
|
||||
bgColor: bgColor
|
||||
});
|
||||
} catch (e) {
|
||||
console.warn(`Failed to set badge count: ${e.message}`);
|
||||
}
|
||||
}
|
||||
|
||||
setNotificationCount(count: number) {
|
||||
super.setNotificationCount(count);
|
||||
this._updateFavicon();
|
||||
}
|
||||
|
||||
setErrorStatus(errorDidOccur: boolean) {
|
||||
super.setErrorStatus(errorDidOccur);
|
||||
this._updateFavicon();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the platform supports displaying
|
||||
* notifications, otherwise false.
|
||||
*/
|
||||
supportsNotifications() : boolean {
|
||||
return Boolean(global.Notification);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the application currently has permission
|
||||
* to display notifications. Otherwise false.
|
||||
*/
|
||||
maySendNotifications() : boolean {
|
||||
return global.Notification.permission == 'granted';
|
||||
}
|
||||
|
||||
/**
|
||||
* Requests permission to send notifications. Returns
|
||||
* a promise that is resolved when the user has responded
|
||||
* to the request. The promise has a single string argument
|
||||
* that is 'granted' if the user allowed the request or
|
||||
* 'denied' otherwise.
|
||||
*/
|
||||
requestNotificationPermission() : Promise {
|
||||
// annoyingly, the latest spec says this returns a
|
||||
// promise, but this is only supported in Chrome 46
|
||||
// and Firefox 47, so adapt the callback API.
|
||||
const defer = q.defer();
|
||||
global.Notification.requestPermission((result) => {
|
||||
defer.resolve(result);
|
||||
});
|
||||
return defer.promise;
|
||||
}
|
||||
|
||||
displayNotification(title: string, msg: string, avatarUrl: string, room: Object) {
|
||||
const notification = new global.Notification(
|
||||
title,
|
||||
{
|
||||
body: msg,
|
||||
icon: avatarUrl,
|
||||
tag: "vector",
|
||||
silent: true, // we play our own sounds
|
||||
}
|
||||
);
|
||||
|
||||
notification.onclick = function() {
|
||||
dis.dispatch({
|
||||
action: 'view_room',
|
||||
room_id: room.roomId
|
||||
});
|
||||
global.focus();
|
||||
notification.close();
|
||||
};
|
||||
|
||||
// Chrome only dismisses notifications after 20s, which
|
||||
// is waaaaay too long
|
||||
global.setTimeout(function() {
|
||||
notification.close();
|
||||
}, 5 * 1000);
|
||||
}
|
||||
|
||||
_getVersion() {
|
||||
const deferred = q.defer();
|
||||
|
||||
// We add a cachebuster to the request to make sure that we know about
|
||||
// the most recent version on the origin server. That might not
|
||||
// actually be the version we'd get on a reload (particularly in the
|
||||
// presence of intermediate caching proxies), but still: we're trying
|
||||
// to tell the user that there is a new version.
|
||||
request(
|
||||
{
|
||||
method: "GET",
|
||||
url: "version",
|
||||
qs: { cachebuster: Date.now() },
|
||||
},
|
||||
(err, response, body) => {
|
||||
if (err || response.status < 200 || response.status >= 300) {
|
||||
if (err == null) err = { status: response.status };
|
||||
deferred.reject(err);
|
||||
return;
|
||||
}
|
||||
|
||||
const ver = body.trim();
|
||||
deferred.resolve(ver);
|
||||
}
|
||||
);
|
||||
return deferred.promise;
|
||||
}
|
||||
|
||||
getAppVersion() {
|
||||
if (this.runningVersion !== null) {
|
||||
return q(this.runningVersion);
|
||||
}
|
||||
return this._getVersion();
|
||||
}
|
||||
|
||||
pollForUpdate() {
|
||||
this._getVersion().done((ver) => {
|
||||
if (this.runningVersion == null) {
|
||||
this.runningVersion = ver;
|
||||
} else if (this.runningVersion != ver) {
|
||||
dis.dispatch({
|
||||
action: 'new_version',
|
||||
currentVersion: this.runningVersion,
|
||||
newVersion: ver,
|
||||
});
|
||||
}
|
||||
}, (err) => {
|
||||
console.error("Failed to poll for update", err);
|
||||
});
|
||||
}
|
||||
|
||||
installUpdate() {
|
||||
window.location.reload();
|
||||
}
|
||||
|
||||
getDefaultDeviceDisplayName() {
|
||||
// strip query-string and fragment from uri
|
||||
let u = url.parse(window.location.href);
|
||||
u.search = "";
|
||||
u.hash = "";
|
||||
let app_name = u.format();
|
||||
|
||||
let ua = new UAParser();
|
||||
return app_name + " via " + ua.getBrowser().name +
|
||||
" on " + ua.getOS().name;
|
||||
}
|
||||
}
|
||||
29
src/vector/platform/index.js
Normal file
@@ -0,0 +1,29 @@
|
||||
// @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.
|
||||
*/
|
||||
|
||||
let Platform = null;
|
||||
|
||||
if (window && window.process && window.process && window.process.type === 'renderer') {
|
||||
// we're running inside electron
|
||||
Platform = require('./ElectronPlatform');
|
||||
} else {
|
||||
Platform = require('./WebPlatform');
|
||||
}
|
||||
|
||||
export default Platform;
|
||||
@@ -13,48 +13,18 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
import PlatformPeg from 'matrix-react-sdk/lib/PlatformPeg';
|
||||
|
||||
var POKE_RATE_MS = 10 * 60 * 1000; // 10 min
|
||||
var currentVersion = null;
|
||||
var latestVersion = null;
|
||||
var listener = function(){}; // NOP
|
||||
|
||||
module.exports = {
|
||||
setVersionListener: function(fn) { // invoked with fn(currentVer, newVer)
|
||||
listener = fn;
|
||||
start: function() {
|
||||
module.exports.poll();
|
||||
setInterval(module.exports.poll, POKE_RATE_MS);
|
||||
},
|
||||
|
||||
run: function() {
|
||||
var req = new XMLHttpRequest();
|
||||
req.addEventListener("load", function() {
|
||||
if (!req.responseText) {
|
||||
return;
|
||||
}
|
||||
var ver = req.responseText.trim();
|
||||
if (!currentVersion) {
|
||||
currentVersion = ver;
|
||||
listener(currentVersion, currentVersion);
|
||||
}
|
||||
|
||||
if (ver !== latestVersion) {
|
||||
latestVersion = ver;
|
||||
if (module.exports.hasNewVersion()) {
|
||||
console.log("Current=%s Latest=%s", currentVersion, latestVersion);
|
||||
listener(currentVersion, latestVersion);
|
||||
}
|
||||
}
|
||||
});
|
||||
var cacheBuster = "?ts=" + new Date().getTime();
|
||||
req.open("GET", "version" + cacheBuster);
|
||||
req.send(); // can't suppress 404s from being logged.
|
||||
|
||||
setTimeout(module.exports.run, POKE_RATE_MS);
|
||||
},
|
||||
|
||||
getCurrentVersion: function() {
|
||||
return currentVersion;
|
||||
},
|
||||
|
||||
hasNewVersion: function() {
|
||||
return currentVersion && latestVersion && (currentVersion !== latestVersion);
|
||||
poll: function() {
|
||||
PlatformPeg.get().pollForUpdate();
|
||||
}
|
||||
};
|
||||
|
||||
@@ -77,6 +77,7 @@ describe('joining a room', function () {
|
||||
httpBackend.when('POST', '/filter').respond(200, { filter_id: 'fid' });
|
||||
httpBackend.when('GET', '/sync').respond(200, {});
|
||||
httpBackend.when('POST', '/publicRooms').respond(200, {chunk: []});
|
||||
httpBackend.when('GET', '/thirdparty/protocols').respond(200, {});
|
||||
httpBackend.when('GET', '/directory/room/'+encodeURIComponent(ROOM_ALIAS)).respond(200, { room_id: ROOM_ID });
|
||||
|
||||
// start with a logged-in client
|
||||
@@ -132,6 +133,12 @@ describe('joining a room', function () {
|
||||
httpBackend.when('POST', '/join/'+encodeURIComponent(ROOM_ALIAS))
|
||||
.respond(200, {room_id: ROOM_ID});
|
||||
return httpBackend.flush();
|
||||
}).then(() => {
|
||||
// wait for the join request to be made
|
||||
return q.delay(1);
|
||||
}).then(() => {
|
||||
// flush it through
|
||||
return httpBackend.flush();
|
||||
}).then(() => {
|
||||
httpBackend.verifyNoOutstandingExpectation();
|
||||
|
||||
|
||||
@@ -16,6 +16,8 @@ limitations under the License.
|
||||
|
||||
/* loading.js: test the myriad paths we have for loading the application */
|
||||
|
||||
import 'skin-sdk';
|
||||
|
||||
import React from 'react';
|
||||
import ReactDOM from 'react-dom';
|
||||
import ReactTestUtils from 'react-addons-test-utils';
|
||||
@@ -152,6 +154,9 @@ describe('loading:', function () {
|
||||
}).respond(403, "Guest access is disabled");
|
||||
|
||||
return httpBackend.flush();
|
||||
}).then(() => {
|
||||
// Wait for another trip around the event loop for the UI to update
|
||||
return q.delay(1);
|
||||
}).then(() => {
|
||||
// we expect a single <Login> component
|
||||
ReactTestUtils.findRenderedComponentWithType(
|
||||
@@ -175,6 +180,9 @@ describe('loading:', function () {
|
||||
}).respond(403, "Guest access is disabled");
|
||||
|
||||
return httpBackend.flush();
|
||||
}).then(() => {
|
||||
// Wait for another trip around the event loop for the UI to update
|
||||
return q.delay(1);
|
||||
}).then(() => {
|
||||
// we expect a single <Login> component
|
||||
let login = ReactTestUtils.findRenderedComponentWithType(
|
||||
@@ -189,6 +197,9 @@ describe('loading:', function () {
|
||||
});
|
||||
login.onPasswordLogin("user", "pass")
|
||||
return httpBackend.flush();
|
||||
}).then(() => {
|
||||
// Wait for another trip around the event loop for the UI to update
|
||||
return q.delay(1);
|
||||
}).then(() => {
|
||||
// we expect a spinner
|
||||
ReactTestUtils.findRenderedComponentWithType(
|
||||
@@ -198,6 +209,9 @@ describe('loading:', function () {
|
||||
httpBackend.when('POST', '/filter').respond(200, { filter_id: 'fid' });
|
||||
httpBackend.when('GET', '/sync').respond(200, {});
|
||||
return httpBackend.flush();
|
||||
}).then(() => {
|
||||
// Wait for another trip around the event loop for the UI to update
|
||||
return q.delay(1);
|
||||
}).then(() => {
|
||||
// once the sync completes, we should have a room view
|
||||
httpBackend.verifyNoOutstandingExpectation();
|
||||
@@ -285,6 +299,9 @@ describe('loading:', function () {
|
||||
});
|
||||
|
||||
return httpBackend.flush();
|
||||
}).then(() => {
|
||||
// Wait for another trip around the event loop for the UI to update
|
||||
return q.delay(1);
|
||||
}).then(() => {
|
||||
// now we should have a spinner with a logout link
|
||||
assertAtSyncingSpinner(matrixChat);
|
||||
@@ -320,6 +337,9 @@ describe('loading:', function () {
|
||||
});
|
||||
|
||||
return httpBackend.flush();
|
||||
}).then(() => {
|
||||
// Wait for another trip around the event loop for the UI to update
|
||||
return q.delay(1);
|
||||
}).then(() => {
|
||||
// now we should have a spinner with a logout link
|
||||
assertAtSyncingSpinner(matrixChat);
|
||||
@@ -356,12 +376,18 @@ describe('loading:', function () {
|
||||
});
|
||||
|
||||
return httpBackend.flush();
|
||||
}).then(() => {
|
||||
// Wait for another trip around the event loop for the UI to update
|
||||
return q.delay(1);
|
||||
}).then(() => {
|
||||
// now we should have a spinner with a logout link
|
||||
assertAtSyncingSpinner(matrixChat);
|
||||
|
||||
httpBackend.when('GET', '/sync').respond(200, {});
|
||||
return httpBackend.flush();
|
||||
}).then(() => {
|
||||
// Wait for another trip around the event loop for the UI to update
|
||||
return q.delay(1);
|
||||
}).then(() => {
|
||||
// once the sync completes, we should have a room view
|
||||
httpBackend.verifyNoOutstandingExpectation();
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
../src/skins/vector/fonts
|
||||
@@ -1 +0,0 @@
|
||||
../src/skins/vector/img/
|
||||
|
Before Width: | Height: | Size: 11 KiB |
|
Before Width: | Height: | Size: 15 KiB |